Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change storage of PKI entries from colons to hyphens #2575

Merged
merged 11 commits into from
May 3, 2017
42 changes: 39 additions & 3 deletions builtin/logical/pki/cert_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,32 +185,68 @@ func fetchCAInfo(req *logical.Request) (*caInfoBundle, error) {
// separate pathing for CA, CRL, and revoked certificates.
func fetchCertBySerial(req *logical.Request, prefix, serial string) (*logical.StorageEntry, error) {
var path string
var err error
var certEntry *logical.StorageEntry

switch {
// Revoked goes first as otherwise ca/crl get hardcoded paths which fail if
// we actually want revocation info
case strings.HasPrefix(prefix, "revoked/"):
path = "revoked/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
path = "revoked/" + strings.Replace(strings.ToLower(serial), ":", "-", -1)
case serial == "ca":
path = "ca"
case serial == "crl":
path = "crl"
default:
path = "certs/" + strings.Replace(strings.ToLower(serial), ":", "-", -1)
}

certEntry, err = req.Storage.Get(path)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)}
}
if certEntry != nil {
if certEntry.Value == nil || len(certEntry.Value) == 0 {
return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)}
}
return certEntry, nil
}

// No point checking these, no old/new style colons/hyphens
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this comment makes sense any more. We should keep the check with path for clarity, or update the comment to say why return if legacyPath is empty.

if path == "ca" || path == "crl" {
return nil, nil
}

// Save the desired path
desiredPath := path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path is no longer needed. You can use hyphenSerial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nevermind. That does not work. That was also one of the reason I wanted to move away from the multiple replacements... Maybe it makes sense to create the legacyPath and path instead of just tracking the serial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, path in this case includes the prefix, so I think we have to save it as desiredPath instead of simply using hyphenSerial.


// If we get here we need to check for old-style paths using colons
switch {
case strings.HasPrefix(prefix, "revoked/"):
path = "revoked/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of flipping the path back and forth, could we store two path variables and use them as we need them? I think this would improve the readability of this code.

default:
path = "certs/" + strings.Replace(strings.ToLower(serial), "-", ":", -1)
}

certEntry, err := req.Storage.Get(path)
certEntry, err = req.Storage.Get(path)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)}
}
if certEntry == nil {
return nil, nil
}

if certEntry.Value == nil || len(certEntry.Value) == 0 {
return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)}
}

certEntry.Key = desiredPath
if err = req.Storage.Put(certEntry); err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error saving certificate with serial %s to new location", serial)}
}
if err = req.Storage.Delete(path); err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error deleting certificate with serial %s from old location", serial)}
}

return certEntry, nil
}

Expand Down
86 changes: 86 additions & 0 deletions builtin/logical/pki/cert_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package pki

import (
"testing"

"github.com/hashicorp/vault/logical"
)

func TestFetchCertBySerial(t *testing.T) {
storage := &logical.InmemStorage{}

cases := map[string]struct {
Req *logical.Request
StorageKey string
}{
"cert, valid colon": {
&logical.Request{
Operation: logical.ReadOperation,
Path: "certs/10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6",
Storage: storage,
},
"10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6",
},
"cert, revoked colon": {
&logical.Request{
Operation: logical.ReadOperation,
Path: "revoked/10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6",
Storage: storage,
},
"10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6",
},
"cert, valid hyphen": {
&logical.Request{
Operation: logical.ReadOperation,
Path: "certs/10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6",
Storage: storage,
},
"10-e6-fc-62-b7-41-8a-d5-00-5e-45-b6",
},
"cert, revoked hyphen": {
&logical.Request{
Operation: logical.ReadOperation,
Path: "revoked/10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6",
Storage: storage,
},
"10-e6-fc-62-b7-41-8a-d5-00-5e-45-b6",
},
"cert, ca": {
&logical.Request{
Operation: logical.ReadOperation,
Path: "ca",
Storage: storage,
},
"",
},
"cert, crl": {
&logical.Request{
Operation: logical.ReadOperation,
Path: "crl",
Storage: storage,
},
"",
},
}

for name, tc := range cases {
// Put pseudo-cert in inmem storage
err := storage.Put(&logical.StorageEntry{
Key: tc.Req.Path,
Value: []byte("some data"),
})
if err != nil {
t.Fatalf("error writing to storage on %s: %s", name, err)
}

certEntry, err := fetchCertBySerial(tc.Req, tc.Req.Path, tc.StorageKey)
if err != nil {
t.Fatalf("fetchBySerial error on %s: %s", name, err)
}

// Check for non-nil on valid/revoked certs
if certEntry == nil && tc.Req.Path != "ca" && tc.Req.Path != "crl" {
t.Fatalf("fetchBySerial returned nil on %s", name)
}
}
}
5 changes: 3 additions & 2 deletions builtin/logical/pki/crl_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"strings"
"time"

"github.com/hashicorp/vault/helper/errutil"
Expand Down Expand Up @@ -65,7 +66,7 @@ func revokeCert(b *backend, req *logical.Request, serial string, fromLease bool)

