mirror of https://github.com/docker/docs.git
Goodbye Certstore
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
parent
01bbd532c6
commit
ca9fc99ba5
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue