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]
// Try to retrieve the ID from the CA store.
cert, err := caStore.GetCertificateByKeyID(gunOrID)
cert, err := caStore.GetCertificateByCertID(gunOrID)
if err == nil {
fmt.Printf("Removing: ")
printCert(cert)
@ -78,7 +78,7 @@ func keysRemove(cmd *cobra.Command, args []string) {
}
// Try to retrieve the ID from the Certificate store.
cert, err = certificateStore.GetCertificateByKeyID(gunOrID)
cert, err = certificateStore.GetCertificateByCertID(gunOrID)
if err == nil {
fmt.Printf("Removing: ")
printCert(cert)
@ -216,12 +216,12 @@ func keysGenerate(cmd *cobra.Command, args []string) {
func printCert(cert *x509.Certificate) {
timeDifference := cert.NotAfter.Sub(time.Now())
keyID, err := trustmanager.FingerprintCert(cert)
certID, err := trustmanager.FingerprintCert(cert)
if err != nil {
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) {

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 (
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"path/filepath"
@ -36,6 +34,15 @@ const (
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
// if it fails to create the KeyFileStores or load certificates
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
trustedCAStore.
When this is being used with a notary repository, the dnsName parameter should
be the GUN associated with the repository.
Currently this method operates on a Trust On First Use (TOFU) model: if we
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:
"roles" : {
"root" : {
"threshold" : 1,
"keyids" : [
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38"
]
},
...
}
Note that since we only allow trust data to be downloaded over an HTTPS channel
we are using the current web-of-trust to validate the first download of the certificate
adding an extra layer of security over the normal (SSH style) trust model.
We shall call this: TOFUS.
Example TUF Content for root key:
"e6da5c303d572712a086e669ecd4df7b785adfc844e0c9a7b1f21a7dfc477a38" : {
"keytype" : "RSA",
"keyval" : {
"private" : "",
"public" : "Base64-encoded, PEM encoded x509 Certificate"
}
}
ValidateRoot also supports root key rotation, trusting a new certificate that has
been included in the roots.json, and removing trust in the old one.
*/
func (km *KeyStoreManager) ValidateRoot(root *data.Signed, dnsName string) error {
rootSigned := &data.Root{}
err := json.Unmarshal(root.Signed, rootSigned)
logrus.Debugf("entered ValidateRoot with dns: %s", dnsName)
rootSigned, err := data.RootFromSigned(root)
if err != nil {
return err
}
certs := make(map[string]data.PublicKey)
for _, keyID := range rootSigned.Roles["root"].KeyIDs {
// TODO(dlaw): currently assuming only one cert contained in
// public key entry. Need to fix when we want to pass in chains.
k, _ := pem.Decode([]byte(rootSigned.Keys[keyID].Public()))
decodedCerts, err := x509.ParseCertificates(k.Bytes)
// validKeys will store all the keys that were considered valid either by
// direct certificate match, or CA chain path
validKeys := make(map[string]data.PublicKey)
// allCerts will keep a list of all leafCerts that were found, and is used
// 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 {
logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
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)
if err != nil {
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
continue
}
// Check to see if there is an exact match of this certificate.
// Checking the CommonName is not required since ID is calculated over
// Cert.Raw. It's included to prevent breaking logic with changes of how the
// ID gets computed.
_, err = km.trustedCertificateStore.GetCertificateByKeyID(leafID)
if err == nil && leafCert.Subject.CommonName == dnsName {
certs[keyID] = rootSigned.Keys[keyID]
// Validate that this leaf certificate has a CN that matches the exact gun
if leafCert.Subject.CommonName != dnsName {
logrus.Debugf("error leaf certificate CN: %s doesn't match the given dns name: %s", leafCert.Subject.CommonName, dnsName)
continue
}
// Check to see if this leafCertificate has a chain to one of the Root CAs
// of our CA Store.
certList := []*x509.Certificate{leafCert}
err = trustmanager.Verify(km.trustedCAStore, dnsName, certList)
// Add all the valid leafs to the certificates map so we can refer to them later
allCerts[leafID] = leafCert
// 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 {
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 {
return errors.New("could not validate the path to a trusted root")
if len(validKeys) < 1 {
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
}
// 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")
}
}
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
func (s X509FileStore) AddCert(cert *x509.Certificate) error {
func (s *X509FileStore) AddCert(cert *x509.Certificate) error {
if cert == nil {
return errors.New("adding nil Certificate to X509Store")
}
// Check if this certificate meets our validation criteria
if !s.validate.Validate(cert) {
return errors.New("certificate validation failed")
return &ErrCertValidation{}
}
// Attempt to write the certificate to the file
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
// stored under. If the file does not exist on disk, saves it.
func (s X509FileStore) addNamedCert(cert *x509.Certificate) error {
fileName, keyID, err := fileName(cert)
func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error {
fileName, certID, err := fileName(cert)
if err != nil {
return err
}
logrus.Debug("Adding cert with keyID: ", keyID)
// Validate if we already loaded this certificate before
if _, ok := s.fingerprintMap[keyID]; ok {
return errors.New("certificate already in the store")
logrus.Debug("Adding cert with certID: ", certID)
// Validate if we already added this certificate before
if _, ok := s.fingerprintMap[certID]; ok {
return &ErrCertExists{}
}
// 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
s.fingerprintMap[keyID] = cert
s.fileMap[keyID] = fileName
s.fingerprintMap[certID] = cert
s.fileMap[certID] = fileName
name := string(cert.RawSubject)
s.nameMap[name] = append(s.nameMap[name], keyID)
name := string(cert.Subject.CommonName)
s.nameMap[name] = append(s.nameMap[name], certID)
return nil
}
// 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 {
return errors.New("removing nil Certificate from X509Store")
}
keyID, err := fingerprintCert(cert)
certID, err := fingerprintCert(cert)
if err != nil {
return err
}
delete(s.fingerprintMap, keyID)
filename := s.fileMap[keyID]
delete(s.fileMap, keyID)
delete(s.fingerprintMap, certID)
filename := s.fileMap[certID]
delete(s.fileMap, certID)
name := string(cert.RawSubject)
@ -127,7 +127,7 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
fpList := s.nameMap[name]
newfpList := fpList[:0]
for _, x := range fpList {
if x != keyID {
if x != certID {
newfpList = append(newfpList, x)
}
}
@ -141,6 +141,20 @@ func (s X509FileStore) RemoveCert(cert *x509.Certificate) error {
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
// an error if no Certificates are found
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
func (s X509FileStore) AddCertFromFile(filename string) error {
func (s *X509FileStore) AddCertFromFile(filename string) error {
cert, err := LoadCertFromFile(filename)
if err != nil {
return err
@ -162,7 +176,7 @@ func (s X509FileStore) AddCertFromFile(filename string) error {
}
// 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))
i := 0
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
// in the store.
func (s X509FileStore) GetCertificatePool() *x509.CertPool {
func (s *X509FileStore) GetCertificatePool() *x509.CertPool {
pool := x509.NewCertPool()
for _, v := range s.fingerprintMap {
@ -183,25 +197,52 @@ func (s X509FileStore) GetCertificatePool() *x509.CertPool {
return pool
}
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error
func (s X509FileStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) {
// GetCertificateByCertID returns the certificate that matches a certain certID
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 len(keyID) != 64 {
if len(certID) != 64 {
return nil, errors.New("invalid Subject Key Identifier")
}
// 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 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
// as part of the roots list. This never allows the use of system roots, returning
// 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
// system CAs).
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) {
keyID, err := fingerprintCert(cert)
certID, err := fingerprintCert(cert)
if err != nil {
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) {
tempDir, err := ioutil.TempDir("", "cert-test")
if err != nil {
@ -146,7 +192,7 @@ func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
t.Fatalf("failed to load certificate from file: %v", err)
}
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
_, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
if err == nil {
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
_, err = store.GetCertificateByKeyID(keyID)
_, err = store.GetCertificateByCertID(keyID)
if err != nil {
t.Fatalf("expected certificate in store: %s", keyID)
}

View File

@ -3,6 +3,8 @@ package trustmanager
import (
"crypto/x509"
"errors"
"github.com/Sirupsen/logrus"
)
// 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.
func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore {
s := &X509MemStore{
@ -37,45 +39,48 @@ func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStor
}
// 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 {
return errors.New("adding nil Certificate to X509Store")
}
if !s.validate.Validate(cert) {
return errors.New("certificate failed validation")
return &ErrCertValidation{}
}
keyID, err := fingerprintCert(cert)
certID, err := fingerprintCert(cert)
if err != nil {
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)
s.nameMap[name] = append(s.nameMap[name], keyID)
s.nameMap[name] = append(s.nameMap[name], certID)
return nil
}
// 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 {
return errors.New("removing nil Certificate to X509Store")
}
keyID, err := fingerprintCert(cert)
certID, err := fingerprintCert(cert)
if err != nil {
return err
}
delete(s.fingerprintMap, keyID)
delete(s.fingerprintMap, certID)
name := string(cert.RawSubject)
// Filter the fingerprint out of this name entry
fpList := s.nameMap[name]
newfpList := fpList[:0]
for _, x := range fpList {
if x != keyID {
if x != certID {
newfpList = append(newfpList, x)
}
}
@ -84,8 +89,20 @@ func (s X509MemStore) RemoveCert(cert *x509.Certificate) error {
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
func (s X509MemStore) AddCertFromPEM(pemBytes []byte) error {
func (s *X509MemStore) AddCertFromPEM(pemBytes []byte) error {
cert, err := LoadCertFromPEM(pemBytes)
if err != nil {
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
func (s X509MemStore) AddCertFromFile(originFilname string) error {
func (s *X509MemStore) AddCertFromFile(originFilname string) error {
cert, err := LoadCertFromFile(originFilname)
if err != nil {
return err
@ -104,7 +121,7 @@ func (s X509MemStore) AddCertFromFile(originFilname string) error {
}
// 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))
i := 0
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
// in the store.
func (s X509MemStore) GetCertificatePool() *x509.CertPool {
func (s *X509MemStore) GetCertificatePool() *x509.CertPool {
pool := x509.NewCertPool()
for _, v := range s.fingerprintMap {
@ -125,25 +142,52 @@ func (s X509MemStore) GetCertificatePool() *x509.CertPool {
return pool
}
// GetCertificateByKeyID returns the certificate that matches a certain keyID or error
func (s X509MemStore) GetCertificateByKeyID(keyID string) (*x509.Certificate, error) {
// GetCertificateByCertID returns the certificate that matches a certain certID
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 len(keyID) != 64 {
if len(certID) != 64 {
return nil, errors.New("invalid Subject Key Identifier")
}
// 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 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
// as part of the roots list. This never allows the use of system roots, returning
// 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
// system CAs).
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()
err := store.AddCertFromFile("../fixtures/root-ca.crt")
if err != nil {
t.Fatalf("failed to load certificate from file: %v", err)
}
_, err = store.GetCertificateByKeyID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
_, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
if err == nil {
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)
}
keyID, err := FingerprintCert(cert)
certID, err := FingerprintCert(cert)
if err != nil {
t.Fatalf("failed to fingerprint the certificate: %v", err)
}
// Tries to retrieve cert by Subject Key IDs
_, err = store.GetCertificateByKeyID(keyID)
_, err = store.GetCertificateByCertID(certID)
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"
// 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
type X509Store interface {
AddCert(cert *x509.Certificate) error
AddCertFromPEM(pemCerts []byte) error
AddCertFromFile(filename string) 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
GetCertificatePool() *x509.CertPool
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) {
// 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)
if err != nil {
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
block, b = pem.Decode(b)
for ; block != nil; block, b = pem.Decode(b) {
block, pemBytes = pem.Decode(pemBytes)
for ; block != nil; block, pemBytes = pem.Decode(pemBytes) {
if block.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
return cert, nil
if err != 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