Merge pull request #65 from docker/adding-the-not-yummy-kind-of-tofu

Adding the not yummy kind of tofu
This commit is contained in:
Nathan McCauley 2015-07-17 15:59:30 -07:00
commit a90a8b27b3
13 changed files with 805 additions and 117 deletions

File diff suppressed because one or more lines are too long

View File

@ -64,7 +64,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
gunOrID := args[0] gunOrID := args[0]
// Try to retrieve the ID from the CA store. // Try to retrieve the ID from the CA store.
cert, err := caStore.GetCertificateByKeyID(gunOrID) cert, err := caStore.GetCertificateByCertID(gunOrID)
if err == nil { if err == nil {
fmt.Printf("Removing: ") fmt.Printf("Removing: ")
printCert(cert) printCert(cert)
@ -78,7 +78,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
} }
// Try to retrieve the ID from the Certificate store. // Try to retrieve the ID from the Certificate store.
cert, err = certificateStore.GetCertificateByKeyID(gunOrID) cert, err = certificateStore.GetCertificateByCertID(gunOrID)
if err == nil { if err == nil {
fmt.Printf("Removing: ") fmt.Printf("Removing: ")
printCert(cert) printCert(cert)
@ -216,12 +216,12 @@ func keysGenerate(cmd *cobra.Command, args []string) {
func printCert(cert *x509.Certificate) { func printCert(cert *x509.Certificate) {
timeDifference := cert.NotAfter.Sub(time.Now()) timeDifference := cert.NotAfter.Sub(time.Now())
keyID, err := trustmanager.FingerprintCert(cert) certID, err := trustmanager.FingerprintCert(cert)
if err != nil { if err != nil {
fatalf("could not fingerprint certificate: %v", err) fatalf("could not fingerprint certificate: %v", err)
} }
fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, keyID, math.Floor(timeDifference.Hours()/24)) fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24))
} }
func printKey(keyPath string) { func printKey(keyPath string) {

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBpDCCAUqgAwIBAgIRAIquZ7lRJj1Um030Kd7GFXgwCgYIKoZIzj0EAwIwODEa
MBgGA1UEChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20v
bm90YXJ5MB4XDTE1MDcxNzAwMzE1NFoXDTE3MDcxNjAwMzE1NFowODEaMBgGA1UE
ChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20vbm90YXJ5
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjnnozttLzYgIN5fL8ZwYbsMig0pj
HSNupVTPjDIrLUYUnoQfG6IQ0E2BMixEGnI/A9WreeXP2oz06LZ4SROMQqM1MDMw
DgYDVR0PAQH/BAQDAgCgMBMGA1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQC
MAAwCgYIKoZIzj0EAwIDSAAwRQIgT9cxottjza9BBQcMsoB/Uf2JYXWgSkp9QMXT
8mG4mMICIQDMYWFdgn5u8nDeThJ+bG8Lu5nIGb/NWEOFtU0xQv913Q==
-----END CERTIFICATE-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBqDCCAU6gAwIBAgIRAM1vKVhmZuWcrogc3ASBaZUwCgYIKoZIzj0EAwIwOjEb
MBkGA1UEChMSc2VjdXJlLmV4YW1wbGUuY29tMRswGQYDVQQDExJzZWN1cmUuZXhh
bXBsZS5jb20wHhcNMTUwNzE3MDU1NTIzWhcNMTcwNzE2MDU1NTIzWjA6MRswGQYD
VQQKExJzZWN1cmUuZXhhbXBsZS5jb20xGzAZBgNVBAMTEnNlY3VyZS5leGFtcGxl
LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI556M7bS82ICDeXy/GcGG7D
IoNKYx0jbqVUz4wyKy1GFJ6EHxuiENBNgTIsRBpyPwPVq3nlz9qM9Oi2eEkTjEKj
NTAzMA4GA1UdDwEB/wQEAwIAoDATBgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMB
Af8EAjAAMAoGCCqGSM49BAMCA0gAMEUCIER2XCkQ8dUWBZEUeT5kABg7neiHPtSL
VVE6bJxu2sxlAiEAkRG6u1ieXKGl38gUkCn75Yvo9nOSLdh0gtxUUcOXvUc=
-----END CERTIFICATE-----

View File

@ -3,8 +3,6 @@ package keystoremanager
import ( import (
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"encoding/json"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
@ -36,6 +34,15 @@ const (
rsaRootKeySize = 4096 // Used for new root keys rsaRootKeySize = 4096 // Used for new root keys
) )
var (
// ErrValidationFail is returned when there is no trusted certificate in any of the
// root keys available in the roots.json
ErrValidationFail = errors.New("could not validate the path to a trusted root")
// ErrRootRotationFail is returned when we fail to do a full root key rotation
// by either failing to add the new root certificate, or delete the old ones
ErrRootRotationFail = errors.New("could not rotate trust to a new trusted root")
)
// NewKeyStoreManager returns an initialized KeyStoreManager, or an error // NewKeyStoreManager returns an initialized KeyStoreManager, or an error
// if it fails to create the KeyFileStores or load certificates // if it fails to create the KeyFileStores or load certificates
func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) { func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) {
@ -166,79 +173,166 @@ attempts to validate the certificate by first checking for an exact match on
the certificate store, and subsequently trying to find a valid chain on the the certificate store, and subsequently trying to find a valid chain on the
trustedCAStore. trustedCAStore.
When this is being used with a notary repository, the dnsName parameter should Currently this method operates on a Trust On First Use (TOFU) model: if we
be the GUN associated with the repository. have never seen a certificate for a particular CN, we trust it. If later we see
a different certificate for that certificate, we return an ErrValidationFailed error.
Example TUF Content for root role: Note that since we only allow trust data to be downloaded over an HTTPS channel
"roles" : { we are using the current web-of-trust to validate the first download of the certificate
"root" : { adding an extra layer of security over the normal (SSH style) trust model.
"threshold" : 1, We shall call this: TOFUS.
"keyids" : [
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38"
]
},
...
}
Example TUF Content for root key: ValidateRoot also supports root key rotation, trusting a new certificate that has
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : { been included in the roots.json, and removing trust in the old one.
"keytype" : "RSA",
"keyval" : {
"private" : "",
"public" : "Base64-encoded, PEM encoded x509 Certificate"
}
}
*/ */
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error { func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error {
rootSigned := &data.Root{} logrus.Debugf("entered ValidateRoot with dns: %s", dnsName)
err := json.Unmarshal(root.Signed, rootSigned) rootSigned, err := data.RootFromSigned(root)
if err != nil { if err != nil {
return err return err
} }
certs := make(map[string]data.PublicKey) // validKeys will store all the keys that were considered valid either by
for _, keyID := range rootSigned.Roles["root"].KeyIDs { // direct certificate match, or CA chain path
// TODO(dlaw): currently assuming only one cert contained in validKeys := make(map[string]data.PublicKey)
// public key entry. Need to fix when we want to pass in chains.
k, _ := pem.Decode([]byte(rootSigned.Keys[keyID].Public())) // allCerts will keep a list of all leafCerts that were found, and is used
decodedCerts, err := x509.ParseCertificates(k.Bytes) // to aid on root certificate rotation
allCerts := make(map[string]*x509.Certificate)
// Before we loop through all root keys available, make sure any exist
rootRoles, ok := rootSigned.Signed.Roles["root"]
if !ok {
return errors.New("no root roles found in tuf metadata")
}
logrus.Debugf("found the following root keys in roots.json: %v", rootRoles.KeyIDs)
// Iterate over every keyID for the root role inside of roots.json
for _, keyID := range rootRoles.KeyIDs {
// Decode all the x509 certificates that were bundled with this
// Specific root key
decodedCerts, err := trustmanager.LoadCertBundleFromPEM([]byte(rootSigned.Signed.Keys[keyID].Public()))
if err != nil { if err != nil {
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err) logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
continue continue
} }
// TODO(diogo): Assuming that first certificate is the leaf-cert. Need to
// iterate over all decodedCerts and find a non-CA one (should be the last).
leafCert := decodedCerts[0]
// Get all non-CA certificates in the decoded certificates
leafCerts := trustmanager.GetLeafCerts(decodedCerts)
// If we got no leaf certificates or we got more than one, fail
if len(leafCerts) != 1 {
logrus.Debugf("wasn't able to find a leaf certificate in the chain of keyID: %s", keyID)
continue
}
// Get the ID of the leaf certificate
leafCert := leafCerts[0]
leafID, err := trustmanager.FingerprintCert(leafCert) leafID, err := trustmanager.FingerprintCert(leafCert)
if err != nil { if err != nil {
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err) logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
continue continue
} }
// Check to see if there is an exact match of this certificate. // Validate that this leaf certificate has a CN that matches the exact gun
// Checking the CommonName is not required since ID is calculated over if leafCert.Subject.CommonName != dnsName {
// Cert.Raw. It's included to prevent breaking logic with changes of how the logrus.Debugf("error leaf certificate CN: %s doesn't match the given dns name: %s", leafCert.Subject.CommonName, dnsName)
// ID gets computed. continue
_, err = km.trustedCertificateStore.GetCertificateByKeyID(leafID)
if err == nil && leafCert.Subject.CommonName == dnsName {
certs[keyID] = rootSigned.Keys[keyID]
} }
// Check to see if this leafCertificate has a chain to one of the Root CAs // Add all the valid leafs to the certificates map so we can refer to them later
// of our CA Store. allCerts[leafID] = leafCert
certList := []*x509.Certificate{leafCert}
err = trustmanager.Verify(km.trustedCAStore, dnsName, certList) // Retrieve all the trusted certificates that match this dns Name
certsForCN, err := km.trustedCertificateStore.GetCertificatesByCN(dnsName)
if err != nil {
// If the error that we get back is different than ErrNoCertificatesFound
// we couldn't check if there are any certificates with this CN already
// trusted. Let's take the conservative approach and not trust this key
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
logrus.Debugf("error retrieving certificates for: %s, %v", dnsName, err)
continue
}
}
// If there are no certificates with this CN, lets TOFUS!
// Note that this logic should only exist in docker 1.8
if len(certsForCN) == 0 {
km.trustedCertificateStore.AddCert(leafCert)
certsForCN = append(certsForCN, leafCert)
logrus.Debugf("using TOFUS on %s with keyID: %s", dnsName, leafID)
}
// Iterate over all known certificates for this CN and see if any are trusted
for _, cert := range certsForCN {
// Check to see if there is an exact match of this certificate.
certID, err := trustmanager.FingerprintCert(cert)
if err == nil && certID == leafID {
validKeys[keyID] = rootSigned.Signed.Keys[keyID]
logrus.Debugf("found an exact match for %s with keyID: %s", dnsName, keyID)
}
}
// Check to see if this leafCertificate has a chain to one of the Root
// CAs of our CA Store.
err = trustmanager.Verify(km.trustedCAStore, dnsName, decodedCerts)
if err == nil { if err == nil {
certs[keyID] = rootSigned.Keys[keyID] validKeys[keyID] = rootSigned.Signed.Keys[keyID]
logrus.Debugf("found a CA path for %s with keyID: %s", dnsName, keyID)
} }
} }
if len(certs) < 1 { if len(validKeys) < 1 {
return errors.New("could not validate the path to a trusted root") logrus.Debugf("wasn't able to trust any of the root keys")
return ErrValidationFail
} }
_, err = signed.VerifyRoot(root, 0, certs, 1) // TODO(david): change hardcoded minversion on TUF.
newRootKey, err := signed.VerifyRoot(root, 0, validKeys, 1)
if err != nil {
return err
}
return err // VerifyRoot returns a non-nil value if there is a root key rotation happening.
// If this happens, we should replace the old root of trust with the new one
if newRootKey != nil {
logrus.Debugf("got a new root key to rotate to: %s", newRootKey.ID())
// Retrieve the certificate associated with the new root key and trust it
newRootKeyCert, ok := allCerts[newRootKey.ID()]
// Paranoid check for the certificate still being in the map
if !ok {
logrus.Debugf("error while retrieving new root certificate with keyID: %s, %v", newRootKey.ID(), err)
return ErrRootRotationFail
}
// Add the new root certificate to our certificate store
err := km.trustedCertificateStore.AddCert(newRootKeyCert)
if err != nil {
// Ignore the error if the certificate already exists
if _, ok := err.(*trustmanager.ErrCertExists); !ok {
logrus.Debugf("error while adding new root certificate with keyID: %s, %v", newRootKey.ID(), err)
return ErrRootRotationFail
}
logrus.Debugf("root certificate already exists in keystore: %s", newRootKey.ID())
}
// Remove the new root certificate from the certificate mapping so we
// can remove trust from all of the remaining ones
delete(allCerts, newRootKey.ID())
// Iterate over all old valid certificates and remove them, essentially
// finishing the rotation of the currently trusted root certificate
for _, cert := range allCerts {
err := km.trustedCertificateStore.RemoveCert(cert)
if err != nil {
logrus.Debugf("error while removing old root certificate: %v", err)
return ErrRootRotationFail
}
logrus.Debugf("removed trust from old root certificate")
}
}
logrus.Debugf("Root validation succeeded")
return nil
} }

