Merge pull request #693 from docker/remove-certstore

Remove certstore
This commit is contained in:
Ying Li 2016-04-27 10:58:37 -07:00
commit 2874955337
24 changed files with 355 additions and 2706 deletions

View File

@ -29,10 +29,6 @@ func requireValidFixture(t *testing.T, notaryRepo *NotaryRepository) {
for _, targetObj := range notaryRepo.tufRepo.Targets { for _, targetObj := range notaryRepo.tufRepo.Targets {
require.True(t, targetObj.Signed.Expires.After(tenYearsInFuture)) require.True(t, targetObj.Signed.Expires.After(tenYearsInFuture))
} }
for _, cert := range notaryRepo.CertStore.GetCertificates() {
require.True(t, cert.NotAfter.After(tenYearsInFuture))
}
} }
// recursively copies the contents of one directory into another - ignores // recursively copies the contents of one directory into another - ignores

View File

@ -2,7 +2,6 @@ package client
import ( import (
"bytes" "bytes"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -87,7 +86,6 @@ type NotaryRepository struct {
CryptoService signed.CryptoService CryptoService signed.CryptoService
tufRepo *tuf.Repo tufRepo *tuf.Repo
roundTrip http.RoundTripper roundTrip http.RoundTripper
CertStore trustmanager.X509Store
trustPinning trustpinning.TrustPinConfig trustPinning trustpinning.TrustPinConfig
} }
@ -97,15 +95,6 @@ type NotaryRepository struct {
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper, func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) { keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) {
certPath := filepath.Join(baseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
certPath,
trustmanager.FilterCertsExpiredSha1,
)
if err != nil {
return nil, err
}
cryptoService := cryptoservice.NewCryptoService(keyStores...) cryptoService := cryptoservice.NewCryptoService(keyStores...)
nRepo := &NotaryRepository{ nRepo := &NotaryRepository{
@ -115,7 +104,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)), tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
CryptoService: cryptoService, CryptoService: cryptoService,
roundTrip: rt, roundTrip: rt,
CertStore: certStore,
trustPinning: trustPin, trustPinning: trustPin,
} }
@ -162,22 +150,22 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
} }
func rootCertKey(gun string, privKey data.PrivateKey) (*x509.Certificate, data.PublicKey, error) { func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) {
// Hard-coded policy: the generated certificate expires in 10 years. // Hard-coded policy: the generated certificate expires in 10 years.
startTime := time.Now() startTime := time.Now()
cert, err := cryptoservice.GenerateCertificate( cert, err := cryptoservice.GenerateCertificate(
privKey, gun, startTime, startTime.Add(notary.Year*10)) privKey, gun, startTime, startTime.Add(notary.Year*10))
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
x509PublicKey := trustmanager.CertToKey(cert) x509PublicKey := trustmanager.CertToKey(cert)
if x509PublicKey == nil { if x509PublicKey == nil {
return nil, nil, fmt.Errorf( return nil, fmt.Errorf(
"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm) "cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
} }
return cert, x509PublicKey, nil return x509PublicKey, nil
} }
// Initialize creates a new repository by using rootKey as the root Key for the // Initialize creates a new repository by using rootKey as the root Key for the
@ -218,11 +206,10 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
} }
} }
rootCert, rootKey, err := rootCertKey(r.gun, privKey) rootKey, err := rootCertKey(r.gun, privKey)
if err != nil { if err != nil {
return err return err
} }
r.CertStore.AddCert(rootCert)
var ( var (
rootRole = data.NewBaseRole( rootRole = data.NewBaseRole(
@ -790,9 +777,6 @@ func (r *NotaryRepository) Update(forWrite bool) error {
// Partially populates r.tufRepo with this root metadata (only; use // Partially populates r.tufRepo with this root metadata (only; use
// tufclient.Client.Update to load the rest). // tufclient.Client.Update to load the rest).
// //
// As another side effect, r.CertManager's list of trusted certificates
// is updated with data from the loaded root.json.
//
// Fails if the remote server is reachable and does not know the repo // Fails if the remote server is reachable and does not know the repo
// (i.e. before the first r.Publish()), in which case the error is // (i.e. before the first r.Publish()), in which case the error is
// store.ErrMetaNotFound, or if the root metadata (from whichever source is used) // store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
@ -812,7 +796,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1) rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
if cachedRootErr == nil { if cachedRootErr == nil {
signedRoot, cachedRootErr = r.validateRoot(rootJSON) signedRoot, cachedRootErr = r.validateRoot(rootJSON, false)
} }
remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
@ -833,7 +817,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
if cachedRootErr != nil { if cachedRootErr != nil {
// we always want to use the downloaded root if there was a cache // we always want to use the downloaded root if there was a cache
// error. // error.
signedRoot, err = r.validateRoot(tmpJSON) signedRoot, err = r.validateRoot(tmpJSON, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -868,7 +852,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
// signatures of the root based on known keys, not expiry or other metadata. // signatures of the root based on known keys, not expiry or other metadata.
// This is so that an out of date root can be loaded to be used in a rotation // This is so that an out of date root can be loaded to be used in a rotation
// should the TUF update process detect a problem. // should the TUF update process detect a problem.
func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, error) { func (r *NotaryRepository) validateRoot(rootJSON []byte, fromRemote bool) (*data.SignedRoot, error) {
// can't just unmarshal into SignedRoot because validate root // can't just unmarshal into SignedRoot because validate root
// needs the root.Signed field to still be []byte for signature // needs the root.Signed field to still be []byte for signature
// validation // validation
@ -878,12 +862,26 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
return nil, err return nil, err
} }
err = trustpinning.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning) // If we're downloading a root from a remote source, attempt to load a local root
if err != nil { // to ensure that we consider old roots when validating this new one
return nil, err var prevRoot *data.SignedRoot
if fromRemote {
prevRootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
// A previous root exists, so we attempt to use it
// If for some reason we can't extract it (ex: it's corrupted), we should error client-side to be conservative
if err == nil {
prevSignedRoot := &data.Signed{}
err = json.Unmarshal(prevRootJSON, prevSignedRoot)
if err != nil {
return nil, &trustpinning.ErrValidationFail{fmt.Sprintf("unable to unmarshal previously trusted root from disk: %v", err)}
}
prevRoot, err = data.RootFromSigned(prevSignedRoot)
if err != nil {
return nil, &trustpinning.ErrValidationFail{fmt.Sprintf("error loading previously trusted root into valid role format: %v", err)}
}
}
} }
return trustpinning.ValidateRoot(prevRoot, root, r.gun, r.trustPinning)
return data.RootFromSigned(root)
} }
// RotateKey removes all existing keys associated with the role, and either // RotateKey removes all existing keys associated with the role, and either
@ -929,7 +927,7 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
if err != nil { if err != nil {
return err return err
} }
_, pubKey, err = rootCertKey(r.gun, privKey) pubKey, err = rootCertKey(r.gun, privKey)
if err != nil { if err != nil {
return err return err
} }
@ -964,26 +962,12 @@ func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, act
return cl.Add(c) return cl.Add(c)
} }
// DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side // DeleteTrustData removes the trust data stored for this repo in the TUF cache on the client side
func (r *NotaryRepository) DeleteTrustData() error { func (r *NotaryRepository) DeleteTrustData() error {
// Clear TUF files and cache // Clear TUF files and cache
if err := r.fileStore.RemoveAll(); err != nil { if err := r.fileStore.RemoveAll(); err != nil {
return fmt.Errorf("error clearing TUF repo data: %v", err) return fmt.Errorf("error clearing TUF repo data: %v", err)
} }
r.tufRepo = tuf.NewRepo(nil) r.tufRepo = tuf.NewRepo(nil)
// Clear certificates
certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
if err != nil {
// If there were no certificates to delete, we're done
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); ok {
return nil
}
return fmt.Errorf("error retrieving certificates for %s: %v", r.gun, err)
}
for _, cert := range certificates {
if err := r.CertStore.RemoveCert(cert); err != nil {
return fmt.Errorf("error removing certificate: %v: %v", cert, err)
}
}
return nil return nil
} }

View File

@ -1,78 +0,0 @@
package client
import (
"os"
"testing"
"github.com/Sirupsen/logrus"
"github.com/docker/notary/trustpinning"
"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)
// Initialize is supposed to have created new certificate for this repository
// Lets check for it and store it for later use
allCerts := repo.CertStore.GetCertificates()
require.Len(t, allCerts, 1)
fakeServerData(t, repo, mux, keys)
//
// Test TOFUS logic. We remove all certs and expect a new one to be added after ListTargets
//
err = repo.CertStore.RemoveAll()
require.NoError(t, err)
require.Len(t, repo.CertStore.GetCertificates(), 0)
// This list targets is expected to succeed and the certificate store to have the new certificate
_, err = repo.ListTargets(data.CanonicalTargetsRole)
require.NoError(t, err)
require.Len(t, repo.CertStore.GetCertificates(), 1)
//
// Test certificate mismatch logic. We remove all certs, add a different cert to the
// same CN, and expect ValidateRoot to fail
//
// First, remove all certs
err = repo.CertStore.RemoveAll()
require.NoError(t, err)
require.Len(t, repo.CertStore.GetCertificates(), 0)
// Add a previously generated certificate with CN=docker.com/notary
err = repo.CertStore.AddCertFromFile(
"../fixtures/self-signed_docker.com-notary.crt")
require.NoError(t, err)
// This list targets is expected to fail, since there already exists a certificate
// in the store for the dnsName docker.com/notary, so TOFUS doesn't apply
_, err = repo.ListTargets(data.CanonicalTargetsRole)
require.Error(t, err, "An error was expected")
require.Equal(t, err, &trustpinning.ErrValidationFail{
Reason: "failed to validate data with current trusted certificates",
})
}

View File

