Goodbye Certstore

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-04-22 11:11:13 -07:00
parent 01bbd532c6
commit ca9fc99ba5
8 changed files with 3 additions and 1481 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}