View File

@ -162,3 +162,8 @@ func listKeys(s LimitedFileStore) []string {
} }
return keyIDList return keyIDList
} }
// RemoveKey removes the key from the keyfilestore
func (s *KeyFileStore) RemoveKey(name string) error {
return s.Remove(name)
}

View File

@ -251,3 +251,53 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) {
t.Fatalf("expected error while decrypting the content due to invalid passphrase") t.Fatalf("expected error while decrypting the content due to invalid passphrase")
} }
} }
func TestRemoveKey(t *testing.T) {
testName := "docker.com/notary/root"
testExt := "key"
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
if err != nil {
t.Fatalf("failed to create a temporary directory: %v", err)
}
defer os.RemoveAll(tempBaseDir)
// Since we're generating this manually we need to add the extension '.'
expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt)
// Create our store
store, err := NewKeyFileStore(tempBaseDir)
if err != nil {
t.Fatalf("failed to create new key filestore: %v", err)
}
privKey, err := GenerateRSAKey(rand.Reader, 512)
if err != nil {
t.Fatalf("could not generate private key: %v", err)
}
// Call the AddKey function
err = store.AddKey(testName, privKey)
if err != nil {
t.Fatalf("failed to add file to store: %v", err)
}
// Check to see if file exists
_, err = ioutil.ReadFile(expectedFilePath)
if err != nil {
t.Fatalf("expected file not found: %v", err)
}
// Call remove key
err = store.RemoveKey(testName)
if err != nil {
t.Fatalf("unable to remove key: %v", err)
}
// Check to see if file still exists
_, err = ioutil.ReadFile(expectedFilePath)
if err == nil {
t.Fatalf("file should not exist %s", expectedFilePath)
}
}