@ -425,26 +425,6 @@ func requireRepoHasExpectedKeys(t *testing.T, repo *NotaryRepository,
"there should be no timestamp key because the server manages it") "there should be no timestamp key because the server manages it")
} }
// This creates a new certificate store in the repo's base directory and
// makes sure the repo has the right certificates
func requireRepoHasExpectedCerts(t *testing.T, repo *NotaryRepository) {
// The repo should have a certificate store and have created certs using
// it, so create a new store, and check that the certs do exist and
// are valid
trustPath := filepath.Join(repo.baseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
certificates := certStore.GetCertificates()
require.Len(t, certificates, 1, "unexpected number of trusted certificates")
certID, err := trustmanager.FingerprintCert(certificates[0])
require.NoError(t, err, "unable to fingerprint the trusted certificate")
require.NotEqual(t, certID, "")
}
// Sanity check the TUF metadata files. Verify that it exists for a particular // Sanity check the TUF metadata files. Verify that it exists for a particular
// role, the JSON is well-formed, and the signatures exist. // role, the JSON is well-formed, and the signatures exist.
// For the root.json file, also check that the root, snapshot, and // For the root.json file, also check that the root, snapshot, and
@ -515,7 +495,6 @@ func testInitRepoMetadata(t *testing.T, rootType string, serverManagesSnapshot b
defer os.RemoveAll(repo.baseDir) defer os.RemoveAll(repo.baseDir)
requireRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot) requireRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot)
requireRepoHasExpectedCerts(t, repo)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole,
@ -1932,17 +1911,15 @@ func TestPublishRootCorrupt(t *testing.T) {
defer os.RemoveAll(repo.baseDir) defer os.RemoveAll(repo.baseDir)
testPublishBadMetadata(t, data.CanonicalRootRole, repo, false, false) testPublishBadMetadata(t, data.CanonicalRootRole, repo, false, false)
// publish first - publish should still succeed if root corrupt since the // publish first - publish should still fail if the local root is corrupt since
// remote root is signed with the same key. // we can't determine whether remote root is signed with the same key.
repo, _ = initializeRepo(t, data.ECDSAKey, "docker.com/notary2", ts.URL, false) repo, _ = initializeRepo(t, data.ECDSAKey, "docker.com/notary2", ts.URL, false)
defer os.RemoveAll(repo.baseDir) defer os.RemoveAll(repo.baseDir)
testPublishBadMetadata(t, data.CanonicalRootRole, repo, true, true) testPublishBadMetadata(t, data.CanonicalRootRole, repo, true, false)
} }
// When publishing snapshot, root, or target, if the repo hasn't been published // When publishing snapshot, root, or target, if the repo hasn't been published
// before, if the metadata is corrupt, it can't be published. If it has been // before, if the metadata is corrupt, it can't be published.
// published already, then the corrupt metadata can just be re-downloaded, so
// publishing is successful.
func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepository, func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepository,
publishFirst, succeeds bool) { publishFirst, succeeds bool) {
@ -1959,7 +1936,11 @@ func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepositor
require.NoError(t, err) require.NoError(t, err)
} else { } else {
require.Error(t, err) require.Error(t, err)
require.IsType(t, &regJson.SyntaxError{}, err) if roleName == data.CanonicalRootRole && publishFirst {
require.IsType(t, &trustpinning.ErrValidationFail{}, err)
} else {
require.IsType(t, &regJson.SyntaxError{}, err)
}
} }
// make an unreadable file by creating a directory instead of a file // make an unreadable file by creating a directory instead of a file
@ -1971,7 +1952,7 @@ func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepositor
defer os.RemoveAll(path) defer os.RemoveAll(path)
err = repo.Publish() err = repo.Publish()
if succeeds { if succeeds || publishFirst {
require.NoError(t, err) require.NoError(t, err)
} else { } else {
require.Error(t, err) require.Error(t, err)
@ -2757,14 +2738,6 @@ func logRepoTrustRoot(t *testing.T, prefix string, repo *NotaryRepository) {
for _, k := range root.Signed.Roles[data.CanonicalRootRole].KeyIDs { for _, k := range root.Signed.Roles[data.CanonicalRootRole].KeyIDs {
logrus.Debugf("\t%s", k) logrus.Debugf("\t%s", k)
} }
logrus.Debugf("All trusted certs:")
certs, err := repo.CertStore.GetCertificatesByCN(repo.gun)
require.NoError(t, err)
for _, cert := range certs {
id, err := trustmanager.FingerprintCert(cert)
require.NoError(t, err)
logrus.Debugf("\t%s", id)
}
} }
// ID of the (only) certificate trusted by the root role metadata // ID of the (only) certificate trusted by the root role metadata
@ -2774,15 +2747,6 @@ func rootRoleCertID(t *testing.T, repo *NotaryRepository) string {
return rootKeys[0] return rootKeys[0]
} }
func verifyOnlyTrustedCertificate(t *testing.T, repo *NotaryRepository, certID string) {
certs, err := repo.CertStore.GetCertificatesByCN(repo.gun)
require.NoError(t, err)
require.Len(t, certs, 1)
id, err := trustmanager.FingerprintCert(certs[0])
require.NoError(t, err)
require.Equal(t, certID, id)
}
func TestRotateRootKey(t *testing.T) { func TestRotateRootKey(t *testing.T) {
ts := fullTestServer(t) ts := fullTestServer(t)
defer ts.Close() defer ts.Close()
@ -2825,27 +2789,12 @@ func TestRotateRootKey(t *testing.T) {
require.Error(t, err) require.Error(t, err)
addTarget(t, authorRepo, "current", "../fixtures/intermediate-ca.crt") 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 // 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) logRepoTrustRoot(t, "pre-publish", authorRepo)
err = authorRepo.Publish() err = authorRepo.Publish()
require.NoError(t, err) require.NoError(t, err)
logRepoTrustRoot(t, "post-publish", authorRepo) logRepoTrustRoot(t, "post-publish", authorRepo)
verifyOnlyTrustedCertificate(t, authorRepo, newRootCertID)
// Verify the user can use the rotated repo, and see the added target. // Verify the user can use the rotated repo, and see the added target.
_, err = userRepo.GetTargetByName("current") _, err = userRepo.GetTargetByName("current")
@ -2859,7 +2808,6 @@ func TestRotateRootKey(t *testing.T) {
_, err = freshUserRepo.GetTargetByName("current") _, err = freshUserRepo.GetTargetByName("current")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, newRootCertID, rootRoleCertID(t, freshUserRepo)) require.Equal(t, newRootCertID, rootRoleCertID(t, freshUserRepo))
verifyOnlyTrustedCertificate(t, freshUserRepo, newRootCertID)
logRepoTrustRoot(t, "fresh client", freshUserRepo) logRepoTrustRoot(t, "fresh client", freshUserRepo)
// Verify that the user initialized with the original certificate eventually // Verify that the user initialized with the original certificate eventually
@ -3307,7 +3255,6 @@ func TestDeleteRepo(t *testing.T) {
// Assert initialization was successful before we delete // Assert initialization was successful before we delete
requireRepoHasExpectedKeys(t, repo, rootKeyID, true) requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
requireRepoHasExpectedCerts(t, repo)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
@ -3322,12 +3269,6 @@ func TestDeleteRepo(t *testing.T) {
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false) requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false) requireRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
// Assert no certs for this repo exist locally
_, err = repo.CertStore.GetCertificatesByCN(gun)
require.Error(t, err)
require.IsType(t, &trustmanager.ErrNoCertificatesFound{}, err)
require.NotNil(t, err)
// Assert keys for this repo exist locally // Assert keys for this repo exist locally
requireRepoHasExpectedKeys(t, repo, rootKeyID, true) requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
} }
@ -3352,7 +3293,6 @@ func TestDeleteRepoBadFilestore(t *testing.T) {
// Assert initialization was successful before we delete // Assert initialization was successful before we delete
requireRepoHasExpectedKeys(t, repo, rootKeyID, true) requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
requireRepoHasExpectedCerts(t, repo)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true) requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
@ -3365,50 +3305,6 @@ func TestDeleteRepoBadFilestore(t *testing.T) {
require.Error(t, err) require.Error(t, err)
} }
// TestDeleteRepoNoCerts tests that local repo data is deleted successfully without an error even when we do not have certificates
func TestDeleteRepoNoCerts(t *testing.T) {
gun := "docker.com/notary"
ts, _, _ := simpleTestServer(t)
defer ts.Close()
repo, rootKeyID := initializeRepo(t, data.ECDSAKey, gun, ts.URL, false)
defer os.RemoveAll(repo.baseDir)
// Assert initialization was successful before we delete
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
requireRepoHasExpectedCerts(t, repo)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
// Delete the certificate store contents and require it has been fully deleted
repo.CertStore.RemoveAll()
_, err := repo.CertStore.GetCertificatesByCN(gun)
require.Error(t, err)
require.IsType(t, &trustmanager.ErrNoCertificatesFound{}, err)
require.NotNil(t, err)
// Delete all client trust data for repo
err = repo.DeleteTrustData()
require.NoError(t, err)
// Assert no metadata for this repo exists locally
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, false)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, false)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
// Assert no certs for this repo exist locally
_, err = repo.CertStore.GetCertificatesByCN(gun)
require.Error(t, err)
require.IsType(t, &trustmanager.ErrNoCertificatesFound{}, err)
require.NotNil(t, err)
// Assert keys for this repo exist locally
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
}
// Test that we get a correct list of roles with keys and signatures // Test that we get a correct list of roles with keys and signatures
func TestListRoles(t *testing.T) { func TestListRoles(t *testing.T) {
ts := fullTestServer(t) ts := fullTestServer(t)

View File

@ -208,8 +208,9 @@ var waysToMessUpLocalMetadata = []swizzleExpectations{
// actively sabotage and break their own local repo (particularly the root.json) // actively sabotage and break their own local repo (particularly the root.json)
} }
// If a repo has corrupt metadata (in that the hash doesn't match the snapshot) or // If a repo has missing metadata, an update will replace all of it
// missing metadata, an update will replace all of it // If a repo has corrupt metadata for root, the update will fail
// For other roles, corrupt metadata will be replaced
func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) { func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping test in short mode") t.Skip("skipping test in short mode")
@ -230,18 +231,29 @@ func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
repoSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs) repoSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs)
repoSwizzler.MetadataCache = repo.fileStore repoSwizzler.MetadataCache = repo.fileStore
origMeta := testutils.CopyRepoMetadata(serverMeta)
for _, role := range repoSwizzler.Roles { for _, role := range repoSwizzler.Roles {
for _, expt := range waysToMessUpLocalMetadata { for _, expt := range waysToMessUpLocalMetadata {
text, messItUp := expt.desc, expt.swizzle text, messItUp := expt.desc, expt.swizzle
for _, forWrite := range []bool{true, false} { for _, forWrite := range []bool{true, false} {
require.NoError(t, messItUp(repoSwizzler, role), "could not fuzz %s (%s)", role, text) require.NoError(t, messItUp(repoSwizzler, role), "could not fuzz %s (%s)", role, text)
err := repo.Update(forWrite) err := repo.Update(forWrite)
require.NoError(t, err) // if this is a root role, we should error if it's corrupted data
for r, expected := range serverMeta { if role == data.CanonicalRootRole && expt.desc == "invalid JSON" {
actual, err := repo.fileStore.GetMeta(r, -1) require.Error(t, err)
require.NoError(t, err, "problem getting repo metadata for %s", role) // revert our original metadata
require.True(t, bytes.Equal(expected, actual), for role := range origMeta {
"%s for %s: expected to recover after update", text, role) require.NoError(t, repo.fileStore.SetMeta(role, origMeta[role]))
}
} else {
require.NoError(t, err)
for r, expected := range serverMeta {
actual, err := repo.fileStore.GetMeta(r, -1)
require.NoError(t, err, "problem getting repo metadata for %s", role)
require.True(t, bytes.Equal(expected, actual),
"%s for %s: expected to recover after update", text, role)
}
} }
} }
} }
@ -291,8 +303,12 @@ func TestUpdateFailsIfServerRootKeyChangedWithoutMultiSign(t *testing.T) {
if _, ok := err.(store.ErrMetaNotFound); ok { // one of the ways to mess up is to delete metadata if _, ok := err.(store.ErrMetaNotFound); ok { // one of the ways to mess up is to delete metadata
err = repo.Update(forWrite) err = repo.Update(forWrite)
require.Error(t, err) // the new server has a different root key, update fails // the new server has a different root key, but we don't have any way of pinning against a previous root
require.NoError(t, err)
// revert our original metadata
for role := range origMeta {
require.NoError(t, repo.fileStore.SetMeta(role, origMeta[role]))
}
} else { } else {
require.NoError(t, err) require.NoError(t, err)
@ -312,7 +328,7 @@ func TestUpdateFailsIfServerRootKeyChangedWithoutMultiSign(t *testing.T) {
expected = messedUpMeta expected = messedUpMeta
} }
require.True(t, bytes.Equal(expected, actual), require.True(t, bytes.Equal(expected, actual),
"%s for %s: expected to not have updated", text, role) "%s for %s: expected to not have updated -- swizzle method %s", text, role, expt.desc)
} }
} }
@ -770,15 +786,15 @@ func testUpdateRemoteFileChecksumWrong(t *testing.T, opts updateOpts, errExpecte
// this does not include delete, which is tested separately so we can try to get // this does not include delete, which is tested separately so we can try to get
// 404s and 503s // 404s and 503s
var waysToMessUpServer = []swizzleExpectations{ var waysToMessUpServer = []swizzleExpectations{
{desc: "invalid JSON", expectErrs: []interface{}{&json.SyntaxError{}}, {desc: "invalid JSON", expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.SyntaxError{}},
swizzle: (*testutils.MetadataSwizzler).SetInvalidJSON}, swizzle: (*testutils.MetadataSwizzler).SetInvalidJSON},
{desc: "an invalid Signed", expectErrs: []interface{}{&json.UnmarshalTypeError{}}, {desc: "an invalid Signed", expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.UnmarshalTypeError{}},
swizzle: (*testutils.MetadataSwizzler).SetInvalidSigned}, swizzle: (*testutils.MetadataSwizzler).SetInvalidSigned},
{desc: "an invalid SignedMeta", {desc: "an invalid SignedMeta",
// it depends which field gets unmarshalled first // it depends which field gets unmarshalled first
expectErrs: []interface{}{&json.UnmarshalTypeError{}, &time.ParseError{}}, expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.UnmarshalTypeError{}, &time.ParseError{}},
swizzle: (*testutils.MetadataSwizzler).SetInvalidSignedMeta}, swizzle: (*testutils.MetadataSwizzler).SetInvalidSignedMeta},
// for the errors below, when we bootstrap root, we get cert.ErrValidationFail failures // for the errors below, when we bootstrap root, we get cert.ErrValidationFail failures
@ -789,11 +805,11 @@ var waysToMessUpServer = []swizzleExpectations{
swizzle: (*testutils.MetadataSwizzler).SetInvalidMetadataType}, swizzle: (*testutils.MetadataSwizzler).SetInvalidMetadataType},
{desc: "invalid signatures", expectErrs: []interface{}{ {desc: "invalid signatures", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}}, &trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
swizzle: (*testutils.MetadataSwizzler).InvalidateMetadataSignatures}, swizzle: (*testutils.MetadataSwizzler).InvalidateMetadataSignatures},
{desc: "meta signed by wrong key", expectErrs: []interface{}{ {desc: "meta signed by wrong key", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}}, &trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
swizzle: (*testutils.MetadataSwizzler).SignMetadataWithInvalidKey}, swizzle: (*testutils.MetadataSwizzler).SignMetadataWithInvalidKey},
{desc: "expired metadata", expectErrs: []interface{}{ {desc: "expired metadata", expectErrs: []interface{}{
@ -1197,12 +1213,6 @@ func TestUpdateLocalAndRemoteRootCorrupt(t *testing.T) {
} }
for _, localExpt := range waysToMessUpLocalMetadata { for _, localExpt := range waysToMessUpLocalMetadata {
for _, serverExpt := range waysToMessUpServer { for _, serverExpt := range waysToMessUpServer {
if localExpt.desc == "expired metadata" && serverExpt.desc == "lower metadata version" {
// TODO: bug right now where if the local metadata is invalid, we just download a
// new version - we verify the signatures and everything, but don't check the version
// against the previous if we can
continue
}
testUpdateLocalAndRemoteRootCorrupt(t, true, localExpt, serverExpt) testUpdateLocalAndRemoteRootCorrupt(t, true, localExpt, serverExpt)
testUpdateLocalAndRemoteRootCorrupt(t, false, localExpt, serverExpt) testUpdateLocalAndRemoteRootCorrupt(t, false, localExpt, serverExpt)
} }
@ -1364,7 +1374,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
keyIDs := make([]string, len(threeKeys)) keyIDs := make([]string, len(threeKeys))
for i := 0; i < len(threeKeys); i++ { for i := 0; i < len(threeKeys); i++ {
threeKeys[i], err = testutils.CreateKey( threeKeys[i], err = testutils.CreateKey(
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole) serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole, data.ECDSAKey)
require.NoError(t, err) require.NoError(t, err)
keyIDs[i] = threeKeys[i].ID() keyIDs[i] = threeKeys[i].ID()
signedRoot.Signed.Keys[keyIDs[i]] = threeKeys[i] signedRoot.Signed.Keys[keyIDs[i]] = threeKeys[i]
@ -1382,7 +1392,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
// --- threshold back to 1 // --- threshold back to 1
replacementKey, err := testutils.CreateKey( replacementKey, err := testutils.CreateKey(
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole) serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole, data.ECDSAKey)
require.NoError(t, err) require.NoError(t, err)
signedRoot.Signed.Version++ signedRoot.Signed.Version++
signedRoot.Signed.Keys[replacementKey.ID()] = replacementKey signedRoot.Signed.Keys[replacementKey.ID()] = replacementKey
@ -1406,7 +1416,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
// --- latest root role) // --- latest root role)
signedRoot.Signed.Version++ signedRoot.Signed.Version++
snapKey, err := testutils.CreateKey( snapKey, err := testutils.CreateKey(
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalSnapshotRole) serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalSnapshotRole, data.ECDSAKey)
require.NoError(t, err) require.NoError(t, err)
signedRoot.Signed.Keys[snapKey.ID()] = snapKey signedRoot.Signed.Keys[snapKey.ID()] = snapKey
signedRoot.Signed.Roles[data.CanonicalSnapshotRole].KeyIDs = []string{snapKey.ID()} signedRoot.Signed.Roles[data.CanonicalSnapshotRole].KeyIDs = []string{snapKey.ID()}

View File

@ -1,194 +0,0 @@
package main
import (
"crypto/x509"
"fmt"
"os"
"path/filepath"
"github.com/docker/notary"
notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cmdCertTemplate = usageTemplate{
Use: "cert",
Short: "Operates on certificates.",
Long: `Operations on certificates.`,
}
var cmdCertListTemplate = usageTemplate{
Use: "list",
Short: "Lists certificates.",
Long: "Lists root certificates known to notary.",
}
var cmdCertRemoveTemplate = usageTemplate{
Use: "remove [ certID ]",
Short: "Removes the certificate with the given cert ID.",
Long: "Remove the certificate with the given cert ID from the local host.",
}
type certCommander struct {
// these need to be set
configGetter func() (*viper.Viper, error)
retriever passphrase.Retriever
// these are for command line parsing - no need to set
certRemoveGUN string
certRemoveYes bool
}
func (c *certCommander) GetCommand() *cobra.Command {
cmd := cmdCertTemplate.ToCommand(nil)
cmd.AddCommand(cmdCertListTemplate.ToCommand(c.certList))
cmdCertRemove := cmdCertRemoveTemplate.ToCommand(c.certRemove)
cmdCertRemove.Flags().StringVarP(
&c.certRemoveGUN, "gun", "g", "", "Globally unique name to delete certificates for")
cmdCertRemove.Flags().BoolVarP(
&c.certRemoveYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
cmd.AddCommand(cmdCertRemove)
return cmd
}
// certRemove deletes a certificate given a cert ID or a gun
// If given a gun, certRemove will also remove local TUF data
func (c *certCommander) certRemove(cmd *cobra.Command, args []string) error {
// If the user hasn't provided -g with a gun, or a cert ID, show usage
// If the user provided -g and a cert ID, also show usage
if (len(args) < 1 && c.certRemoveGUN == "") || (len(args) > 0 && c.certRemoveGUN != "") {
cmd.Usage()
return fmt.Errorf("Must specify the cert ID or the GUN of the certificates to remove")
}
config, err := c.configGetter()
if err != nil {
return err
}
trustDir := config.GetString("trust_dir")
certPath := filepath.Join(trustDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
certPath,
trustmanager.FilterCertsExpiredSha1,
)
if err != nil {
return fmt.Errorf("Failed to create a new truststore with directory: %s", trustDir)
}
var certsToRemove []*x509.Certificate
var certFoundByID *x509.Certificate
var removeTrustData bool
// If there is no GUN, we expect a cert ID
if c.certRemoveGUN == "" {
certID := args[0]
// Attempt to find this certificate
certFoundByID, err = certStore.GetCertificateByCertID(certID)
if err != nil {
// This is an invalid ID, the user might have forgotten a character
if len(certID) != notary.Sha256HexSize {
return fmt.Errorf("Unable to retrieve certificate with invalid certificate ID provided: %s", certID)
}
return fmt.Errorf("Unable to retrieve certificate with cert ID: %s", certID)
}
// the GUN is the CN from the certificate
c.certRemoveGUN = certFoundByID.Subject.CommonName
certsToRemove = []*x509.Certificate{certFoundByID}
}
toRemove, err := certStore.GetCertificatesByCN(c.certRemoveGUN)
// We could not find any certificates matching the user's query, so propagate the error
if err != nil {
return fmt.Errorf("%v", err)
}
// If we specified a GUN or if the ID we specified is the only certificate with its CN, remove all GUN certs and trust data too
if certFoundByID == nil || len(toRemove) == 1 {
removeTrustData = true
certsToRemove = toRemove
}
// List all the certificates about to be removed
cmd.Printf("The following certificates will be removed:\n\n")
for _, cert := range certsToRemove {
// This error can't occur because we're getting certs off of an
// x509 store that indexes by ID.
certID, _ := trustmanager.FingerprintCert(cert)
cmd.Printf("%s - %s\n", cert.Subject.CommonName, certID)
}
// If we were given a GUN or the last ID for a GUN, inform the user that we'll also delete all TUF data
if removeTrustData {
cmd.Printf("\nAll local trust data will be removed for %s\n", c.certRemoveGUN)
}
cmd.Println("\nAre you sure you want to remove these certificates? (yes/no)")
// Ask for confirmation before removing certificates, unless -y is provided
if !c.certRemoveYes {
confirmed := askConfirm(os.Stdin)
if !confirmed {
return fmt.Errorf("Aborting action.")
}
}
if removeTrustData {
// Remove all TUF data, so call RemoveTrustData on a NotaryRepository with the GUN
// no online operations are performed so the transport argument is nil
trustPin, err := getTrustPinning(config)
if err != nil {
return err
}
nRepo, err := notaryclient.NewNotaryRepository(
trustDir, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever, trustPin)
if err != nil {
return fmt.Errorf("Could not establish trust data for GUN %s", c.certRemoveGUN)
}
// DeleteTrustData will pick up all of the same certificates by GUN (CN) and remove them
err = nRepo.DeleteTrustData()
if err != nil {
return fmt.Errorf("Failed to delete trust data for %s", c.certRemoveGUN)
}
} else {
for _, cert := range certsToRemove {
err = certStore.RemoveCert(cert)
if err != nil {
return fmt.Errorf("Failed to remove cert %s", cert)
}
}
}
return nil
}
func (c *certCommander) certList(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
cmd.Usage()
return fmt.Errorf("")
}
config, err := c.configGetter()
if err != nil {
return err
}
trustDir := config.GetString("trust_dir")
certPath := filepath.Join(trustDir, notary.TrustedCertsDir)
// Load all individual (non-CA) certificates that aren't expired and don't use SHA1
certStore, err := trustmanager.NewX509FilteredFileStore(
certPath,
trustmanager.FilterCertsExpiredSha1,
)
if err != nil {
return fmt.Errorf("Failed to create a new truststore with directory: %s", trustDir)
}
trustedCerts := certStore.GetCertificates()
cmd.Println("")
prettyPrintCerts(trustedCerts, cmd.Out())
cmd.Println("")
return nil
}

View File

@ -1346,98 +1346,6 @@ func TestClientKeyImportExportAllRoles(t *testing.T) {
} }
} }
func assertNumCerts(t *testing.T, tempDir string, expectedNum int) []string {
output, err := runCommand(t, tempDir, "cert", "list")
require.NoError(t, err)
lines := splitLines(strings.TrimSpace(output))
if expectedNum == 0 {
require.Len(t, lines, 1)
require.Equal(t, "No trusted root certificates present.", lines[0])
return []string{}
}
require.Len(t, lines, expectedNum+2)
return lines[2:]
}
// TestClientCertInteraction
func TestClientCertInteraction(t *testing.T) {
// -- setup --
setUp(t)
tempDir := tempDirWithConfig(t, "{}")
defer os.RemoveAll(tempDir)
server := setupServer()
defer server.Close()
// -- tests --
_, err := runCommand(t, tempDir, "-s", server.URL, "init", "gun1")
require.NoError(t, err)
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2")
require.NoError(t, err)
certs := assertNumCerts(t, tempDir, 2)
// root is always on disk, because even if there's a yubikey a backup is created
assertNumKeys(t, tempDir, 1, 4, true)
// remove certs for one gun
_, err = runCommand(t, tempDir, "cert", "remove", "-g", "gun1", "-y")
require.NoError(t, err)
certs = assertNumCerts(t, tempDir, 1)
// assert that when we remove cert by gun, we do not remove repo signing keys
// (root is always on disk, because even if there's a yubikey a backup is created)
assertNumKeys(t, tempDir, 1, 4, true)
// assert that when we remove cert by gun, we also remove TUF metadata
_, err = os.Stat(filepath.Join(tempDir, "tuf", "gun1"))
require.Error(t, err)
// remove a single cert
certID := strings.Fields(certs[0])[1]
// passing an empty gun here because the string for the previous gun has
// has already been stored (a drawback of running these commands without)
// shelling out
_, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "")
require.NoError(t, err)
assertNumCerts(t, tempDir, 0)
// assert that when we remove the last cert ID for a gun, we also remove TUF metadata
_, err = os.Stat(filepath.Join(tempDir, "tuf", "gun2"))
require.Error(t, err)
// Setup certificate with nonexistent repo GUN
// Check that we can only remove one certificate when specifying one ID
startTime := time.Now()
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
noGunCert, err := cryptoservice.GenerateCertificate(
privKey, "nonexistent", startTime, startTime.AddDate(10, 0, 0))
require.NoError(t, err)
certStore, err := trustmanager.NewX509FileStore(filepath.Join(tempDir, "trusted_certificates"))
require.NoError(t, err)
err = certStore.AddCert(noGunCert)
require.NoError(t, err)
certs = assertNumCerts(t, tempDir, 1)
certID = strings.Fields(certs[0])[1]
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
noGunCert2, err := cryptoservice.GenerateCertificate(
privKey, "nonexistent", startTime, startTime.AddDate(10, 0, 0))
require.NoError(t, err)
err = certStore.AddCert(noGunCert2)
require.NoError(t, err)
certs = assertNumCerts(t, tempDir, 2)
// passing an empty gun to overwrite previously stored gun
_, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "")
require.NoError(t, err)
// Since another cert with the same GUN exists, we didn't remove everything
assertNumCerts(t, tempDir, 1)
}
// Tests default root key generation // Tests default root key generation
func TestDefaultRootKeyGeneration(t *testing.T) { func TestDefaultRootKeyGeneration(t *testing.T) {
// -- setup -- // -- setup --

View File

@ -180,11 +180,6 @@ func (n *notaryCommander) GetCommand() *cobra.Command {
retriever: n.getRetriever(), retriever: n.getRetriever(),
} }
cmdCertGenerator := &certCommander{
configGetter: n.parseConfig,
retriever: n.getRetriever(),
}
cmdTufGenerator := &tufCommander{ cmdTufGenerator := &tufCommander{
configGetter: n.parseConfig, configGetter: n.parseConfig,
retriever: n.getRetriever(), retriever: n.getRetriever(),
@ -192,7 +187,6 @@ func (n *notaryCommander) GetCommand() *cobra.Command {
notaryCmd.AddCommand(cmdKeyGenerator.GetCommand()) notaryCmd.AddCommand(cmdKeyGenerator.GetCommand())
notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand()) notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand())
notaryCmd.AddCommand(cmdCertGenerator.GetCommand())
cmdTufGenerator.AddToCommand(&notaryCmd) cmdTufGenerator.AddToCommand(&notaryCmd)

View File

@ -164,8 +164,6 @@ var exampleValidCommands = []string{
"key import backup.pem", "key import backup.pem",
"key remove e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "key remove e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"key passwd e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "key passwd e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"cert list",
"cert remove e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"delegation list repo", "delegation list repo",
"delegation add repo targets/releases path/to/pem/file.pem", "delegation add repo targets/releases path/to/pem/file.pem",
"delegation remove repo targets/releases", "delegation remove repo targets/releases",
@ -205,8 +203,8 @@ func TestInsufficientArgumentsReturnsErrorAndPrintsUsage(t *testing.T) {
cmd.SetOutput(b) cmd.SetOutput(b)
arglist := strings.Fields(args) arglist := strings.Fields(args)
if args == "key list" || args == "cert list" || args == "key generate rsa" { if args == "key list" || args == "key generate rsa" {
// in these case, "key" or "cert" or "key generate" are valid commands, so add an arg to them instead // in these case, "key" or "key generate" are valid commands, so add an arg to them instead
arglist = append(arglist, "extraArg") arglist = append(arglist, "extraArg")
} else { } else {
arglist = arglist[:len(arglist)-1] arglist = arglist[:len(arglist)-1]
@ -240,8 +238,8 @@ func TestBareCommandPrintsUsageAndNoError(t *testing.T) {
// usage is printed // usage is printed
require.Contains(t, b.String(), "Usage:", "expected usage when running `notary`") require.Contains(t, b.String(), "Usage:", "expected usage when running `notary`")
// notary key, notary cert, and notary delegation // notary key and notary delegation
for _, bareCommand := range []string{"key", "cert", "delegation"} { for _, bareCommand := range []string{"key", "delegation"} {
b := new(bytes.Buffer) b := new(bytes.Buffer)
cmd := NewNotaryCommand() cmd := NewNotaryCommand()
cmd.SetOutput(b) cmd.SetOutput(b)

View File

@ -1,14 +1,11 @@
package main package main
import ( import (
"crypto/x509"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"math"
"sort" "sort"
"strings" "strings"
"time"
"github.com/docker/notary/client" "github.com/docker/notary/client"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
@ -204,52 +201,3 @@ func prettyPrintPaths(paths []string) string {
} }
return strings.Join(prettyPaths, "\n") return strings.Join(prettyPaths, "\n")
} }
// --- pretty printing certs ---
// cert by repo name then expiry time. Don't bother sorting by fingerprint.
type certSorter []*x509.Certificate
func (t certSorter) Len() int { return len(t) }
func (t certSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t certSorter) Less(i, j int) bool {
if t[i].Subject.CommonName < t[j].Subject.CommonName {
return true
} else if t[i].Subject.CommonName > t[j].Subject.CommonName {
return false
}
return t[i].NotAfter.Before(t[j].NotAfter)
}
// Given a list of Ceritifcates in order of listing preference, pretty-prints
// the cert common name, fingerprint, and expiry
func prettyPrintCerts(certs []*x509.Certificate, writer io.Writer) {
if len(certs) == 0 {
writer.Write([]byte("\nNo trusted root certificates present.\n\n"))
return
}
sort.Stable(certSorter(certs))
table := getTable([]string{
"GUN", "Fingerprint of Trusted Root Certificate", "Expires In"}, writer)
for _, c := range certs {
days := math.Floor(c.NotAfter.Sub(time.Now()).Hours() / 24)
expiryString := "< 1 day"
if days == 1 {
expiryString = "1 day"
} else if days > 1 {
expiryString = fmt.Sprintf("%d days", int(days))
}
certID, err := trustmanager.FingerprintCert(c)
if err != nil {
fatalf("Could not fingerprint certificate: %v", err)
}
table.Append([]string{c.Subject.CommonName, certID, expiryString})
}
table.Render()
}

View File

@ -3,7 +3,6 @@ package main
import ( import (
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"crypto/x509"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -11,10 +10,8 @@ import (
"sort" "sort"
"strings" "strings"
"testing" "testing"
"time"
"github.com/docker/notary/client" "github.com/docker/notary/client"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase" "github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
@ -201,19 +198,6 @@ func TestPrettyPrintSortedTargets(t *testing.T) {
} }
} }
// --- tests for pretty printing certs ---
func generateCertificate(t *testing.T, gun string, expireInHours int64) *x509.Certificate {
ecdsaPrivKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
startTime := time.Now()
endTime := startTime.Add(time.Hour * time.Duration(expireInHours))
cert, err := cryptoservice.GenerateCertificate(ecdsaPrivKey, gun, startTime, endTime)
require.NoError(t, err)
return cert
}
// --- tests for pretty printing roles --- // --- tests for pretty printing roles ---
// If there are no roles, no table is printed, only a line saying that there // If there are no roles, no table is printed, only a line saying that there
@ -268,53 +252,3 @@ func TestPrettyPrintSortedRoles(t *testing.T) {
require.Equal(t, expected[i], splitted) require.Equal(t, expected[i], splitted)
} }
} }
// If there are no certs in the cert store store, a message that there are no
// certs should be displayed.
func TestPrettyPrintZeroCerts(t *testing.T) {
var b bytes.Buffer
prettyPrintCerts([]*x509.Certificate{}, &b)
text, err := ioutil.ReadAll(&b)
require.NoError(t, err)
lines := strings.Split(strings.TrimSpace(string(text)), "\n")
require.Len(t, lines, 1)
require.Equal(t, "No trusted root certificates present.", lines[0])
}
// Certificates are pretty-printed in table form sorted by gun and then expiry
func TestPrettyPrintSortedCerts(t *testing.T) {
unsorted := []*x509.Certificate{
generateCertificate(t, "xylitol", 77), // 3 days 5 hours
generateCertificate(t, "xylitol", 12), // less than 1 day
generateCertificate(t, "cheesecake", 25), // a little more than 1 day
generateCertificate(t, "baklava", 239), // almost 10 days
}
var b bytes.Buffer
prettyPrintCerts(unsorted, &b)
text, err := ioutil.ReadAll(&b)
require.NoError(t, err)
expected := [][]string{
{"baklava", "9 days"},
{"cheesecake", "1 day"},
{"xylitol", "< 1 day"},
{"xylitol", "3 days"},
}
lines := strings.Split(strings.TrimSpace(string(text)), "\n")
require.Len(t, lines, len(expected)+2)
// starts with headers
require.True(t, reflect.DeepEqual(strings.Fields(lines[0]), strings.Fields(
"GUN FINGERPRINT OF TRUSTED ROOT CERTIFICATE EXPIRES IN")))
require.Equal(t, "----", lines[1][:4])
for i, line := range lines[2:] {
splitted := strings.Fields(line)
require.True(t, len(splitted) >= 3)
require.Equal(t, expected[i][0], splitted[0])
require.Equal(t, expected[i][1], strings.Join(splitted[2:], " "))
}
}

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

@ -1,138 +0,0 @@
package trustmanager
import (
"crypto/x509"
"testing"
)
func TestVerifyLeafSuccessfully(t *testing.T) {
// Get root certificate
rootCA, err := LoadCertFromFile("../fixtures/root-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Get intermediate certificate
intermediateCA, err := LoadCertFromFile("../fixtures/intermediate-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Get leaf certificate
leafCert, err := LoadCertFromFile("../fixtures/secure.example.com.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Create a store and add the CA root
store := NewX509MemStore()
err = store.AddCert(rootCA)
if err != nil {
t.Fatalf("failed to load certificate from file: %v", err)
}
// Get our certList with Leaf Cert and Intermediate
certList := []*x509.Certificate{leafCert, intermediateCA}
// Try to find a valid chain for cert
err = Verify(store, "secure.example.com", certList)
if err != nil {
t.Fatalf("expected to find a valid chain for this certificate: %v", err)
}
}
func TestVerifyLeafSuccessfullyWithMultipleIntermediates(t *testing.T) {
// Get root certificate
rootCA, err := LoadCertFromFile("../fixtures/root-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Get intermediate certificate
intermediateCA, err := LoadCertFromFile("../fixtures/intermediate-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Get leaf certificate
leafCert, err := LoadCertFromFile("../fixtures/secure.example.com.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Create a store and add the CA root
store := NewX509MemStore()
err = store.AddCert(rootCA)
if err != nil {
t.Fatalf("failed to load certificate from file: %v", err)
}
// Get our certList with Leaf Cert and Intermediate
certList := []*x509.Certificate{leafCert, intermediateCA, intermediateCA, rootCA}
// Try to find a valid chain for cert
err = Verify(store, "secure.example.com", certList)
if err != nil {
t.Fatalf("expected to find a valid chain for this certificate: %v", err)
}
}
func TestVerifyLeafWithNoIntermediate(t *testing.T) {
// Get root certificate
rootCA, err := LoadCertFromFile("../fixtures/root-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Get leaf certificate
leafCert, err := LoadCertFromFile("../fixtures/secure.example.com.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Create a store and add the CA root
store := NewX509MemStore()
err = store.AddCert(rootCA)
if err != nil {
t.Fatalf("failed to load certificate from file: %v", err)
}
// Get our certList with Leaf Cert and Intermediate
certList := []*x509.Certificate{leafCert, leafCert}
// Try to find a valid chain for cert
err = Verify(store, "secure.example.com", certList)
if err == nil {
t.Fatalf("expected error due to more than one leaf certificate")
}
}
func TestVerifyLeafWithNoLeaf(t *testing.T) {
// Get root certificate
rootCA, err := LoadCertFromFile("../fixtures/root-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Get intermediate certificate
intermediateCA, err := LoadCertFromFile("../fixtures/intermediate-ca.crt")
if err != nil {
t.Fatalf("couldn't load fixture: %v", err)
}
// Create a store and add the CA root
store := NewX509MemStore()
err = store.AddCert(rootCA)
if err != nil {
t.Fatalf("failed to load certificate from file: %v", err)
}
// Get our certList with Leaf Cert and Intermediate
certList := []*x509.Certificate{intermediateCA, intermediateCA}
// Try to find a valid chain for cert
err = Verify(store, "secure.example.com", certList)
if err == nil {
t.Fatalf("expected error due to no leaves provided")
}
}

View File

@ -14,8 +14,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"net/http"
"net/url"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
@ -24,40 +22,6 @@ import (
"github.com/docker/notary/tuf/data" "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
}
// CertToPEM is a utility function returns a PEM encoded x509 Certificate // CertToPEM is a utility function returns a PEM encoded x509 Certificate
func CertToPEM(cert *x509.Certificate) []byte { func CertToPEM(cert *x509.Certificate) []byte {
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
@ -100,60 +64,6 @@ func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
return nil, errors.New("no certificates found in PEM data") return nil, errors.New("no certificates found in PEM data")
} }
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
func FingerprintCert(cert *x509.Certificate) (string, error) {
certID, err := fingerprintCert(cert)
if err != nil {
return "", err
}
return string(certID), nil
}
func fingerprintCert(cert *x509.Certificate) (CertID, error) {
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
pemdata := pem.EncodeToMemory(&block)
var tufKey data.PublicKey
switch cert.PublicKeyAlgorithm {
case x509.RSA:
tufKey = data.NewRSAx509PublicKey(pemdata)
case x509.ECDSA:
tufKey = data.NewECDSAx509PublicKey(pemdata)
default:
return "", fmt.Errorf("got Unknown key type while fingerprinting certificate")
}
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 // LoadCertFromFile loads the first certificate from the file provided. The
// data is expected to be PEM Encoded and contain one of more certificates // data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE" // with PEM type "CERTIFICATE"
@ -533,37 +443,39 @@ func CertToKey(cert *x509.Certificate) data.PublicKey {
// CertsToKeys transforms each of the input certificate chains into its corresponding // CertsToKeys transforms each of the input certificate chains into its corresponding
// PublicKey // PublicKey
func CertsToKeys(leafCerts []*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey { func CertsToKeys(leafCerts map[string]*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey {
keys := make(map[string]data.PublicKey) keys := make(map[string]data.PublicKey)
for _, leafCert := range leafCerts { for id, leafCert := range leafCerts {
certBundle := []*x509.Certificate{leafCert} if key, err := CertBundleToKey(leafCert, intCerts[id]); err == nil {
certID, err := FingerprintCert(leafCert) keys[key.ID()] = key
if err != nil {
continue
} }
if intCertsForLeaves, ok := intCerts[certID]; ok {
certBundle = append(certBundle, intCertsForLeaves...)
}
certChainPEM, err := CertChainToPEM(certBundle)
if err != nil {
continue
}
var newKey data.PublicKey
// Use the leaf cert's public key algorithm for typing
switch leafCert.PublicKeyAlgorithm {
case x509.RSA:
newKey = data.NewRSAx509PublicKey(certChainPEM)
case x509.ECDSA:
newKey = data.NewECDSAx509PublicKey(certChainPEM)
default:
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
continue
}
keys[newKey.ID()] = newKey
} }
return keys return keys
} }
// CertBundleToKey creates a TUF key from a leaf certs and a list of
// intermediates
func CertBundleToKey(leafCert *x509.Certificate, intCerts []*x509.Certificate) (data.PublicKey, error) {
certBundle := []*x509.Certificate{leafCert}
certBundle = append(certBundle, intCerts...)
certChainPEM, err := CertChainToPEM(certBundle)
if err != nil {
return nil, err
}
var newKey data.PublicKey
// Use the leaf cert's public key algorithm for typing
switch leafCert.PublicKeyAlgorithm {
case x509.RSA:
newKey = data.NewRSAx509PublicKey(certChainPEM)
case x509.ECDSA:
newKey = data.NewECDSAx509PublicKey(certChainPEM)
default:
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
return nil, x509.ErrUnsupportedAlgorithm
}
return newKey, nil
}
// NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval. // NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval.
func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate, error) { func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
@ -610,14 +522,3 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
return key.ID(), nil 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
}

View File

@ -28,10 +28,19 @@ func TestCertsToKeys(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Get our certList with Leaf Cert and Intermediate // Get our certList with Leaf Cert and Intermediate
certList := []*x509.Certificate{leafCert, intermediateCA, rootCA} certMap := map[string]*x509.Certificate{
"a": leafCert,
"b": intermediateCA,
"c": rootCA,
}
certList := []*x509.Certificate{
leafCert,
intermediateCA,
rootCA,
}
// Call CertsToKeys // Call CertsToKeys
keys := CertsToKeys(certList, make(map[string][]*x509.Certificate)) keys := CertsToKeys(certMap, make(map[string][]*x509.Certificate))
require.NotNil(t, keys) require.NotNil(t, keys)
require.Len(t, keys, 3) require.Len(t, keys, 3)
@ -49,7 +58,7 @@ func TestCertsToKeys(t *testing.T) {
emptyCert := x509.Certificate{} emptyCert := x509.Certificate{}
// Also try changing the pre-existing leaf cert into an invalid algorithm // Also try changing the pre-existing leaf cert into an invalid algorithm
leafCert.PublicKeyAlgorithm = x509.DSA leafCert.PublicKeyAlgorithm = x509.DSA
keys = CertsToKeys([]*x509.Certificate{&emptyCert, leafCert}, make(map[string][]*x509.Certificate)) keys = CertsToKeys(map[string]*x509.Certificate{"d": &emptyCert, "e": leafCert}, make(map[string][]*x509.Certificate))
require.Empty(t, keys) require.Empty(t, keys)
} }

View File

@ -37,13 +37,9 @@ func (err ErrRootRotationFail) Error() string {
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason) return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
} }
func prettyFormatCertIDs(certs []*x509.Certificate) string { func prettyFormatCertIDs(certs map[string]*x509.Certificate) string {
ids := make([]string, 0, len(certs)) ids := make([]string, 0, len(certs))
for _, cert := range certs { for id := range certs {
id, err := trustmanager.FingerprintCert(cert)
if err != nil {
id = fmt.Sprintf("[Error %s]", err)
}
ids = append(ids, id) ids = append(ids, id)
} }
return strings.Join(ids, ", ") return strings.Join(ids, ", ")
@ -53,8 +49,9 @@ func prettyFormatCertIDs(certs []*x509.Certificate) string {
ValidateRoot receives a new root, validates its correctness and attempts to ValidateRoot receives a new root, validates its correctness and attempts to
do root key rotation if needed. do root key rotation if needed.
First we list the current trusted certificates we have for a particular GUN. If First we check if we have any trusted certificates for a particular GUN in
that list is non-empty means that we've already seen this repository before, and a previous root, if we have one. If the previous root is not nil and we find
certificates for this GUN, we've already seen this repository before, and
have a list of trusted certificates for it. In this case, we use this list of have a list of trusted certificates for it. In this case, we use this list of
certificates to attempt to validate this root file. certificates to attempt to validate this root file.
@ -86,68 +83,66 @@ We shall call this: TOFUS.
Validation failure at any step will result in an ErrValidationFailed error. Validation failure at any step will result in an ErrValidationFailed error.
*/ */
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error { func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) (*data.SignedRoot, error) {
logrus.Debugf("entered ValidateRoot with dns: %s", gun) logrus.Debugf("entered ValidateRoot with dns: %s", gun)
signedRoot, err := data.RootFromSigned(root) signedRoot, err := data.RootFromSigned(root)
if err != nil { if err != nil {
return err return nil, err
} }
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole) rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
if err != nil { if err != nil {
return err return nil, err
} }
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN // Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
allLeafCerts, allIntCerts := parseAllCerts(signedRoot) allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun) certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
if err != nil { if err != nil {
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err) logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"} return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
} }
// Retrieve all the trusted certificates that match this gun // If we have a previous root, let's try to use it to validate that this new root is valid.
trustedCerts, err := certStore.GetCertificatesByCN(gun) if prevRoot != nil {
if err != nil { // Retrieve all the trusted certificates from our previous root
// If the error that we get back is different than ErrNoCertificatesFound // Note that we do not validate expiries here since our originally trusted root might have expired certs
// we couldn't check if there are any certificates with this CN already allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
// trusted. Let's take the conservative approach and return a failed validation trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err) // Use the certificates we found in the previous root for the GUN to verify its signatures
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"} // This could potentially be an empty set, in which case we will fail to verify
logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun,
prettyFormatCertIDs(trustedLeafCerts))
// Extract the previous root's threshold for signature verification
prevRootRoleData, ok := prevRoot.Signed.Roles[data.CanonicalRootRole]
if !ok {
return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"}
} }
}
// If we have certificates that match this specific GUN, let's make sure to
// use them first to validate that this new root is valid.
if len(trustedCerts) != 0 {
logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun,
prettyFormatCertIDs(trustedCerts))
err = signed.VerifySignatures( err = signed.VerifySignatures(
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1}) root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold})
if err != nil { if err != nil {
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"} return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"}
} }
} else { } else {
logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun) logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun) trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
if err != nil { if err != nil {
return &ErrValidationFail{Reason: err.Error()} return nil, &ErrValidationFail{Reason: err.Error()}
} }
validPinnedCerts := []*x509.Certificate{} validPinnedCerts := map[string]*x509.Certificate{}
for _, cert := range certsFromRoot { for id, cert := range certsFromRoot {
certID, err := trustmanager.FingerprintCert(cert) if ok := trustPinCheckFunc(cert, allIntCerts[id]); !ok {
if err != nil {
continue continue
} }
if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok { validPinnedCerts[id] = cert
continue
}
validPinnedCerts = append(validPinnedCerts, cert)
} }
if len(validPinnedCerts) == 0 { if len(validPinnedCerts) == 0 {
return &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"} return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
} }
certsFromRoot = validPinnedCerts certsFromRoot = validPinnedCerts
} }
@ -159,64 +154,29 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold}) Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
if err != nil { if err != nil {
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
return &ErrValidationFail{Reason: "failed to validate integrity of roots"} return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"}
}
// Getting here means:
// A) we had trusted certificates and both the old and new validated this root.
// or
// B) we had no trusted certificates but the new set of certificates has integrity (self-signed).
logrus.Debugf("entering root certificate rotation for: %s", gun)
// Do root certificate rotation: we trust only the certs present in the new root
// First we add all the new certificates (even if they already exist)
for _, cert := range certsFromRoot {
err := certStore.AddCert(cert)
if err != nil {
// If the error is already exists we don't fail the rotation
if _, ok := err.(*trustmanager.ErrCertExists); ok {
logrus.Debugf("ignoring certificate addition to: %s", gun)
continue
}
logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
}
}
// Now we delete old certificates that aren't present in the new root
oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot)
if err != nil {
logrus.Debugf("inconsistency when removing old certificates: %v", err)
return err
}
for certID, cert := range oldCertsToRemove {
logrus.Debugf("removing certificate with certID: %s", certID)
err = certStore.RemoveCert(cert)
if err != nil {
logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
}
} }
logrus.Debugf("Root validation succeeded for %s", gun) logrus.Debugf("Root validation succeeded for %s", gun)
return nil return signedRoot, nil
} }
// validRootLeafCerts returns a list of non-expired, non-sha1 certificates // validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
// found in root whose Common-Names match the provided GUN. Note that this // found in root whose Common-Names match the provided GUN. Note that this
// "validity" alone does not imply any measure of trust. // "validity" alone does not imply any measure of trust.
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) ([]*x509.Certificate, error) { func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, checkExpiry bool) (map[string]*x509.Certificate, error) {
var validLeafCerts []*x509.Certificate validLeafCerts := make(map[string]*x509.Certificate)
// Go through every leaf certificate and check that the CN matches the gun // Go through every leaf certificate and check that the CN matches the gun
for _, cert := range allLeafCerts { for id, cert := range allLeafCerts {
// Validate that this leaf certificate has a CN that matches the exact gun // Validate that this leaf certificate has a CN that matches the exact gun
if cert.Subject.CommonName != gun { if cert.Subject.CommonName != gun {
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s", logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
cert.Subject.CommonName, gun) cert.Subject.CommonName, gun)
continue continue
} }
// Make sure the certificate is not expired // Make sure the certificate is not expired if checkExpiry is true
if time.Now().After(cert.NotAfter) { if checkExpiry && time.Now().After(cert.NotAfter) {
logrus.Debugf("error leaf certificate is expired") logrus.Debugf("error leaf certificate is expired")
continue continue
} }
@ -230,7 +190,7 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
continue continue
} }
validLeafCerts = append(validLeafCerts, cert) validLeafCerts[id] = cert
} }
if len(validLeafCerts) < 1 { if len(validLeafCerts) < 1 {
@ -246,11 +206,15 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
// parseAllCerts returns two maps, one with all of the leafCertificates and one // parseAllCerts returns two maps, one with all of the leafCertificates and one
// with all the intermediate certificates found in signedRoot // with all the intermediate certificates found in signedRoot
func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) { func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
if signedRoot == nil {
return nil, nil
}
leafCerts := make(map[string]*x509.Certificate) leafCerts := make(map[string]*x509.Certificate)
intCerts := make(map[string][]*x509.Certificate) intCerts := make(map[string][]*x509.Certificate)
// Before we loop through all root keys available, make sure any exist // Before we loop through all root keys available, make sure any exist
rootRoles, ok := signedRoot.Signed.Roles["root"] rootRoles, ok := signedRoot.Signed.Roles[data.CanonicalRootRole]
if !ok { if !ok {
logrus.Debugf("tried to parse certificates from invalid root signed data") logrus.Debugf("tried to parse certificates from invalid root signed data")
return nil, nil return nil, nil
@ -290,59 +254,14 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
// Get the ID of the leaf certificate // Get the ID of the leaf certificate
leafCert := leafCertList[0] leafCert := leafCertList[0]
leafID, err := trustmanager.FingerprintCert(leafCert)
if err != nil {
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
continue
}
// Store the leaf cert in the map // Store the leaf cert in the map
leafCerts[leafID] = leafCert leafCerts[key.ID()] = leafCert
// Get all the remainder certificates marked as a CA to be used as intermediates // Get all the remainder certificates marked as a CA to be used as intermediates
intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts) intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
intCerts[leafID] = intermediateCerts intCerts[key.ID()] = intermediateCerts
} }
return leafCerts, intCerts return leafCerts, intCerts
} }
// certsToRemove returns all the certificates from oldCerts that aren't present
// in newCerts. Note that newCerts should never be empty, else this function will error.
// We expect newCerts to come from validateRootLeafCerts, which does not return empty sets.
func certsToRemove(oldCerts, newCerts []*x509.Certificate) (map[string]*x509.Certificate, error) {
certsToRemove := make(map[string]*x509.Certificate)
// Populate a map with all the IDs from newCert
var newCertMap = make(map[string]struct{})
for _, cert := range newCerts {
certID, err := trustmanager.FingerprintCert(cert)
if err != nil {
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", certID, err)
continue
}
newCertMap[certID] = struct{}{}
}
// We don't want to "rotate" certificates to an empty set, nor keep old certificates if the
// new root does not trust them. newCerts should come from validRootLeafCerts, which refuses
// to return an empty set, and they should all be fingerprintable, so this should never happen
// - fail just to be sure.
if len(newCertMap) == 0 {
return nil, &ErrRootRotationFail{Reason: "internal error, got no certificates to rotate to"}
}
// Iterate over all the old certificates and check to see if we should remove them
for _, cert := range oldCerts {
certID, err := trustmanager.FingerprintCert(cert)
if err != nil {
logrus.Debugf("error while fingerprinting root certificate with certID: %s, %v", certID, err)
continue
}
if _, ok := newCertMap[certID]; !ok {
certsToRemove[certID] = cert
}
}
return certsToRemove, nil
}

View File

@ -18,11 +18,11 @@ import (
"time" "time"
"github.com/docker/notary"
"github.com/docker/notary/cryptoservice" "github.com/docker/notary/cryptoservice"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/testutils"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -44,86 +44,6 @@ const rootPubKeyID = "49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b
const targetsPubKeyID = "1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670" const targetsPubKeyID = "1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670"
func TestCertsToRemove(t *testing.T) {
// Get a few certificates to test with
cert1, err := trustmanager.LoadCertFromFile("../fixtures/secure.example.com.crt")
require.NoError(t, err)
cert1KeyID, err := trustmanager.FingerprintCert(cert1)
require.NoError(t, err)
// Get intermediate certificate
cert2, err := trustmanager.LoadCertFromFile("../fixtures/self-signed_secure.example.com.crt")
require.NoError(t, err)
cert2KeyID, err := trustmanager.FingerprintCert(cert2)
require.NoError(t, err)
// Get leaf certificate
cert3, err := trustmanager.LoadCertFromFile("../fixtures/self-signed_docker.com-notary.crt")
require.NoError(t, err)
cert3KeyID, err := trustmanager.FingerprintCert(cert3)
require.NoError(t, err)
// Call CertsToRemove with only one old and one new
oldCerts := []*x509.Certificate{cert1}
newCerts := []*x509.Certificate{cert2}
certificates, err := certsToRemove(oldCerts, newCerts)
require.NoError(t, err)
require.Len(t, certificates, 1)
_, ok := certificates[cert1KeyID]
require.True(t, ok)
// Call CertsToRemove with two old and one new
oldCerts = []*x509.Certificate{cert1, cert2}
newCerts = []*x509.Certificate{cert3}
certificates, err = certsToRemove(oldCerts, newCerts)
require.NoError(t, err)
require.Len(t, certificates, 2)
_, ok = certificates[cert1KeyID]
require.True(t, ok)
_, ok = certificates[cert2KeyID]
require.True(t, ok)
_, ok = certificates[cert3KeyID]
require.False(t, ok)
// Call CertsToRemove with two new and one old
oldCerts = []*x509.Certificate{cert3}
newCerts = []*x509.Certificate{cert2, cert1}
certificates, err = certsToRemove(oldCerts, newCerts)
require.NoError(t, err)
require.Len(t, certificates, 1)
_, ok = certificates[cert3KeyID]
require.True(t, ok)
_, ok = certificates[cert1KeyID]
require.False(t, ok)
_, ok = certificates[cert2KeyID]
require.False(t, ok)
// Call CertsToRemove with three old certificates and no new
oldCerts = []*x509.Certificate{cert1, cert2, cert3}
newCerts = []*x509.Certificate{}
certificates, err = certsToRemove(oldCerts, newCerts)
require.Error(t, err)
// Call CertsToRemove with three new certificates and no old
oldCerts = []*x509.Certificate{}
newCerts = []*x509.Certificate{cert1, cert2, cert3}
certificates, err = certsToRemove(oldCerts, newCerts)
require.NoError(t, err)
require.Len(t, certificates, 0)
_, ok = certificates[cert1KeyID]
require.False(t, ok)
_, ok = certificates[cert2KeyID]
require.False(t, ok)
_, ok = certificates[cert3KeyID]
require.False(t, ok)
}
func TestValidateRoot(t *testing.T) { func TestValidateRoot(t *testing.T) {
var testSignedRoot data.Signed var testSignedRoot data.Signed
var signedRootBytes bytes.Buffer var signedRootBytes bytes.Buffer
@ -133,14 +53,6 @@ func TestValidateRoot(t *testing.T) {
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
// Create a X509Store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
// Execute our template // Execute our template
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
@ -150,12 +62,12 @@ func TestValidateRoot(t *testing.T) {
// This call to ValidateRoot will succeed since we are using a valid PEM // This call to ValidateRoot will succeed since we are using a valid PEM
// encoded certificate, and have no other certificates for this CN // encoded certificate, and have no other certificates for this CN
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
require.NoError(t, err) require.NoError(t, err)
// This call to ValidateRoot will fail since we are passing in a dnsName that // This call to ValidateRoot will fail since we are passing in a dnsName that
// doesn't match the CN of the certificate. // doesn't match the CN of the certificate.
err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{}) _, err = ValidateRoot(nil, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{})
require.Error(t, err, "An error was expected") require.Error(t, err, "An error was expected")
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
@ -169,7 +81,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot // Unmarshal our signedroot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
require.Error(t, err, "illegal base64 data at input byte") require.Error(t, err, "illegal base64 data at input byte")
// //
@ -182,7 +94,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot // Unmarshal our signedroot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
require.Error(t, err, "An error was expected") require.Error(t, err, "An error was expected")
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
@ -197,7 +109,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot // Unmarshal our signedroot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
require.Error(t, err, "An error was expected") require.Error(t, err, "An error was expected")
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
@ -216,7 +128,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot // Unmarshal our signedroot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com", TrustPinConfig{}) _, err = ValidateRoot(nil, &testSignedRoot, "secure.example.com", TrustPinConfig{})
require.Error(t, err, "An error was expected") require.Error(t, err, "An error was expected")
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
} }
@ -230,14 +142,6 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
// Create a X509Store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
// Execute our template // Execute our template
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
@ -246,7 +150,7 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
// This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs // This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
} }
@ -259,31 +163,25 @@ func TestValidateRootWithPinnedCert(t *testing.T) {
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
// Create a X509Store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
// Execute our template // Execute our template
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
// Unmarshal our signedroot // Unmarshal our signedroot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
require.NoError(t, err)
// This call to ValidateRoot should succeed with the correct Cert ID (same as root public key ID) // This call to ValidateRoot should succeed with the correct Cert ID (same as root public key ID)
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true}) validatedSignedRoot, err := ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true})
require.NoError(t, err) require.NoError(t, err)
generateRootKeyIDs(typedSignedRoot)
// purge the cert store to check another valid certs trust pinning require.Equal(t, validatedSignedRoot, typedSignedRoot)
certStore.RemoveAll()
// This call to ValidateRoot should also succeed with the correct Cert ID (same as root public key ID), even though we passed an extra bad one // This call to ValidateRoot should also succeed with the correct Cert ID (same as root public key ID), even though we passed an extra bad one
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true}) validatedSignedRoot, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, validatedSignedRoot, typedSignedRoot)
} }
func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) { func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
@ -436,19 +334,15 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil) err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil)
require.NoError(t, err) require.NoError(t, err)
typedSignedRoot, err := data.RootFromSigned(signedRoot)
require.NoError(t, err)
tempBaseDir, err := ioutil.TempDir("", "notary-test-") tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
// Create a X509Store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
err = ValidateRoot( validatedRoot, err := ValidateRoot(
certStore, nil,
signedRoot, signedRoot,
"docker.io/notary/test", "docker.io/notary/test",
TrustPinConfig{ TrustPinConfig{
@ -459,6 +353,8 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
}, },
) )
require.NoError(t, err, "failed to validate certID with intermediate") require.NoError(t, err, "failed to validate certID with intermediate")
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
} }
func TestValidateRootFailuresWithPinnedCert(t *testing.T) { func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
@ -470,40 +366,36 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
// Create a X509Store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
// Execute our template // Execute our template
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
// Unmarshal our signedroot // Unmarshal our signedroot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
require.NoError(t, err)
// This call to ValidateRoot should fail due to an incorrect cert ID // This call to ValidateRoot should fail due to an incorrect cert ID
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// This call to ValidateRoot should fail due to an empty cert ID // This call to ValidateRoot should fail due to an empty cert ID
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false // This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets // This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// This call to ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs // This call to ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false}) validatedRoot, err := ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false})
require.NoError(t, err) require.NoError(t, err)
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
} }
func TestValidateRootWithPinnedCA(t *testing.T) { func TestValidateRootWithPinnedCA(t *testing.T) {
@ -515,44 +407,39 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
// Create a X509Store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
// Unmarshal our signedRoot // Unmarshal our signedRoot
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
require.NoError(t, err)
// This call to ValidateRoot will fail because we have an invalid path for the CA // This call to ValidateRoot will fail because we have an invalid path for the CA
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
require.Error(t, err) require.Error(t, err)
// This call to ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled // This call to ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// This call to ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS // This call to ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false}) validatedRoot, err := ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false})
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, certStore.RemoveAll()) generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
// Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it // Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it
invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca") invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca")
require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644)) require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644))
// Using this invalid CA cert should fail on ValidateRoot // Using this invalid CA cert should fail on ValidateRoot
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
validCAFilepath := "../fixtures/root-ca.crt" validCAFilepath := "../fixtures/root-ca.crt"
// If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail // If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true}) _, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate // Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate
@ -601,12 +488,16 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil) err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil)
require.NoError(t, err) require.NoError(t, err)
// Check that we validate correctly against a pinned CA and provided bundle newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot)
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
require.NoError(t, err) require.NoError(t, err)
// Check that we validate correctly against a pinned CA and provided bundle
validatedRoot, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
require.NoError(t, err)
generateRootKeyIDs(newTypedSignedRoot)
require.Equal(t, newTypedSignedRoot, validatedRoot)
// Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly // Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly
certStore.RemoveAll()
goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath) goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath)
require.NoError(t, err) require.NoError(t, err)
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
@ -623,10 +514,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644)) require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644))
// Check that we validate correctly against a pinned CA and provided bundle // Check that we validate correctly against a pinned CA and provided bundle
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true}) validatedRoot, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, newTypedSignedRoot, validatedRoot)
certStore.RemoveAll()
testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
require.NoError(t, err) require.NoError(t, err)
testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID()) testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID())
@ -638,11 +529,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem") allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem")
require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644)) require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644))
// Now only use expired certs in the bundle, we should fail // Now only use expired certs in the bundle, we should fail
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true}) _, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
// Add a CA cert for a that won't validate against the root leaf certificate // Add a CA cert for a that won't validate against the root leaf certificate
certStore.RemoveAll()
testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
require.NoError(t, err) require.NoError(t, err)
testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID()) testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID())
@ -653,7 +543,7 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644)) require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644))
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true}) _, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true})
require.Error(t, err) require.Error(t, err)
} }
@ -666,62 +556,43 @@ func TestValidateSuccessfulRootRotation(t *testing.T) {
} }
} }
// Generates a X509Store in a temporary directory and returns the
// store and certificates for two keys which have been added to the keystore.
// Also returns the temporary directory so it can be cleaned up.
func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
string, trustmanager.X509Store, *cryptoservice.CryptoService, []*x509.Certificate) {
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
require.NoError(t, err, "failed to create a temporary directory: %s", err)
fileKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever)
require.NoError(t, err)
cryptoService := cryptoservice.NewCryptoService(fileKeyStore)
// Create a store
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
trustPath,
trustmanager.FilterCertsExpiredSha1,
)
require.NoError(t, err)
certificates := make([]*x509.Certificate, 2)
for i := 0; i < 2; i++ {
pubKey, err := cryptoService.Create("root", gun, keyAlg)
require.NoError(t, err)
key, _, err := fileKeyStore.GetKey(pubKey.ID())
require.NoError(t, err)
cert, err := generateTestingCertificate(key, gun)
require.NoError(t, err)
certificates[i] = cert
}
return tempBaseDir, certStore, cryptoService, certificates
}
func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string) { func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string) {
// The gun to test // The gun to test
gun := "docker.com/notary" gun := "docker.com/notary"
tempBaseDir, certStore, cs, certificates := filestoreWithTwoCerts(t, gun, keyAlg) memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
defer os.RemoveAll(tempBaseDir) cs := cryptoservice.NewCryptoService(memKeyStore)
origRootCert := certificates[0]
replRootCert := certificates[1]
// Add the old root cert part of trustedCertificates
certStore.AddCert(origRootCert)
// We need the PEM representation of the replacement key to put it into the TUF data
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
// Tuf key with PEM-encoded x509 certificate // Tuf key with PEM-encoded x509 certificate
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert) origRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert) require.NoError(t, err)
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
require.NoError(t, err)
origTestRoot, err := data.NewRoot(
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
map[string]*data.RootRole{
data.CanonicalRootRole: &origRootRole.RootRole,
data.CanonicalTargetsRole: &origRootRole.RootRole,
data.CanonicalSnapshotRole: &origRootRole.RootRole,
data.CanonicalTimestampRole: &origRootRole.RootRole,
},
false,
)
require.NoError(t, err, "Failed to create new root")
signedOrigTestRoot, err := origTestRoot.ToSigned()
require.NoError(t, err)
err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
require.NoError(t, err)
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
require.NoError(t, err)
// Tuf key with PEM-encoded x509 certificate
replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
require.NoError(t, err)
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
require.NoError(t, err) require.NoError(t, err)
@ -743,15 +614,15 @@ func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string
err = signed.Sign(cs, signedTestRoot, []data.PublicKey{replRootKey, origRootKey}, 2, nil) err = signed.Sign(cs, signedTestRoot, []data.PublicKey{replRootKey, origRootKey}, 2, nil)
require.NoError(t, err) require.NoError(t, err)
// This call to ValidateRoot will succeed since we are using a valid PEM typedSignedRoot, err := data.RootFromSigned(signedTestRoot)
// encoded certificate, and have no other certificates for this CN
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
require.NoError(t, err) require.NoError(t, err)
// Finally, validate the only trusted certificate that exists is the new one // This call to ValidateRoot will succeed since we are using a valid PEM
certificates = certStore.GetCertificates() // encoded certificate, and have no other certificates for this CN
require.Len(t, certificates, 1) validatedRoot, err := ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
require.Equal(t, certificates[0], replRootCert) require.NoError(t, err)
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
} }
// TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation // TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation
@ -767,20 +638,39 @@ func TestValidateRootRotationMissingOrigSig(t *testing.T) {
func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType string) { func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType string) {
gun := "docker.com/notary" gun := "docker.com/notary"
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts( memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
t, gun, keyAlg) cs := cryptoservice.NewCryptoService(memKeyStore)
defer os.RemoveAll(tempBaseDir)
origRootCert := certificates[0]
replRootCert := certificates[1]
// Add the old root cert part of trustedCertificates
certStore.AddCert(origRootCert)
// We need the PEM representation of the replacement key to put it into the TUF data
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
// Tuf key with PEM-encoded x509 certificate // Tuf key with PEM-encoded x509 certificate
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert) origRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
require.NoError(t, err)
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
require.NoError(t, err)
origTestRoot, err := data.NewRoot(
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
map[string]*data.RootRole{
data.CanonicalRootRole: &origRootRole.RootRole,
data.CanonicalTargetsRole: &origRootRole.RootRole,
data.CanonicalSnapshotRole: &origRootRole.RootRole,
data.CanonicalTimestampRole: &origRootRole.RootRole,
},
false,
)
require.NoError(t, err, "Failed to create new root")
signedOrigTestRoot, err := origTestRoot.ToSigned()
require.NoError(t, err)
err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
require.NoError(t, err)
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
require.NoError(t, err)
// Tuf key with PEM-encoded x509 certificate
replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
require.NoError(t, err)
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
require.NoError(t, err) require.NoError(t, err)
@ -801,19 +691,13 @@ func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType st
require.NoError(t, err) require.NoError(t, err)
// We only sign with the new key, and not with the original one. // We only sign with the new key, and not with the original one.
err = signed.Sign(cryptoService, signedTestRoot, []data.PublicKey{replRootKey}, 1, nil) err = signed.Sign(cs, signedTestRoot, []data.PublicKey{replRootKey}, 1, nil)
require.NoError(t, err) require.NoError(t, err)
// This call to ValidateRoot will succeed since we are using a valid PEM // This call to ValidateRoot will succeed since we are using a valid PEM
// encoded certificate, and have no other certificates for this CN // encoded certificate, and have no other certificates for this CN
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{}) _, err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
require.Error(t, err, "insuficient signatures on root") require.Error(t, err, "insuficient signatures on root")
// Finally, validate the only trusted certificate that exists is still
// the old one
certificates = certStore.GetCertificates()
require.Len(t, certificates, 1)
require.Equal(t, certificates[0], origRootCert)
} }
// TestValidateRootRotationMissingNewSig runs through a full root certificate rotation // TestValidateRootRotationMissingNewSig runs through a full root certificate rotation
@ -829,22 +713,39 @@ func TestValidateRootRotationMissingNewSig(t *testing.T) {
func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) { func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) {
gun := "docker.com/notary" gun := "docker.com/notary"
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts( memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
t, gun, keyAlg) cs := cryptoservice.NewCryptoService(memKeyStore)
defer os.RemoveAll(tempBaseDir)
origRootCert := certificates[0]
replRootCert := certificates[1]
// Add the old root cert part of trustedCertificates
certStore.AddCert(origRootCert)
// We need the PEM representation of the replacement key to put it into the TUF data
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
// Tuf key with PEM-encoded x509 certificate // Tuf key with PEM-encoded x509 certificate
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert) origRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert) require.NoError(t, err)
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
require.NoError(t, err)
origTestRoot, err := data.NewRoot(
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
map[string]*data.RootRole{
data.CanonicalRootRole: &origRootRole.RootRole,
data.CanonicalTargetsRole: &origRootRole.RootRole,
data.CanonicalSnapshotRole: &origRootRole.RootRole,
data.CanonicalTimestampRole: &origRootRole.RootRole,
},
false,
)
require.NoError(t, err, "Failed to create new root")
signedOrigTestRoot, err := origTestRoot.ToSigned()
require.NoError(t, err)
err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
require.NoError(t, err)
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
require.NoError(t, err)
// Tuf key with PEM-encoded x509 certificate
replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
require.NoError(t, err)
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
require.NoError(t, err) require.NoError(t, err)
@ -865,19 +766,13 @@ func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType str
require.NoError(t, err) require.NoError(t, err)
// We only sign with the old key, and not with the new one // We only sign with the old key, and not with the new one
err = signed.Sign(cryptoService, signedTestRoot, []data.PublicKey{origRootKey}, 1, nil) err = signed.Sign(cs, signedTestRoot, []data.PublicKey{origRootKey}, 1, nil)
require.NoError(t, err) require.NoError(t, err)
// This call to ValidateRoot will succeed since we are using a valid PEM // This call to ValidateRoot will succeed since we are using a valid PEM
// encoded certificate, and have no other certificates for this CN // encoded certificate, and have no other certificates for this CN
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{}) _, err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
require.Error(t, err, "insuficient signatures on root") require.Error(t, err, "insuficient signatures on root")
// Finally, validate the only trusted certificate that exists is still
// the old one
certificates = certStore.GetCertificates()
require.Len(t, certificates, 1)
require.Equal(t, certificates[0], origRootCert)
} }
func generateTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) { func generateTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) {
@ -889,3 +784,12 @@ func generateExpiredTestingCertificate(rootKey data.PrivateKey, gun string) (*x5
startTime := time.Now().AddDate(-10, 0, 0) startTime := time.Now().AddDate(-10, 0, 0)
return cryptoservice.GenerateCertificate(rootKey, gun, startTime, startTime.AddDate(1, 0, 0)) return cryptoservice.GenerateCertificate(rootKey, gun, startTime, startTime.AddDate(1, 0, 0))
} }
// Helper function for explicitly generating key IDs and unexported fields for equality testing
func generateRootKeyIDs(r *data.SignedRoot) {
for _, keyID := range r.Signed.Roles[data.CanonicalRootRole].KeyIDs {
if k, ok := r.Signed.Keys[keyID]; ok {
_ = k.ID()
}
}
}