cert, err := x509.ParseCertificate(certEntry.Value)
if err != nil {
return nil, fmt.Errorf("Error parsing certificate")
return nil, fmt.Errorf("Error parsing certificate: %s", err)
}
if cert == nil {
return nil, fmt.Errorf("Got a nil certificate")
Expand All @@ -86,7 +87,7 @@ func revokeCert(b *backend, req *logical.Request, serial string, fromLease bool)
revInfo.RevocationTime = currTime.Unix()
revInfo.RevocationTimeUTC = currTime.UTC()

revEntry, err = logical.StorageEntryJSON("revoked/"+serial, revInfo)
revEntry, err = logical.StorageEntryJSON("revoked/"+strings.ToLower(strings.Replace(serial, ":", "-", -1)), revInfo)
if err != nil {
return nil, fmt.Errorf("Error creating revocation entry")
}
Expand Down
126 changes: 126 additions & 0 deletions builtin/logical/pki/crl_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package pki

import (
"encoding/pem"
"io/ioutil"
"testing"
"time"

"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/helper/jsonutil"
"github.com/hashicorp/vault/logical"
)

func TestRevokeCert(t *testing.T) {
storage := &logical.InmemStorage{}
config := logical.TestBackendConfig()
config.StorageView = storage

b := Backend()
_, err := b.Setup(config)
if err != nil {
t.Fatal(err)
}

// Place CA cert in storage
rootCAKeyPEM, err := ioutil.ReadFile("test-fixtures/root/rootcakey.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
rootCACertPEM, err := ioutil.ReadFile("test-fixtures/root/rootcacert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
cb := &certutil.CertBundle{}
cb.PrivateKey = string(rootCAKeyPEM)
cb.PrivateKeyType = certutil.RSAPrivateKey
cb.Certificate = string(rootCACertPEM)

bundleEntry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
if err != nil {
t.Fatal(err)
}
err = storage.Put(bundleEntry)
if err != nil {
t.Fatal(err)
}

certValue, err := ioutil.ReadFile("test-fixtures/keys/cert.pem")
if err != nil {
t.Fatalf("err: %v", err)
}
block, _ := pem.Decode(certValue)
if block == nil {
t.Fatal("failed to decode PEM cert into DER")
}
certDER := block.Bytes

var revInfo revocationInfo
currTime := time.Now()
revInfo.CertificateBytes = certDER
revInfo.RevocationTime = currTime.Unix()
revInfo.RevocationTimeUTC = currTime.UTC()
encodedCertDER, err := jsonutil.EncodeJSON(revInfo)
if err != nil {
t.Fatalf("error encoding pseudo cert value: %s", err)
}

cases := map[string]struct {
Req *logical.Request
StorageKey string
StorageValue []byte
}{
"cert, valid colon": {
&logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/7f:e8:e1:29:31:41:9e:a4:ac:df:82:08:d1:64:b5:2f:84:2c:6d:b0",
Storage: storage,
},
"7f:e8:e1:29:31:41:9e:a4:ac:df:82:08:d1:64:b5:2f:84:2c:6d:b0",
certDER,
},
"cert, revoked colon": {
&logical.Request{
Operation: logical.UpdateOperation,
Path: "revoked/7f:e8:e1:29:31:41:9e:a4:ac:df:82:08:d1:64:b5:2f:84:2c:6d:b0",
Storage: storage,
},
"7f:e8:e1:29:31:41:9e:a4:ac:df:82:08:d1:64:b5:2f:84:2c:6d:b0",
encodedCertDER,
},
"cert, valid hyphen": {
&logical.Request{
Operation: logical.UpdateOperation,
Path: "certs/7f:e8:e1:29:31:41:9e:a4:ac:df:82:08:d1:64:b5:2f:84:2c:6d:b0",
Storage: storage,
},
"7f-e8-e1-29-31-41-9e-a4-ac-df-82-08-d1-64-b5-2f-84-2c-6d-b0",
certDER,
},
"cert, revoked hyphen": {
&logical.Request{
Operation: logical.UpdateOperation,
Path: "revoked/7f:e8:e1:29:31:41:9e:a4:ac:df:82:08:d1:64:b5:2f:84:2c:6d:b0",
Storage: storage,
},
"7f-e8-e1-29-31-41-9e-a4-ac-df-82-08-d1-64-b5-2f-84-2c-6d-b0",
encodedCertDER,
},
}

for name, tc := range cases {
// Put pseudo-cert in inmem storage
err := storage.Put(&logical.StorageEntry{
Key: tc.Req.Path,
Value: tc.StorageValue,
})
if err != nil {
t.Fatalf("error writing to storage on %s: %s", name, err)
}

resp, err := revokeCert(b, tc.Req, tc.StorageKey, false)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v resp: %#v", err, resp)
}
}
}
3 changes: 2 additions & 1 deletion builtin/logical/pki/path_intermediate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pki
import (
"encoding/base64"
"fmt"
"strings"

"github.com/hashicorp/vault/helper/certutil"
"github.com/hashicorp/vault/helper/errutil"
Expand Down Expand Up @@ -196,7 +197,7 @@ func (b *backend) pathSetSignedIntermediate(
return nil, err
}

entry.Key = "certs/" + cb.SerialNumber
entry.Key = "certs/" + strings.ToLower(strings.Replace(cb.SerialNumber, ":", "-", -1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have this creation of hyphenSerial in a function and use it in all the places? Seems like we are doing it in a couple of places. Easy to miss out if some if we make changes to this.

entry.Value = inputBundle.CertificateBytes
err = req.Storage.Put(entry)
if err != nil {
Expand Down
Loading