diff --git a/client/client_root_validation_test.go b/client/client_root_validation_test.go deleted file mode 100644 index 1b58240aa3..0000000000 --- a/client/client_root_validation_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package client - -import ( - "os" - "testing" - - "github.com/Sirupsen/logrus" - "github.com/docker/notary/tuf/data" - "github.com/stretchr/testify/require" -) - -// TestValidateRoot through the process of initializing a repository and makes -// sure the repository looks correct on disk. -// We test this with both an RSA and ECDSA root key -func TestValidateRoot(t *testing.T) { - logrus.SetLevel(logrus.ErrorLevel) - validateRootSuccessfully(t, data.ECDSAKey) - if !testing.Short() { - validateRootSuccessfully(t, data.RSAKey) - } -} - -func validateRootSuccessfully(t *testing.T, rootType string) { - gun := "docker.com/notary" - - ts, mux, keys := simpleTestServer(t) - defer ts.Close() - - repo, _ := initializeRepo(t, rootType, gun, ts.URL, false) - defer os.RemoveAll(repo.baseDir) - - // tests need to manually boostrap timestamp as client doesn't generate it - err := repo.tufRepo.InitTimestamp() - require.NoError(t, err, "error creating repository: %s", err) - - fakeServerData(t, repo, mux, keys) - - // - // Test TOFUS logic. - // - - _, err = repo.ListTargets(data.CanonicalTargetsRole) - require.NoError(t, err) -} diff --git a/client/client_test.go b/client/client_test.go index 40626a8612..022507b3a3 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2789,22 +2789,8 @@ func TestRotateRootKey(t *testing.T) { require.Error(t, err) addTarget(t, authorRepo, "current", "../fixtures/intermediate-ca.crt") - // NotaryRepository.Update's handling of certificate rotation is weird: - // - // On every run, NotaryRepository.bootstrapClient rotates the trusted certificates - // based on CACHED root data. - // Then the client calls Repo.Update, which fetches a new timestmap, - // notices an updated root.json, validates it and stores it into the cache. - // - // So, the locally trusted certificates are rotated only on the SECOND call of - // NotaryRepository.Update after the rotation is pushed to the server. - // - // This would be nice to fix eventually (breaking down the NotaryRepository.Update - // / Repo.Update separation which causes this), but for now, just ensure that the - // second update does result in updated certificates. - // Publish the target, which does an update and pulls down the latest metadata, and - // should update the cert store now + // should update the trusted root logRepoTrustRoot(t, "pre-publish", authorRepo) err = authorRepo.Publish() require.NoError(t, err) diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go deleted file mode 100644 index ebca3b3839..0000000000 --- a/trustmanager/x509filestore.go +++ /dev/null @@ -1,272 +0,0 @@ -package trustmanager - -import ( - "crypto/x509" - "errors" - "os" - "path" - - "github.com/Sirupsen/logrus" -) - -// X509FileStore implements X509Store that persists on disk -type X509FileStore struct { - validate Validator - fileMap map[CertID]string - fingerprintMap map[CertID]*x509.Certificate - nameMap map[string][]CertID - fileStore Storage -} - -// NewX509FileStore returns a new X509FileStore. -func NewX509FileStore(directory string) (*X509FileStore, error) { - validate := ValidatorFunc(func(cert *x509.Certificate) bool { return true }) - return newX509FileStore(directory, validate) -} - -// NewX509FilteredFileStore returns a new X509FileStore that validates certificates -// that are added. -func NewX509FilteredFileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) { - return newX509FileStore(directory, validate) -} - -func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) { - fileStore, err := NewSimpleFileStore(directory, certExtension) - if err != nil { - return nil, err - } - - s := &X509FileStore{ - validate: ValidatorFunc(validate), - fileMap: make(map[CertID]string), - fingerprintMap: make(map[CertID]*x509.Certificate), - nameMap: make(map[string][]CertID), - fileStore: fileStore, - } - - err = loadCertsFromDir(s) - if err != nil { - return nil, err - } - - return s, nil -} - -// AddCert creates a filename for a given cert and adds a certificate with that name -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 &ErrCertValidation{} - } - // Attempt to write the certificate to the file - if err := s.addNamedCert(cert); err != nil { - return err - } - - return nil -} - -// addNamedCert allows adding a certificate while controlling 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, certID, err := fileName(cert) - if err != nil { - return err - } - - 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 - certBytes := CertToPEM(cert) - - // Save the file to disk if not already there. - if _, err = s.fileStore.Get(fileName); os.IsNotExist(err) { - if err := s.fileStore.Add(fileName, certBytes); err != nil { - return err - } - } else if err != nil { - return err - } - - // We wrote the certificate succcessfully, add it to our in-memory storage - s.fingerprintMap[certID] = cert - s.fileMap[certID] = fileName - - 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 { - if cert == nil { - return errors.New("removing nil Certificate from X509Store") - } - - certID, err := fingerprintCert(cert) - if err != nil { - return err - } - delete(s.fingerprintMap, certID) - filename := s.fileMap[certID] - delete(s.fileMap, certID) - - name := string(cert.Subject.CommonName) - - // Filter the fingerprint out of this name entry - fpList := s.nameMap[name] - newfpList := fpList[:0] - for _, x := range fpList { - if x != certID { - newfpList = append(newfpList, x) - } - } - - s.nameMap[name] = newfpList - - if err := s.fileStore.Remove(filename); err != nil { - return err - } - - 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 { - cert, err := LoadCertFromPEM(pemBytes) - if err != nil { - return err - } - return s.AddCert(cert) -} - -// AddCertFromFile tries to adds a X509 certificate to the store given a filename -func (s *X509FileStore) AddCertFromFile(filename string) error { - cert, err := LoadCertFromFile(filename) - if err != nil { - return err - } - - return s.AddCert(cert) -} - -// GetCertificates returns an array with all of the current X509 Certificates. -func (s *X509FileStore) GetCertificates() []*x509.Certificate { - certs := make([]*x509.Certificate, len(s.fingerprintMap)) - i := 0 - for _, v := range s.fingerprintMap { - certs[i] = v - i++ - } - return certs -} - -// GetCertificatePool returns an x509 CertPool loaded with all the certificates -// in the store. -func (s *X509FileStore) GetCertificatePool() *x509.CertPool { - pool := x509.NewCertPool() - - for _, v := range s.fingerprintMap { - pool.AddCert(v) - } - return pool -} - -// 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(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(certID)]; ok { - return cert, nil - - } - 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, &ErrBadCertificateStore{} - } - 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) { - // If we have no Certificates loaded return error (we don't want to revert to using - // system CAs). - if len(s.fingerprintMap) == 0 { - return x509.VerifyOptions{}, errors.New("no root CAs available") - } - - opts := x509.VerifyOptions{ - DNSName: dnsName, - Roots: s.GetCertificatePool(), - } - - return opts, nil -} - -// Empty returns true if there are no certificates in the X509FileStore, false -// otherwise. -func (s *X509FileStore) Empty() bool { - return len(s.fingerprintMap) == 0 -} - -func fileName(cert *x509.Certificate) (string, CertID, error) { - certID, err := fingerprintCert(cert) - if err != nil { - return "", "", err - } - - return path.Join(cert.Subject.CommonName, string(certID)), certID, nil -} diff --git a/trustmanager/x509filestore_test.go b/trustmanager/x509filestore_test.go deleted file mode 100644 index fe41ec57ed..0000000000 --- a/trustmanager/x509filestore_test.go +++ /dev/null @@ -1,428 +0,0 @@ -package trustmanager - -import ( - "crypto/x509" - "encoding/pem" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewX509FileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tempDir) - store, err := NewX509FileStore(tempDir) - if err != nil { - t.Fatalf("failed to create a new X509FileStore: %v", store) - } -} - -// NewX509FileStore loads any existing certs from the directory, and does -// not overwrite any of the. -func TestNewX509FileStoreLoadsExistingCerts(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - certBytes, err := ioutil.ReadFile("../fixtures/root-ca.crt") - require.NoError(t, err) - out, err := os.Create(filepath.Join(tempDir, "root-ca.crt")) - require.NoError(t, err) - - // to distinguish it from the canonical format - distinguishingBytes := []byte{'\n', '\n', '\n', '\n', '\n', '\n'} - nBytes, err := out.Write(distinguishingBytes) - require.NoError(t, err) - require.Len(t, distinguishingBytes, nBytes) - - nBytes, err = out.Write(certBytes) - require.NoError(t, err) - require.Len(t, certBytes, nBytes) - - err = out.Close() - require.NoError(t, err) - - store, err := NewX509FileStore(tempDir) - require.NoError(t, err) - - expectedCert, err := LoadCertFromFile("../fixtures/root-ca.crt") - require.NoError(t, err) - require.Equal(t, []*x509.Certificate{expectedCert}, store.GetCertificates()) - - outBytes, err := ioutil.ReadFile(filepath.Join(tempDir, "root-ca.crt")) - require.NoError(t, err) - require.Equal(t, distinguishingBytes, outBytes[:6], "original file overwritten") - require.Equal(t, certBytes, outBytes[6:], "original file overwritten") -} - -func TestAddCertX509FileStore(t *testing.T) { - // Read certificate from file - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("couldn't load fixture: %v", err) - } - // Decode PEM block - var block *pem.Block - block, _ = pem.Decode(b) - - // Load X509 Certificate - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - t.Fatalf("couldn't parse certificate: %v", err) - } - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - // Create a Store and add the certificate to it - store, _ := NewX509FileStore(tempDir) - err = store.AddCert(cert) - if err != nil { - t.Fatalf("failed to load certificate: %v", err) - } - // Retrieve all the certificates - certs := store.GetCertificates() - // Check to see if certificate is present and total number of certs is correct - numCerts := len(certs) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } - if certs[0] != cert { - t.Fatalf("expected certificates to be the same") - } -} - -func TestAddCertFromFileX509FileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - require.NoError(t, err, "failed to create temporary directory") - - store, err := NewX509FileStore(tempDir) - require.NoError(t, err, "failed to load x509 filestore") - - err = store.AddCertFromFile("../fixtures/root-ca.crt") - require.NoError(t, err, "failed to add certificate from file") - require.Len(t, store.GetCertificates(), 1) - - // Now load the x509 filestore with the same path and expect the same result - newStore, err := NewX509FileStore(tempDir) - require.NoError(t, err, "failed to load x509 filestore") - require.Len(t, newStore.GetCertificates(), 1) - - // Test that adding the same certificate returns an error - err = newStore.AddCert(newStore.GetCertificates()[0]) - require.Error(t, err, "expected error when adding certificate twice") - require.Equal(t, err, &ErrCertExists{}) -} - -// TestNewX509FileStoreEmpty verifies the behavior of the Empty function -func TestNewX509FileStoreEmpty(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - require.NoError(t, err) - defer os.RemoveAll(tempDir) - - store, err := NewX509FileStore(tempDir) - require.NoError(t, err) - require.True(t, store.Empty()) - - err = store.AddCertFromFile("../fixtures/root-ca.crt") - require.NoError(t, err) - require.False(t, store.Empty()) -} - -func TestAddCertFromPEMX509FileStore(t *testing.T) { - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("couldn't load fixture: %v", err) - } - - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - store, _ := NewX509FileStore(tempDir) - err = store.AddCertFromPEM(b) - if err != nil { - t.Fatalf("failed to load certificate from PEM: %v", err) - } - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } -} - -func TestRemoveCertX509FileStore(t *testing.T) { - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - 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) - } - - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - store, _ := NewX509FileStore(tempDir) - err = store.AddCert(cert) - if err != nil { - t.Fatalf("failed to load certificate: %v", err) - } - - // Number of certificates should be 1 since we added the cert - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } - - // Remove the cert from the store - err = store.RemoveCert(cert) - if err != nil { - t.Fatalf("failed to remove certificate: %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 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 { - t.Fatal(err) - } - store, _ := NewX509FileStore(tempDir) - err = store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - _, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a") - if err == nil { - t.Fatalf("no error returned for inexistent certificate") - } -} - -func TestGetCertificateByKeyIDX509FileStore(t *testing.T) { - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - 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) - } - - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - store, _ := NewX509FileStore(tempDir) - err = store.AddCert(cert) - if err != nil { - t.Fatalf("failed to load certificate from PEM: %v", err) - } - - keyID, 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.GetCertificateByCertID(keyID) - if err != nil { - t.Fatalf("expected certificate in store: %s", keyID) - } -} - -func TestGetVerifyOpsErrorsWithoutCertsX509FileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - // Create empty Store - store, _ := NewX509FileStore(tempDir) - - // Try to get VerifyOptions without certs added - _, err = store.GetVerifyOptions("example.com") - if err == nil { - t.Fatalf("expecting an error when getting empty VerifyOptions") - } -} - -func TestVerifyLeafCertFromIntermediateX509FileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - // Create a store and add a root - store, _ := NewX509FileStore(tempDir) - err = store.AddCertFromFile("../fixtures/intermediate-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - // Get the VerifyOptions from our Store - opts, err := store.GetVerifyOptions("secure.example.com") - - // Get leaf certificate - b, err := ioutil.ReadFile("../fixtures/secure.example.com.crt") - 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) - } - - // Try to find a valid chain for cert - _, err = cert.Verify(opts) - if err != nil { - t.Fatalf("couldn't find a valid chain for this certificate: %v", err) - } -} - -func TestVerifyIntermediateFromRootX509FileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - // Create a store and add a root - store, _ := NewX509FileStore(tempDir) - err = store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - // Get the VerifyOptions from our Store - opts, err := store.GetVerifyOptions("Notary Testing CA") - - // Get leaf certificate - b, err := ioutil.ReadFile("../fixtures/intermediate-ca.crt") - 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) - } - - // Try to find a valid chain for cert - _, err = cert.Verify(opts) - if err != nil { - t.Fatalf("couldn't find a valid chain for this certificate: %v", err) - } -} - -func TestNewX509FilteredFileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - store, err := NewX509FilteredFileStore(tempDir, func(cert *x509.Certificate) bool { - return cert.IsCA - }) - if err != nil { - t.Fatalf("failed to create new X509FilteredFileStore: %v", err) - } - // AddCert should succeed because this is a CA being added - err = store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } - - // AddCert should fail because this is a leaf cert being added - err = store.AddCertFromFile("../fixtures/secure.example.com.crt") - if err == nil { - t.Fatalf("was expecting non-CA certificate to be rejected") - } -} - -func TestGetCertificatePoolX509FileStore(t *testing.T) { - tempDir, err := ioutil.TempDir("", "cert-test") - if err != nil { - t.Fatal(err) - } - // Create a store and add a root - store, _ := NewX509FileStore(tempDir) - err = store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - pool := store.GetCertificatePool() - numCerts := len(pool.Subjects()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in pool: %d", numCerts) - } -} diff --git a/trustmanager/x509memstore.go b/trustmanager/x509memstore.go deleted file mode 100644 index 0d321938d5..0000000000 --- a/trustmanager/x509memstore.go +++ /dev/null @@ -1,203 +0,0 @@ -package trustmanager - -import ( - "crypto/x509" - "errors" - - "github.com/Sirupsen/logrus" -) - -// X509MemStore implements X509Store as an in-memory object with no persistence -type X509MemStore struct { - validate Validator - fingerprintMap map[CertID]*x509.Certificate - nameMap map[string][]CertID -} - -// NewX509MemStore returns a new X509MemStore. -func NewX509MemStore() *X509MemStore { - validate := ValidatorFunc(func(cert *x509.Certificate) bool { return true }) - - return &X509MemStore{ - validate: validate, - fingerprintMap: make(map[CertID]*x509.Certificate), - nameMap: make(map[string][]CertID), - } -} - -// NewX509FilteredMemStore returns a new X509Memstore that validates certificates -// that are added. -func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore { - s := &X509MemStore{ - - validate: ValidatorFunc(validate), - fingerprintMap: make(map[CertID]*x509.Certificate), - nameMap: make(map[string][]CertID), - } - - return s -} - -// AddCert adds a certificate to the store -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 &ErrCertValidation{} - } - - certID, err := fingerprintCert(cert) - if err != nil { - return err - } - - 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], certID) - - return nil -} - -// RemoveCert removes a certificate from a X509MemStore. -func (s *X509MemStore) RemoveCert(cert *x509.Certificate) error { - if cert == nil { - return errors.New("removing nil Certificate to X509Store") - } - - certID, err := fingerprintCert(cert) - if err != nil { - return err - } - 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 != certID { - newfpList = append(newfpList, x) - } - } - - s.nameMap[name] = newfpList - 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 { - cert, err := LoadCertFromPEM(pemBytes) - if err != nil { - return err - } - return s.AddCert(cert) -} - -// AddCertFromFile tries to adds a X509 certificate to the store given a filename -func (s *X509MemStore) AddCertFromFile(originFilname string) error { - cert, err := LoadCertFromFile(originFilname) - if err != nil { - return err - } - - return s.AddCert(cert) -} - -// GetCertificates returns an array with all of the current X509 Certificates. -func (s *X509MemStore) GetCertificates() []*x509.Certificate { - certs := make([]*x509.Certificate, len(s.fingerprintMap)) - i := 0 - for _, v := range s.fingerprintMap { - certs[i] = v - i++ - } - return certs -} - -// GetCertificatePool returns an x509 CertPool loaded with all the certificates -// in the store. -func (s *X509MemStore) GetCertificatePool() *x509.CertPool { - pool := x509.NewCertPool() - - for _, v := range s.fingerprintMap { - pool.AddCert(v) - } - return pool -} - -// 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(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(certID)]; ok { - return cert, nil - - } - 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) { - // If we have no Certificates loaded return error (we don't want to revert to using - // system CAs). - if len(s.fingerprintMap) == 0 { - return x509.VerifyOptions{}, errors.New("no root CAs available") - } - - opts := x509.VerifyOptions{ - DNSName: dnsName, - Roots: s.GetCertificatePool(), - } - - return opts, nil -} diff --git a/trustmanager/x509memstore_test.go b/trustmanager/x509memstore_test.go deleted file mode 100644 index e2470bb2c3..0000000000 --- a/trustmanager/x509memstore_test.go +++ /dev/null @@ -1,302 +0,0 @@ -package trustmanager - -import ( - "crypto/x509" - "encoding/pem" - "io/ioutil" - "testing" -) - -func TestAddCert(t *testing.T) { - // Read certificate from file - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("couldn't load fixture: %v", err) - } - // Decode PEM block - var block *pem.Block - block, _ = pem.Decode(b) - - // Load X509 Certificate - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - t.Fatalf("couldn't parse certificate: %v", err) - } - // Create a Store and add the certificate to it - store := NewX509MemStore() - err = store.AddCert(cert) - if err != nil { - t.Fatalf("failed to load certificate: %v", err) - } - // Retrieve all the certificates - certs := store.GetCertificates() - // Check to see if certificate is present and total number of certs is correct - numCerts := len(certs) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } - if certs[0] != cert { - t.Fatalf("expected certificates to be the same") - } -} - -func TestAddCertFromFile(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) - } - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } -} - -func TestAddCertFromPEM(t *testing.T) { - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("couldn't load fixture: %v", err) - } - - store := NewX509MemStore() - err = store.AddCertFromPEM(b) - if err != nil { - t.Fatalf("failed to load certificate from PEM: %v", err) - } - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } -} - -func TestRemoveCert(t *testing.T) { - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - 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) - } - - store := NewX509MemStore() - err = store.AddCert(cert) - if err != nil { - t.Fatalf("failed to load certificate: %v", err) - } - - // Number of certificates should be 1 since we added the cert - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } - - // Remove the cert from the store - err = store.RemoveCert(cert) - if err != nil { - t.Fatalf("failed to remove certificate: %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 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.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a") - if err == nil { - t.Fatalf("no error returned for inexistent certificate") - } -} - -func TestGetCertificateByKeyID(t *testing.T) { - b, err := ioutil.ReadFile("../fixtures/root-ca.crt") - 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) - } - - store := NewX509MemStore() - err = store.AddCert(cert) - if err != nil { - t.Fatalf("failed to load certificate from PEM: %v", err) - } - - 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.GetCertificateByCertID(certID) - if err != nil { - t.Fatalf("expected certificate in store: %s", certID) - } -} - -func TestGetVerifyOpsErrorsWithoutCerts(t *testing.T) { - // Create empty Store - store := NewX509MemStore() - - // Try to get VerifyOptions without certs added - _, err := store.GetVerifyOptions("example.com") - if err == nil { - t.Fatalf("expecting an error when getting empty VerifyOptions") - } -} - -func TestVerifyLeafCertFromIntermediate(t *testing.T) { - // Create a store and add a root - store := NewX509MemStore() - err := store.AddCertFromFile("../fixtures/intermediate-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - // Get the VerifyOptions from our Store - opts, err := store.GetVerifyOptions("secure.example.com") - - // Get leaf certificate - b, err := ioutil.ReadFile("../fixtures/secure.example.com.crt") - 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) - } - - // Try to find a valid chain for cert - _, err = cert.Verify(opts) - if err != nil { - t.Fatalf("couldn't find a valid chain for this certificate: %v", err) - } -} - -func TestVerifyIntermediateFromRoot(t *testing.T) { - // Create a store and add a root - store := NewX509MemStore() - err := store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - // Get the VerifyOptions from our Store - opts, err := store.GetVerifyOptions("Notary Testing CA") - - // Get leaf certificate - b, err := ioutil.ReadFile("../fixtures/intermediate-ca.crt") - 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) - } - - // Try to find a valid chain for cert - _, err = cert.Verify(opts) - if err != nil { - t.Fatalf("couldn't find a valid chain for this certificate: %v", err) - } -} - -func TestNewX509FilteredMemStore(t *testing.T) { - store := NewX509FilteredMemStore(func(cert *x509.Certificate) bool { - return cert.IsCA - }) - - // AddCert should succeed because this is a CA being added - err := store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - numCerts := len(store.GetCertificates()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in store: %d", numCerts) - } - - // AddCert should fail because this is a leaf cert being added - err = store.AddCertFromFile("../fixtures/secure.example.com.crt") - if err == nil { - t.Fatalf("was expecting non-CA certificate to be rejected") - } -} - -func TestGetCertificatePool(t *testing.T) { - // Create a store and add a root - store := NewX509MemStore() - err := store.AddCertFromFile("../fixtures/root-ca.crt") - if err != nil { - t.Fatalf("failed to load certificate from file: %v", err) - } - - pool := store.GetCertificatePool() - numCerts := len(pool.Subjects()) - if numCerts != 1 { - t.Fatalf("unexpected number of certificates in pool: %d", numCerts) - } -} diff --git a/trustmanager/x509store.go b/trustmanager/x509store.go deleted file mode 100644 index 3736ff632c..0000000000 --- a/trustmanager/x509store.go +++ /dev/null @@ -1,144 +0,0 @@ -package trustmanager - -import ( - "crypto/x509" - "errors" - "fmt" -) - -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") -} - -// ErrBadCertificateStore is returned when there is an internal inconsistency -// in our x509 store -type ErrBadCertificateStore struct { -} - -// ErrBadCertificateStore is returned when there is an internal inconsistency -// in our x509 store -func (err ErrBadCertificateStore) Error() string { - return fmt.Sprintf("inconsistent certificate 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 - 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) -} - -// CertID represent the ID used to identify certificates -type CertID string - -// Validator is a convenience type to create validating function that filters -// certificates that get added to the store -type Validator interface { - Validate(cert *x509.Certificate) bool -} - -// ValidatorFunc is a convenience type to create functions that implement -// the Validator interface -type ValidatorFunc func(cert *x509.Certificate) bool - -// Validate implements the Validator interface to allow for any func() bool method -// to be passed as a Validator -func (vf ValidatorFunc) Validate(cert *x509.Certificate) bool { - return vf(cert) -} - -// Verify operates on an X509Store and validates the existence of a chain of trust -// between a leafCertificate and a CA present inside of the X509 Store. -// It requires at least two certificates in certList, a leaf Certificate and an -// intermediate CA certificate. -func Verify(s X509Store, dnsName string, certList []*x509.Certificate) error { - // If we have no Certificates loaded return error (we don't want to revert to using - // system CAs). - if len(s.GetCertificates()) == 0 { - return errors.New("no root CAs available") - } - - // At a minimum we should be provided a leaf cert and an intermediate. - if len(certList) < 2 { - return errors.New("certificate and at least one intermediate needed") - } - - // Get the VerifyOptions from the keystore for a base dnsName - opts, err := s.GetVerifyOptions(dnsName) - if err != nil { - return err - } - - // Create a Certificate Pool for our intermediate certificates - intPool := x509.NewCertPool() - var leafCert *x509.Certificate - - // Iterate through all the certificates - for _, c := range certList { - // If the cert is a CA, we add it to the intermediates pool. If not, we call - // it the leaf cert - if c.IsCA { - intPool.AddCert(c) - continue - } - // Certificate is not a CA, it must be our leaf certificate. - // If we already found one, bail with error - if leafCert != nil { - return errors.New("more than one leaf certificate found") - } - leafCert = c - } - - // We exited the loop with no leaf certificates - if leafCert == nil { - return errors.New("no leaf certificates found") - } - - // We have one leaf certificate and at least one intermediate. Lets add this - // Cert Pool as the Intermediates list on our VerifyOptions - opts.Intermediates = intPool - - // Finally, let's call Verify on our leafCert with our fully configured options - chains, err := leafCert.Verify(opts) - if len(chains) == 0 || err != nil { - return fmt.Errorf("certificate verification failed: %v", err) - } - return nil -} diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index 8a154ab82c..29a1835165 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -14,8 +14,6 @@ import ( "io" "io/ioutil" "math/big" - "net/http" - "net/url" "time" "github.com/Sirupsen/logrus" @@ -24,39 +22,8 @@ import ( "github.com/docker/notary/tuf/data" ) -// GetCertFromURL tries to get a X509 certificate given a HTTPS URL -func GetCertFromURL(urlStr string) (*x509.Certificate, error) { - url, err := url.Parse(urlStr) - if err != nil { - return nil, err - } - - // Check if we are adding via HTTPS - if url.Scheme != "https" { - return nil, errors.New("only HTTPS URLs allowed") - } - - // Download the certificate and write to directory - resp, err := http.Get(url.String()) - if err != nil { - return nil, err - } - - // Copy the content to certBytes - defer resp.Body.Close() - certBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - // Try to extract the first valid PEM certificate from the bytes - cert, err := LoadCertFromPEM(certBytes) - if err != nil { - return nil, err - } - - return cert, nil -} +// CertID is a simple string type for identifying x509 certificates +type CertID string // CertToPEM is a utility function returns a PEM encoded x509 Certificate func CertToPEM(cert *x509.Certificate) []byte { @@ -127,33 +94,6 @@ func fingerprintCert(cert *x509.Certificate) (CertID, error) { return CertID(tufKey.ID()), nil } -// loadCertsFromDir receives a store AddCertFromFile for each certificate found -func loadCertsFromDir(s *X509FileStore) error { - for _, f := range s.fileStore.ListFiles() { - // ListFiles returns relative paths - data, err := s.fileStore.Get(f) - if err != nil { - // the filestore told us it had a file that it then couldn't serve. - // this is a serious problem so error immediately - return err - } - err = s.AddCertFromPEM(data) - if err != nil { - if _, ok := err.(*ErrCertValidation); ok { - logrus.Debugf("ignoring certificate, did not pass validation: %s", f) - continue - } - if _, ok := err.(*ErrCertExists); ok { - logrus.Debugf("ignoring certificate, already exists in the store: %s", f) - continue - } - - return err - } - } - return nil -} - // 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" @@ -610,14 +550,3 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) { return key.ID(), nil } - -// FilterCertsExpiredSha1 can be used as the filter function to cert store -// initializers to filter out all expired or SHA-1 certificate that we -// shouldn't load. -func FilterCertsExpiredSha1(cert *x509.Certificate) bool { - return !cert.IsCA && - time.Now().Before(cert.NotAfter) && - cert.SignatureAlgorithm != x509.SHA1WithRSA && - cert.SignatureAlgorithm != x509.DSAWithSHA1 && - cert.SignatureAlgorithm != x509.ECDSAWithSHA1 -}