View File

@ -50,14 +50,14 @@ func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (
} }
// AddCert creates a filename for a given cert and adds a certificate with that name // AddCert creates a filename for a given cert and adds a certificate with that name
func (s X509FileStore) AddCert(cert *x509.Certificate) error { func (s *X509FileStore) AddCert(cert *x509.Certificate) error {
if cert == nil { if cert == nil {
return errors.New("adding nil Certificate to X509Store") return errors.New("adding nil Certificate to X509Store")
} }
// Check if this certificate meets our validation criteria // Check if this certificate meets our validation criteria
if !s.validate.Validate(cert) { if !s.validate.Validate(cert) {
return errors.New("certificate validation failed") return &ErrCertValidation{}
} }
// Attempt to write the certificate to the file // Attempt to write the certificate to the file
if err := s.addNamedCert(cert); err != nil { if err := s.addNamedCert(cert); err != nil {
@ -69,16 +69,16 @@ func (s X509FileStore) AddCert(cert *x509.Certificate) error {
// addNamedCert allows adding a certificate while controling the filename it gets // addNamedCert allows adding a certificate while controling the filename it gets
// stored under. If the file does not exist on disk, saves it. // stored under. If the file does not exist on disk, saves it.
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error { func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error {
fileName, keyID, err := fileName(cert) fileName, certID, err := fileName(cert)
if err != nil { if err != nil {
return err return err
} }
logrus.Debug("Adding cert with keyID: ", keyID) logrus.Debug("Adding cert with certID: ", certID)
// Validate if we already loaded this certificate before // Validate if we already added this certificate before
if _, ok := s.fingerprintMap[keyID]; ok { if _, ok := s.fingerprintMap[certID]; ok {
return errors.New("certificate already in the store") return &ErrCertExists{}
} }
// Convert certificate to PEM // Convert certificate to PEM
@ -98,28 +98,28 @@ func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
} }
// We wrote the certificate succcessfully, add it to our in-memory storage // We wrote the certificate succcessfully, add it to our in-memory storage
s.fingerprintMap[keyID] = cert s.fingerprintMap[certID] = cert
s.fileMap[keyID] = fileName s.fileMap[certID] = fileName
name := string(cert.RawSubject) name := string(cert.Subject.CommonName)
s.nameMap[name] = append(s.nameMap[name], keyID) s.nameMap[name] = append(s.nameMap[name], certID)
return nil return nil
} }
// RemoveCert removes a certificate from a X509FileStore. // RemoveCert removes a certificate from a X509FileStore.
func (s X509FileStore) RemoveCert(cert *x509.Certificate) error { func (s *X509FileStore) RemoveCert(cert *x509.Certificate) error {
if cert == nil { if cert == nil {
return errors.New("removing nil Certificate from X509Store") return errors.New("removing nil Certificate from X509Store")
} }
keyID, err := fingerprintCert(cert) certID, err := fingerprintCert(cert)
if err != nil { if err != nil {
return err return err
} }
delete(s.fingerprintMap, keyID) delete(s.fingerprintMap, certID)
filename := s.fileMap[keyID] filename := s.fileMap[certID]
delete(s.fileMap, keyID) delete(s.fileMap, certID)
name := string(cert.RawSubject) name := string(cert.RawSubject)
@ -127,7 +127,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
fpList := s.nameMap[name] fpList := s.nameMap[name]
newfpList := fpList[:0] newfpList := fpList[:0]
for _, x := range fpList { for _, x := range fpList {
if x != keyID { if x != certID {
newfpList = append(newfpList, x) newfpList = append(newfpList, x)
} }
} }
@ -141,6 +141,20 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
return nil return nil
} }
// RemoveAll removes all the certificates from the store
func (s *X509FileStore) RemoveAll() error {
for _, filename := range s.fileMap {
if err := s.fileStore.Remove(filename); err != nil {
return err
}
}
s.fileMap = make(map[CertID]string)
s.fingerprintMap = make(map[CertID]*x509.Certificate)
s.nameMap = make(map[string][]CertID)
return nil
}
// AddCertFromPEM adds the first certificate that it finds in the byte[], returning // AddCertFromPEM adds the first certificate that it finds in the byte[], returning
// an error if no Certificates are found // an error if no Certificates are found
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error { func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
@ -152,7 +166,7 @@ func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
} }
// AddCertFromFile tries to adds a X509 certificate to the store given a filename // AddCertFromFile tries to adds a X509 certificate to the store given a filename
func (s X509FileStore) AddCertFromFile(filename string) error { func (s *X509FileStore) AddCertFromFile(filename string) error {
cert, err := LoadCertFromFile(filename) cert, err := LoadCertFromFile(filename)
if err != nil { if err != nil {
return err return err
@ -162,7 +176,7 @@ func (s X509FileStore) AddCertFromFile(filename string) error {
} }
// GetCertificates returns an array with all of the current X509 Certificates. // GetCertificates returns an array with all of the current X509 Certificates.
func (s X509FileStore) GetCertificates() []*x509.Certificate { func (s *X509FileStore) GetCertificates() []*x509.Certificate {
certs := make([]*x509.Certificate, len(s.fingerprintMap)) certs := make([]*x509.Certificate, len(s.fingerprintMap))
i := 0 i := 0
for _, v := range s.fingerprintMap { for _, v := range s.fingerprintMap {
@ -174,7 +188,7 @@ func (s X509FileStore) GetCertificates() []*x509.Certificate {
// GetCertificatePool returns an x509 CertPool loaded with all the certificates // GetCertificatePool returns an x509 CertPool loaded with all the certificates
// in the store. // in the store.
func (s X509FileStore) GetCertificatePool() *x509.CertPool { func (s *X509FileStore) GetCertificatePool() *x509.CertPool {
pool := x509.NewCertPool() pool := x509.NewCertPool()
for _, v := range s.fingerprintMap { for _, v := range s.fingerprintMap {
@ -183,25 +197,52 @@ func (s X509FileStore) GetCertificatePool() *x509.CertPool {
return pool return pool
} }
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error // GetCertificateByCertID returns the certificate that matches a certain certID
func (s X509FileStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) { func (s *X509FileStore) GetCertificateByCertID(certID string) (*x509.Certificate, error) {
return s.getCertificateByCertID(CertID(certID))
}
// getCertificateByCertID returns the certificate that matches a certain certID
func (s *X509FileStore) getCertificateByCertID(certID CertID) (*x509.Certificate, error) {
// If it does not look like a hex encoded sha256 hash, error // If it does not look like a hex encoded sha256 hash, error
if len(keyID) != 64 { if len(certID) != 64 {
return nil, errors.New("invalid Subject Key Identifier") return nil, errors.New("invalid Subject Key Identifier")
} }
// Check to see if this subject key identifier exists // Check to see if this subject key identifier exists
if cert, ok := s.fingerprintMap[CertID(keyID)]; ok { if cert, ok := s.fingerprintMap[CertID(certID)]; ok {
return cert, nil return cert, nil
} }
return nil, errors.New("certificate not found in Key Store") return nil, &ErrNoCertificatesFound{query: string(certID)}
}
// GetCertificatesByCN returns all the certificates that match a specific
// CommonName
func (s *X509FileStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, error) {
var certs []*x509.Certificate
if ids, ok := s.nameMap[cn]; ok {
for _, v := range ids {
cert, err := s.getCertificateByCertID(v)
if err != nil {
// This error should never happen. This would mean that we have
// an inconsistent X509FileStore
return nil, err
}
certs = append(certs, cert)
}
}
if len(certs) == 0 {
return nil, &ErrNoCertificatesFound{query: cn}
}
return certs, nil
} }
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore // GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
// as part of the roots list. This never allows the use of system roots, returning // as part of the roots list. This never allows the use of system roots, returning
// an error if there are no root CAs. // an error if there are no root CAs.
func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) { func (s *X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
// If we have no Certificates loaded return error (we don't want to rever to using // If we have no Certificates loaded return error (we don't want to rever to using
// system CAs). // system CAs).
if len(s.fingerprintMap) == 0 { if len(s.fingerprintMap) == 0 {
@ -217,10 +258,10 @@ func (s X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, err
} }
func fileName(cert *x509.Certificate) (string, CertID, error) { func fileName(cert *x509.Certificate) (string, CertID, error) {
keyID, err := fingerprintCert(cert) certID, err := fingerprintCert(cert)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return path.Join(cert.Subject.CommonName, string(keyID)), keyID, nil return path.Join(cert.Subject.CommonName, string(certID)), certID, nil
} }

View File

@ -135,6 +135,52 @@ func TestRemoveCertX509FileStore(t *testing.T) {
} }
} }
func TestRemoveAllX509FileStore(t *testing.T) {
tempDir, err := ioutil.TempDir("", "cert-test")
if err != nil {
t.Fatal(err)
}
// Add three certificates to store
store, _ := NewX509FileStore(tempDir)
certFiles := [3]string{"../fixtures/root-ca.crt",
"../fixtures/intermediate-ca.crt",
"../fixtures/secure.example.com.crt"}
for _, file := range certFiles {
b, err := ioutil.ReadFile(file)
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
var block *pem.Block
block, _ = pem.Decode(b)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("couldn't parse certificate: %v", err)
}
err = store.AddCert(cert)
if err != nil {
t.Fatalf("failed to load certificate: %v", err)
}
}
// Number of certificates should be 3 since we added the cert
numCerts := len(store.GetCertificates())
if numCerts != 3 {
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
}
// Remove the cert from the store
err = store.RemoveAll()
if err != nil {
t.Fatalf("failed to remove all certificates: %v", err)
}
// Number of certificates should be 0 since we added and removed the cert
numCerts = len(store.GetCertificates())
if numCerts != 0 {
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
}
}
func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) { func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
tempDir, err := ioutil.TempDir("", "cert-test") tempDir, err := ioutil.TempDir("", "cert-test")
if err != nil { if err != nil {
@ -146,7 +192,7 @@ func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
t.Fatalf("failed to load certificate from file: %v", err) t.Fatalf("failed to load certificate from file: %v", err)
} }
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a") _, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
if err == nil { if err == nil {
t.Fatalf("no error returned for inexistent certificate") t.Fatalf("no error returned for inexistent certificate")
} }
@ -181,7 +227,7 @@ func TestGetCertificateByKeyIDX509FileStore(t *testing.T) {
} }
// Tries to retrieve cert by Subject Key IDs // Tries to retrieve cert by Subject Key IDs
_, err = store.GetCertificateByKeyID(keyID) _, err = store.GetCertificateByCertID(keyID)
if err != nil { if err != nil {
t.Fatalf("expected certificate in store: %s", keyID) t.Fatalf("expected certificate in store: %s", keyID)
} }

View File

@ -3,6 +3,8 @@ package trustmanager
import ( import (
"crypto/x509" "crypto/x509"
"errors" "errors"
"github.com/Sirupsen/logrus"
) )
// X509MemStore implements X509Store as an in-memory object with no persistence // X509MemStore implements X509Store as an in-memory object with no persistence
@ -23,7 +25,7 @@ func NewX509MemStore() *X509MemStore {
} }
} }
// NewX509FilteredMemStore returns a new X509FileStore that validates certificates // NewX509FilteredMemStore returns a new X509Memstore that validates certificates
// that are added. // that are added.
func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore { func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore {
s := &X509MemStore{ s := &X509MemStore{
@ -37,45 +39,48 @@ func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStor
} }
// AddCert adds a certificate to the store // AddCert adds a certificate to the store
func (s X509MemStore) AddCert(cert *x509.Certificate) error { func (s *X509MemStore) AddCert(cert *x509.Certificate) error {
if cert == nil { if cert == nil {
return errors.New("adding nil Certificate to X509Store") return errors.New("adding nil Certificate to X509Store")
} }
if !s.validate.Validate(cert) { if !s.validate.Validate(cert) {
return errors.New("certificate failed validation") return &ErrCertValidation{}
} }
keyID, err := fingerprintCert(cert) certID, err := fingerprintCert(cert)
if err != nil { if err != nil {
return err return err
} }
s.fingerprintMap[keyID] = cert logrus.Debug("Adding cert with certID: ", certID)
// In this store we overwrite the certificate if it already exists
s.fingerprintMap[certID] = cert
name := string(cert.RawSubject) name := string(cert.RawSubject)
s.nameMap[name] = append(s.nameMap[name], keyID) s.nameMap[name] = append(s.nameMap[name], certID)
return nil return nil
} }
// RemoveCert removes a certificate from a X509MemStore. // RemoveCert removes a certificate from a X509MemStore.
func (s X509MemStore) RemoveCert(cert *x509.Certificate) error { func (s *X509MemStore) RemoveCert(cert *x509.Certificate) error {
if cert == nil { if cert == nil {
return errors.New("removing nil Certificate to X509Store") return errors.New("removing nil Certificate to X509Store")
} }
keyID, err := fingerprintCert(cert) certID, err := fingerprintCert(cert)
if err != nil { if err != nil {
return err return err
} }
delete(s.fingerprintMap, keyID) delete(s.fingerprintMap, certID)
name := string(cert.RawSubject) name := string(cert.RawSubject)
// Filter the fingerprint out of this name entry // Filter the fingerprint out of this name entry
fpList := s.nameMap[name] fpList := s.nameMap[name]
newfpList := fpList[:0] newfpList := fpList[:0]
for _, x := range fpList { for _, x := range fpList {
if x != keyID { if x != certID {
newfpList = append(newfpList, x) newfpList = append(newfpList, x)
} }
} }
@ -84,8 +89,20 @@ func (s X509MemStore) RemoveCert(cert *x509.Certificate) error {
return nil return nil
} }
// RemoveAll removes all the certificates from the store
func (s *X509MemStore) RemoveAll() error {
for _, cert := range s.fingerprintMap {
if err := s.RemoveCert(cert); err != nil {
return err
}
}
return nil
}
// AddCertFromPEM adds a certificate to the store from a PEM blob // AddCertFromPEM adds a certificate to the store from a PEM blob
func (s X509MemStore) AddCertFromPEM(pemBytes []byte) error { func (s *X509MemStore) AddCertFromPEM(pemBytes []byte) error {
cert, err := LoadCertFromPEM(pemBytes) cert, err := LoadCertFromPEM(pemBytes)
if err != nil { if err != nil {
return err return err
@ -94,7 +111,7 @@ func (s X509MemStore) AddCertFromPEM(pemBytes []byte) error {
} }
// AddCertFromFile tries to adds a X509 certificate to the store given a filename // AddCertFromFile tries to adds a X509 certificate to the store given a filename
func (s X509MemStore) AddCertFromFile(originFilname string) error { func (s *X509MemStore) AddCertFromFile(originFilname string) error {
cert, err := LoadCertFromFile(originFilname) cert, err := LoadCertFromFile(originFilname)
if err != nil { if err != nil {
return err return err
@ -104,7 +121,7 @@ func (s X509MemStore) AddCertFromFile(originFilname string) error {
} }
// GetCertificates returns an array with all of the current X509 Certificates. // GetCertificates returns an array with all of the current X509 Certificates.
func (s X509MemStore) GetCertificates() []*x509.Certificate { func (s *X509MemStore) GetCertificates() []*x509.Certificate {
certs := make([]*x509.Certificate, len(s.fingerprintMap)) certs := make([]*x509.Certificate, len(s.fingerprintMap))
i := 0 i := 0
for _, v := range s.fingerprintMap { for _, v := range s.fingerprintMap {
@ -116,7 +133,7 @@ func (s X509MemStore) GetCertificates() []*x509.Certificate {
// GetCertificatePool returns an x509 CertPool loaded with all the certificates // GetCertificatePool returns an x509 CertPool loaded with all the certificates
// in the store. // in the store.
func (s X509MemStore) GetCertificatePool() *x509.CertPool { func (s *X509MemStore) GetCertificatePool() *x509.CertPool {
pool := x509.NewCertPool() pool := x509.NewCertPool()
for _, v := range s.fingerprintMap { for _, v := range s.fingerprintMap {
@ -125,25 +142,52 @@ func (s X509MemStore) GetCertificatePool() *x509.CertPool {
return pool return pool
} }
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error // GetCertificateByCertID returns the certificate that matches a certain certID
func (s X509MemStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) { func (s *X509MemStore) GetCertificateByCertID(certID string) (*x509.Certificate, error) {
return s.getCertificateByCertID(CertID(certID))
}
// getCertificateByCertID returns the certificate that matches a certain certID or error
func (s *X509MemStore) getCertificateByCertID(certID CertID) (*x509.Certificate, error) {
// If it does not look like a hex encoded sha256 hash, error // If it does not look like a hex encoded sha256 hash, error
if len(keyID) != 64 { if len(certID) != 64 {
return nil, errors.New("invalid Subject Key Identifier") return nil, errors.New("invalid Subject Key Identifier")
} }
// Check to see if this subject key identifier exists // Check to see if this subject key identifier exists
if cert, ok := s.fingerprintMap[CertID(keyID)]; ok { if cert, ok := s.fingerprintMap[CertID(certID)]; ok {
return cert, nil return cert, nil
} }
return nil, errors.New("certificate not found in Key Store") return nil, &ErrNoCertificatesFound{query: string(certID)}
}
// GetCertificatesByCN returns all the certificates that match a specific
// CommonName
func (s *X509MemStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, error) {
var certs []*x509.Certificate
if ids, ok := s.nameMap[cn]; ok {
for _, v := range ids {
cert, err := s.getCertificateByCertID(v)
if err != nil {
// This error should never happen. This would mean that we have
// an inconsistent X509MemStore
return nil, err
}
certs = append(certs, cert)
}
}
if len(certs) == 0 {
return nil, &ErrNoCertificatesFound{query: cn}
}
return certs, nil
} }
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore // GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
// as part of the roots list. This never allows the use of system roots, returning // as part of the roots list. This never allows the use of system roots, returning
// an error if there are no root CAs. // an error if there are no root CAs.
func (s X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) { func (s *X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
// If we have no Certificates loaded return error (we don't want to rever to using // If we have no Certificates loaded return error (we don't want to rever to using
// system CAs). // system CAs).
if len(s.fingerprintMap) == 0 { if len(s.fingerprintMap) == 0 {

View File

@ -106,14 +106,55 @@ func TestRemoveCert(t *testing.T) {
} }
} }
func TestInexistentGetCertificateByKeyID(t *testing.T) { func TestRemoveAllX509MemStore(t *testing.T) {
// Add three certificates to store
store := NewX509MemStore()
certFiles := [3]string{"../fixtures/root-ca.crt",
"../fixtures/intermediate-ca.crt",
"../fixtures/secure.example.com.crt"}
for _, file := range certFiles {
b, err := ioutil.ReadFile(file)
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
var block *pem.Block
block, _ = pem.Decode(b)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("couldn't parse certificate: %v", err)
}
err = store.AddCert(cert)
if err != nil {
t.Fatalf("failed to load certificate: %v", err)
}
}
// Number of certificates should be 3 since we added the cert
numCerts := len(store.GetCertificates())
if numCerts != 3 {
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
}
// Remove the cert from the store
err := store.RemoveAll()
if err != nil {
t.Fatalf("failed to remove all certificates: %v", err)
}
// Number of certificates should be 0 since we added and removed the cert
numCerts = len(store.GetCertificates())
if numCerts != 0 {
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
}
}
func TestInexistentGetCertificateByCertID(t *testing.T) {
store := NewX509MemStore() store := NewX509MemStore()
err := store.AddCertFromFile("../fixtures/root-ca.crt") err := store.AddCertFromFile("../fixtures/root-ca.crt")
if err != nil { if err != nil {
t.Fatalf("failed to load certificate from file: %v", err) t.Fatalf("failed to load certificate from file: %v", err)
} }
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a") _, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
if err == nil { if err == nil {
t.Fatalf("no error returned for inexistent certificate") t.Fatalf("no error returned for inexistent certificate")
} }
@ -138,15 +179,15 @@ func TestGetCertificateByKeyID(t *testing.T) {
t.Fatalf("failed to load certificate from PEM: %v", err) t.Fatalf("failed to load certificate from PEM: %v", err)
} }
keyID, err := FingerprintCert(cert) certID, err := FingerprintCert(cert)
if err != nil { if err != nil {
t.Fatalf("failed to fingerprint the certificate: %v", err) t.Fatalf("failed to fingerprint the certificate: %v", err)
} }
// Tries to retrieve cert by Subject Key IDs // Tries to retrieve cert by Subject Key IDs
_, err = store.GetCertificateByKeyID(keyID) _, err = store.GetCertificateByCertID(certID)
if err != nil { if err != nil {
t.Fatalf("expected certificate in store: %s", keyID) t.Fatalf("expected certificate in store: %s", certID)
} }
} }

View File

@ -8,13 +8,47 @@ import (
const certExtension string = "crt" const certExtension string = "crt"
// ErrNoCertificatesFound is returned when no certificates are found for a
// GetCertificatesBy*
type ErrNoCertificatesFound struct {
query string
}
// ErrNoCertificatesFound is returned when no certificates are found for a
// GetCertificatesBy*
func (err ErrNoCertificatesFound) Error() string {
return fmt.Sprintf("error, no certificates found in the keystore match: %s", err.query)
}
// ErrCertValidation is returned when a certificate doesn't pass the store specific
// validations
type ErrCertValidation struct {
}
// ErrCertValidation is returned when a certificate doesn't pass the store specific
// validations
func (err ErrCertValidation) Error() string {
return fmt.Sprintf("store-specific certificate validations failed")
}
// ErrCertExists is returned when a Certificate already exists in the key store
type ErrCertExists struct {
}
// ErrCertExists is returned when a Certificate already exists in the key store
func (err ErrCertExists) Error() string {
return fmt.Sprintf("certificate already in the store")
}
// X509Store is the interface for all X509Stores // X509Store is the interface for all X509Stores
type X509Store interface { type X509Store interface {
AddCert(cert *x509.Certificate) error AddCert(cert *x509.Certificate) error
AddCertFromPEM(pemCerts []byte) error AddCertFromPEM(pemCerts []byte) error
AddCertFromFile(filename string) error AddCertFromFile(filename string) error
RemoveCert(cert *x509.Certificate) error RemoveCert(cert *x509.Certificate) error
GetCertificateByKeyID(keyID string) (*x509.Certificate, error) RemoveAll() error
GetCertificateByCertID(certID string) (*x509.Certificate, error)
GetCertificatesByCN(cn string) ([]*x509.Certificate, error)
GetCertificates() []*x509.Certificate GetCertificates() []*x509.Certificate
GetCertificatePool() *x509.CertPool GetCertificatePool() *x509.CertPool
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)

View File

@ -127,25 +127,66 @@ func loadCertsFromDir(s *X509FileStore) {
} }
} }
// LoadCertFromFile tries to adds a X509 certificate to the store given a filename // LoadCertFromFile loads the first certificate from the file provided. The
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
func LoadCertFromFile(filename string) (*x509.Certificate, error) { func LoadCertFromFile(filename string) (*x509.Certificate, error) {
// TODO(diogo): handle multiple certificates in one file. certs, err := LoadCertBundleFromFile(filename)
if err != nil {
return nil, err
}
return certs[0], nil
}
// LoadCertBundleFromFile loads certificates from the []byte provided. The
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
func LoadCertBundleFromFile(filename string) ([]*x509.Certificate, error) {
b, err := ioutil.ReadFile(filename) b, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return LoadCertBundleFromPEM(b)
}
// LoadCertBundleFromPEM loads certificates from the []byte provided. The
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
func LoadCertBundleFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
certificates := []*x509.Certificate{}
var block *pem.Block var block *pem.Block
block, b = pem.Decode(b) block, pemBytes = pem.Decode(pemBytes)
for ; block != nil; block, b = pem.Decode(b) { for ; block != nil; block, pemBytes = pem.Decode(pemBytes) {
if block.Type == "CERTIFICATE" { if block.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(block.Bytes) cert, err := x509.ParseCertificate(block.Bytes)
if err == nil { if err != nil {
return cert, nil return nil, err
} }
certificates = append(certificates, cert)
} else {
return nil, fmt.Errorf("invalid pem block type: %s", block.Type)
} }
} }
return nil, errors.New("could not load certificate from file") if len(certificates) == 0 {
return nil, fmt.Errorf("no valid certificates found")
}
return certificates, nil
}
// GetLeafCerts parses a list of x509 Certificates and returns all of them
// that aren't CA
func GetLeafCerts(certs []*x509.Certificate) []*x509.Certificate {
var leafCerts []*x509.Certificate
for _, cert := range certs {
if cert.IsCA {
continue
}
leafCerts = append(leafCerts, cert)
}
return leafCerts
} }
// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It // ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It