View File

@ -3,6 +3,7 @@ package trustpinning
import ( import (
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"github.com/Sirupsen/logrus"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/utils" "github.com/docker/notary/tuf/utils"
"strings" "strings"
@ -67,17 +68,12 @@ func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker,
func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool { func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...}, // reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
// in order to get the matching id in the root file // in order to get the matching id in the root file
leafCertID, err := trustmanager.FingerprintCert(leafCert) key, err := trustmanager.CertBundleToKey(leafCert, intCerts)
if err != nil { if err != nil {
logrus.Debug("error creating cert bundle: ", err.Error())
return false return false
} }
rootKeys := trustmanager.CertsToKeys([]*x509.Certificate{leafCert}, map[string][]*x509.Certificate{leafCertID: intCerts}) return utils.StrSliceContains(t.pinnedCertIDs, key.ID())
for keyID := range rootKeys {
if utils.StrSliceContains(t.pinnedCertIDs, keyID) {
return true
}
}
return false
} }
func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool { func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {

View File

@ -1,6 +1,7 @@
package testutils package testutils
import ( import (
"fmt"
"math/rand" "math/rand"
"sort" "sort"
"time" "time"
@ -19,8 +20,8 @@ import (
// CreateKey creates a new key inside the cryptoservice for the given role and gun, // CreateKey creates a new key inside the cryptoservice for the given role and gun,
// returning the public key. If the role is a root role, create an x509 key. // returning the public key. If the role is a root role, create an x509 key.
func CreateKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error) { func CreateKey(cs signed.CryptoService, gun, role, keyAlgorithm string) (data.PublicKey, error) {
key, err := cs.Create(role, gun, data.ECDSAKey) key, err := cs.Create(role, gun, keyAlgorithm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -36,7 +37,17 @@ func CreateKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
key = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert)) // Keep the x509 key type consistent with the key's algorithm
switch keyAlgorithm {
case data.RSAKey:
key = data.NewRSAx509PublicKey(trustmanager.CertToPEM(cert))
case data.ECDSAKey:
key = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert))
default:
// This should be impossible because of the Create() call above, but just in case
return nil, fmt.Errorf("invalid key algorithm type")
}
} }
return key, nil return key, nil
} }
@ -50,7 +61,7 @@ func EmptyRepo(gun string, delegationRoles ...string) (*tuf.Repo, signed.CryptoS
baseRoles := map[string]data.BaseRole{} baseRoles := map[string]data.BaseRole{}
for _, role := range data.BaseRoles { for _, role := range data.BaseRoles {
key, err := CreateKey(cs, gun, role) key, err := CreateKey(cs, gun, role, data.ECDSAKey)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -77,7 +88,7 @@ func EmptyRepo(gun string, delegationRoles ...string) (*tuf.Repo, signed.CryptoS
sort.Strings(delegationRoles) sort.Strings(delegationRoles)
for _, delgName := range delegationRoles { for _, delgName := range delegationRoles {
// create a delegations key and a delegation in the tuf repo // create a delegations key and a delegation in the tuf repo
delgKey, err := CreateKey(cs, gun, delgName) delgKey, err := CreateKey(cs, gun, delgName, data.ECDSAKey)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -269,7 +269,7 @@ func (m *MetadataSwizzler) SignMetadataWithInvalidKey(role string) error {
// create an invalid key, but not in the existing CryptoService // create an invalid key, but not in the existing CryptoService
cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever(""))) cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("")))
key, err := CreateKey(cs, m.Gun, role) key, err := CreateKey(cs, m.Gun, role, data.ECDSAKey)
if err != nil { if err != nil {
return err return err
} }
@ -466,7 +466,7 @@ func (m *MetadataSwizzler) RotateKey(role string, key data.PublicKey) error {
// ChangeRootKey swaps out the root key with a new key, and re-signs the metadata // ChangeRootKey swaps out the root key with a new key, and re-signs the metadata
// with the new key // with the new key
func (m *MetadataSwizzler) ChangeRootKey() error { func (m *MetadataSwizzler) ChangeRootKey() error {
key, err := CreateKey(m.CryptoService, m.Gun, data.CanonicalRootRole) key, err := CreateKey(m.CryptoService, m.Gun, data.CanonicalRootRole, data.ECDSAKey)
if err != nil { if err != nil {
return err return err
} }