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 {
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

View File

@ -2,7 +2,6 @@ package client
import (
"bytes"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
@ -87,7 +86,6 @@ type NotaryRepository struct {
CryptoService signed.CryptoService
tufRepo *tuf.Repo
roundTrip http.RoundTripper
CertStore trustmanager.X509Store
trustPinning trustpinning.TrustPinConfig
}
@ -97,15 +95,6 @@ type NotaryRepository struct {
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
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...)
nRepo := &NotaryRepository{
@ -115,7 +104,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
CryptoService: cryptoService,
roundTrip: rt,
CertStore: certStore,
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
}
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.
startTime := time.Now()
cert, err := cryptoservice.GenerateCertificate(
privKey, gun, startTime, startTime.Add(notary.Year*10))
if err != nil {
return nil, nil, err
return nil, err
}
x509PublicKey := trustmanager.CertToKey(cert)
if x509PublicKey == nil {
return nil, nil, fmt.Errorf(
return nil, fmt.Errorf(
"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
@ -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 {
return err
}
r.CertStore.AddCert(rootCert)
var (
rootRole = data.NewBaseRole(
@ -790,9 +777,6 @@ func (r *NotaryRepository) Update(forWrite bool) error {
// Partially populates r.tufRepo with this root metadata (only; use
// 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
// (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)
@ -812,7 +796,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
if cachedRootErr == nil {
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
signedRoot, cachedRootErr = r.validateRoot(rootJSON, false)
}
remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
@ -833,7 +817,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
if cachedRootErr != nil {
// we always want to use the downloaded root if there was a cache
// error.
signedRoot, err = r.validateRoot(tmpJSON)
signedRoot, err = r.validateRoot(tmpJSON, true)
if err != nil {
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.
// 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.
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
// needs the root.Signed field to still be []byte for signature
// validation
@ -878,12 +862,26 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
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
// to ensure that we consider old roots when validating this new one
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, err
return nil, &trustpinning.ErrValidationFail{fmt.Sprintf("unable to unmarshal previously trusted root from disk: %v", err)}
}
return data.RootFromSigned(root)
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)
}
// 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 {
return err
}
_, pubKey, err = rootCertKey(r.gun, privKey)
pubKey, err = rootCertKey(r.gun, privKey)
if err != nil {
return err
}
@ -964,26 +962,12 @@ func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, act
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 {
// Clear TUF files and cache
if err := r.fileStore.RemoveAll(); err != nil {
return fmt.Errorf("error clearing TUF repo data: %v", err)
}
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
}

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")
}
// 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
// role, the JSON is well-formed, and the signatures exist.
// 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)
requireRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot)
requireRepoHasExpectedCerts(t, repo)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole,
@ -1932,17 +1911,15 @@ func TestPublishRootCorrupt(t *testing.T) {
defer os.RemoveAll(repo.baseDir)
testPublishBadMetadata(t, data.CanonicalRootRole, repo, false, false)
// publish first - publish should still succeed if root corrupt since the
// remote root is signed with the same key.
// publish first - publish should still fail if the local root is corrupt since
// we can't determine whether remote root is signed with the same key.
repo, _ = initializeRepo(t, data.ECDSAKey, "docker.com/notary2", ts.URL, false)
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
// before, if the metadata is corrupt, it can't be published. If it has been
// published already, then the corrupt metadata can just be re-downloaded, so
// publishing is successful.
// before, if the metadata is corrupt, it can't be published.
func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepository,
publishFirst, succeeds bool) {
@ -1959,8 +1936,12 @@ func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepositor
require.NoError(t, err)
} else {
require.Error(t, 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
path := fmt.Sprintf("%s.%s",
@ -1971,7 +1952,7 @@ func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepositor
defer os.RemoveAll(path)
err = repo.Publish()
if succeeds {
if succeeds || publishFirst {
require.NoError(t, err)
} else {
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 {
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
@ -2774,15 +2747,6 @@ func rootRoleCertID(t *testing.T, repo *NotaryRepository) string {
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) {
ts := fullTestServer(t)
defer ts.Close()
@ -2825,27 +2789,12 @@ func TestRotateRootKey(t *testing.T) {
require.Error(t, err)
addTarget(t, authorRepo, "current", "../fixtures/intermediate-ca.crt")
// NotaryRepository.Update's handling of certificate rotation is weird:
//
// On every run, NotaryRepository.bootstrapClient rotates the trusted certificates
// based on CACHED root data.
// Then the client calls Repo.Update, which fetches a new timestmap,
// notices an updated root.json, validates it and stores it into the cache.
//
// So, the locally trusted certificates are rotated only on the SECOND call of
// NotaryRepository.Update after the rotation is pushed to the server.
//
// This would be nice to fix eventually (breaking down the NotaryRepository.Update
// / Repo.Update separation which causes this), but for now, just ensure that the
// second update does result in updated certificates.
// Publish the target, which does an update and pulls down the latest metadata, and
// should update the cert store now
// should update the trusted root
logRepoTrustRoot(t, "pre-publish", authorRepo)
err = authorRepo.Publish()
require.NoError(t, err)
logRepoTrustRoot(t, "post-publish", authorRepo)
verifyOnlyTrustedCertificate(t, authorRepo, newRootCertID)
// Verify the user can use the rotated repo, and see the added target.
_, err = userRepo.GetTargetByName("current")
@ -2859,7 +2808,6 @@ func TestRotateRootKey(t *testing.T) {
_, err = freshUserRepo.GetTargetByName("current")
require.NoError(t, err)
require.Equal(t, newRootCertID, rootRoleCertID(t, freshUserRepo))
verifyOnlyTrustedCertificate(t, freshUserRepo, newRootCertID)
logRepoTrustRoot(t, "fresh client", freshUserRepo)
// 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
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)
@ -3322,12 +3269,6 @@ func TestDeleteRepo(t *testing.T) {
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)
}
@ -3352,7 +3293,6 @@ func TestDeleteRepoBadFilestore(t *testing.T) {
// 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)
@ -3365,50 +3305,6 @@ func TestDeleteRepoBadFilestore(t *testing.T) {
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
func TestListRoles(t *testing.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)
}
// If a repo has corrupt metadata (in that the hash doesn't match the snapshot) or
// missing metadata, an update will replace all of it
// If a repo has 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) {
if testing.Short() {
t.Skip("skipping test in short mode")
@ -230,12 +231,22 @@ func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
repoSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs)
repoSwizzler.MetadataCache = repo.fileStore
origMeta := testutils.CopyRepoMetadata(serverMeta)
for _, role := range repoSwizzler.Roles {
for _, expt := range waysToMessUpLocalMetadata {
text, messItUp := expt.desc, expt.swizzle
for _, forWrite := range []bool{true, false} {
require.NoError(t, messItUp(repoSwizzler, role), "could not fuzz %s (%s)", role, text)
err := repo.Update(forWrite)
// if this is a root role, we should error if it's corrupted data
if role == data.CanonicalRootRole && expt.desc == "invalid JSON" {
require.Error(t, err)
// revert our original metadata
for role := range origMeta {
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)
@ -247,6 +258,7 @@ func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
}
}
}
}
// If a repo has an invalid root (signed by wrong key, expired, invalid version,
// invalid number of signatures, etc.), the repo will just get the new root from
@ -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
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 {
require.NoError(t, err)
@ -312,7 +328,7 @@ func TestUpdateFailsIfServerRootKeyChangedWithoutMultiSign(t *testing.T) {
expected = messedUpMeta
}
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
// 404s and 503s
var waysToMessUpServer = []swizzleExpectations{
{desc: "invalid JSON", expectErrs: []interface{}{&json.SyntaxError{}},
{desc: "invalid JSON", expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.SyntaxError{}},
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},
{desc: "an invalid SignedMeta",
// 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},
// for the errors below, when we bootstrap root, we get cert.ErrValidationFail failures
@ -789,11 +805,11 @@ var waysToMessUpServer = []swizzleExpectations{
swizzle: (*testutils.MetadataSwizzler).SetInvalidMetadataType},
{desc: "invalid signatures", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}},
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
swizzle: (*testutils.MetadataSwizzler).InvalidateMetadataSignatures},
{desc: "meta signed by wrong key", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}},
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
swizzle: (*testutils.MetadataSwizzler).SignMetadataWithInvalidKey},
{desc: "expired metadata", expectErrs: []interface{}{
@ -1197,12 +1213,6 @@ func TestUpdateLocalAndRemoteRootCorrupt(t *testing.T) {
}
for _, localExpt := range waysToMessUpLocalMetadata {
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, false, localExpt, serverExpt)
}
@ -1364,7 +1374,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
keyIDs := make([]string, len(threeKeys))
for i := 0; i < len(threeKeys); i++ {
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)
keyIDs[i] = threeKeys[i].ID()
signedRoot.Signed.Keys[keyIDs[i]] = threeKeys[i]
@ -1382,7 +1392,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
// --- threshold back to 1
replacementKey, err := testutils.CreateKey(
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole)
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole, data.ECDSAKey)
require.NoError(t, err)
signedRoot.Signed.Version++
signedRoot.Signed.Keys[replacementKey.ID()] = replacementKey
@ -1406,7 +1416,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
// --- latest root role)
signedRoot.Signed.Version++
snapKey, err := testutils.CreateKey(
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalSnapshotRole)
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalSnapshotRole, data.ECDSAKey)
require.NoError(t, err)
signedRoot.Signed.Keys[snapKey.ID()] = snapKey
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
func TestDefaultRootKeyGeneration(t *testing.T) {
// -- setup --

View File

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

View File

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

View File

@ -1,14 +1,11 @@
package main
import (
"crypto/x509"
"encoding/hex"
"fmt"
"io"
"math"
"sort"
"strings"
"time"
"github.com/docker/notary/client"
"github.com/docker/notary/trustmanager"
@ -204,52 +201,3 @@ func prettyPrintPaths(paths []string) string {
}
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 (
"bytes"
"crypto/rand"
"crypto/x509"
"encoding/hex"
"fmt"
"io/ioutil"
@ -11,10 +10,8 @@ import (
"sort"
"strings"
"testing"
"time"
"github.com/docker/notary/client"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"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 ---
// 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)
}
}
// 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/ioutil"
"math/big"
"net/http"
"net/url"
"time"
"github.com/Sirupsen/logrus"
@ -24,40 +22,6 @@ import (
"github.com/docker/notary/tuf/data"
)
// GetCertFromURL tries to get a X509 certificate given a HTTPS URL
func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
url, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
// Check if we are adding via HTTPS
if url.Scheme != "https" {
return nil, errors.New("only HTTPS URLs allowed")
}
// Download the certificate and write to directory
resp, err := http.Get(url.String())
if err != nil {
return nil, err
}
// Copy the content to certBytes
defer resp.Body.Close()
certBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Try to extract the first valid PEM certificate from the bytes
cert, err := LoadCertFromPEM(certBytes)
if err != nil {
return nil, err
}
return cert, nil
}
// CertToPEM is a utility function returns a PEM encoded x509 Certificate
func CertToPEM(cert *x509.Certificate) []byte {
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")
}
// 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
// data is expected to be PEM Encoded and contain one of more certificates
// with PEM type "CERTIFICATE"
@ -533,20 +443,24 @@ func CertToKey(cert *x509.Certificate) data.PublicKey {
// CertsToKeys transforms each of the input certificate chains into its corresponding
// 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)
for _, leafCert := range leafCerts {
for id, leafCert := range leafCerts {
if key, err := CertBundleToKey(leafCert, intCerts[id]); err == nil {
keys[key.ID()] = key
}
}
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}
certID, err := FingerprintCert(leafCert)
if err != nil {
continue
}
if intCertsForLeaves, ok := intCerts[certID]; ok {
certBundle = append(certBundle, intCertsForLeaves...)
}
certBundle = append(certBundle, intCerts...)
certChainPEM, err := CertChainToPEM(certBundle)
if err != nil {
continue
return nil, err
}
var newKey data.PublicKey
// Use the leaf cert's public key algorithm for typing
@ -557,11 +471,9 @@ func CertsToKeys(leafCerts []*x509.Certificate, intCerts map[string][]*x509.Cert
newKey = data.NewECDSAx509PublicKey(certChainPEM)
default:
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
continue
return nil, x509.ErrUnsupportedAlgorithm
}
keys[newKey.ID()] = newKey
}
return keys
return newKey, nil
}
// NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval.
@ -610,14 +522,3 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
return key.ID(), nil
}
// FilterCertsExpiredSha1 can be used as the filter function to cert store
// initializers to filter out all expired or SHA-1 certificate that we
// shouldn't load.
func FilterCertsExpiredSha1(cert *x509.Certificate) bool {
return !cert.IsCA &&
time.Now().Before(cert.NotAfter) &&
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
}

View File

@ -28,10 +28,19 @@ func TestCertsToKeys(t *testing.T) {
require.NoError(t, err)
// 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
keys := CertsToKeys(certList, make(map[string][]*x509.Certificate))
keys := CertsToKeys(certMap, make(map[string][]*x509.Certificate))
require.NotNil(t, keys)
require.Len(t, keys, 3)
@ -49,7 +58,7 @@ func TestCertsToKeys(t *testing.T) {
emptyCert := x509.Certificate{}
// Also try changing the pre-existing leaf cert into an invalid algorithm
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)
}

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)
}
func prettyFormatCertIDs(certs []*x509.Certificate) string {
func prettyFormatCertIDs(certs map[string]*x509.Certificate) string {
ids := make([]string, 0, len(certs))
for _, cert := range certs {
id, err := trustmanager.FingerprintCert(cert)
if err != nil {
id = fmt.Sprintf("[Error %s]", err)
}
for id := range certs {
ids = append(ids, id)
}
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
do root key rotation if needed.
First we list the current trusted certificates we have for a particular GUN. If
that list is non-empty means that we've already seen this repository before, and
First we check if we have any trusted certificates for a particular GUN in
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
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.
*/
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)
signedRoot, err := data.RootFromSigned(root)
if err != nil {
return err
return nil, err
}
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
if err != nil {
return err
return nil, err
}
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun)
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
if err != nil {
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
trustedCerts, err := certStore.GetCertificatesByCN(gun)
if err != nil {
// If the error that we get back is different than ErrNoCertificatesFound
// we couldn't check if there are any certificates with this CN already
// trusted. Let's take the conservative approach and return a failed validation
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err)
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
// If we have a previous root, let's try to use it to validate that this new root is valid.
if prevRoot != nil {
// Retrieve all the trusted certificates from our previous root
// Note that we do not validate expiries here since our originally trusted root might have expired certs
allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
// Use the certificates we found in the previous root for the GUN to verify its signatures
// 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(
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1})
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold})
if err != nil {
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 {
logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
if err != nil {
return &ErrValidationFail{Reason: err.Error()}
return nil, &ErrValidationFail{Reason: err.Error()}
}
validPinnedCerts := []*x509.Certificate{}
for _, cert := range certsFromRoot {
certID, err := trustmanager.FingerprintCert(cert)
if err != nil {
validPinnedCerts := map[string]*x509.Certificate{}
for id, cert := range certsFromRoot {
if ok := trustPinCheckFunc(cert, allIntCerts[id]); !ok {
continue
}
if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok {
continue
}
validPinnedCerts = append(validPinnedCerts, cert)
validPinnedCerts[id] = cert
}
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
}
@ -159,64 +154,29 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
if err != nil {
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
return &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"}
}
return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"}
}
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
// "validity" alone does not imply any measure of trust.
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) ([]*x509.Certificate, error) {
var validLeafCerts []*x509.Certificate
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, checkExpiry bool) (map[string]*x509.Certificate, error) {
validLeafCerts := make(map[string]*x509.Certificate)
// 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
if cert.Subject.CommonName != gun {
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
cert.Subject.CommonName, gun)
continue
}
// Make sure the certificate is not expired
if time.Now().After(cert.NotAfter) {
// Make sure the certificate is not expired if checkExpiry is true
if checkExpiry && time.Now().After(cert.NotAfter) {
logrus.Debugf("error leaf certificate is expired")
continue
}
@ -230,7 +190,7 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
continue
}
validLeafCerts = append(validLeafCerts, cert)
validLeafCerts[id] = cert
}
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
// with all the intermediate certificates found in signedRoot
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)
intCerts := make(map[string][]*x509.Certificate)
// 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 {
logrus.Debugf("tried to parse certificates from invalid root signed data")
return nil, nil
@ -290,59 +254,14 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
// Get the ID of the leaf certificate
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
leafCerts[leafID] = leafCert
leafCerts[key.ID()] = leafCert
// Get all the remainder certificates marked as a CA to be used as intermediates
intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
intCerts[leafID] = intermediateCerts
intCerts[key.ID()] = intermediateCerts
}
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"
"github.com/docker/notary"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/testutils"
"github.com/stretchr/testify/require"
)
@ -44,86 +44,6 @@ const rootPubKeyID = "49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b
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) {
var testSignedRoot data.Signed
var signedRootBytes bytes.Buffer
@ -133,14 +53,6 @@ func TestValidateRoot(t *testing.T) {
defer os.RemoveAll(tempBaseDir)
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
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
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
// 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)
// This call to ValidateRoot will fail since we are passing in a dnsName that
// 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.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
@ -169,7 +81,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot
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")
//
@ -182,7 +94,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot
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.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
@ -197,7 +109,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot
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.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
@ -216,7 +128,7 @@ func TestValidateRoot(t *testing.T) {
// Unmarshal our signedroot
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.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
}
@ -230,14 +142,6 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
defer os.RemoveAll(tempBaseDir)
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
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
@ -246,7 +150,7 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
// 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)
}
@ -259,31 +163,25 @@ func TestValidateRootWithPinnedCert(t *testing.T) {
defer os.RemoveAll(tempBaseDir)
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
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
// Unmarshal our signedroot
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)
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)
// purge the cert store to check another valid certs trust pinning
certStore.RemoveAll()
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, validatedSignedRoot, typedSignedRoot)
// 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.Equal(t, validatedSignedRoot, typedSignedRoot)
}
func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
@ -436,19 +334,15 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil)
require.NoError(t, err)
typedSignedRoot, err := data.RootFromSigned(signedRoot)
require.NoError(t, err)
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir)
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(
certStore,
validatedRoot, err := ValidateRoot(
nil,
signedRoot,
"docker.io/notary/test",
TrustPinConfig{
@ -459,6 +353,8 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
},
)
require.NoError(t, err, "failed to validate certID with intermediate")
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
}
func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
@ -470,40 +366,36 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
defer os.RemoveAll(tempBaseDir)
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
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
// Unmarshal our signedroot
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
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)
// 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)
// 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)
// 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)
// 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)
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
}
func TestValidateRootWithPinnedCA(t *testing.T) {
@ -515,44 +407,39 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
defer os.RemoveAll(tempBaseDir)
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.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
// Unmarshal our signedRoot
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
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)
// 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)
// 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, 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
invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca")
require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644))
// 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)
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
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)
// 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)
require.NoError(t, err)
// 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": validCAFilepath}, DisableTOFU: true})
newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot)
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
certStore.RemoveAll()
goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath)
require.NoError(t, err)
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
@ -623,10 +514,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644))
// 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.Equal(t, newTypedSignedRoot, validatedRoot)
certStore.RemoveAll()
testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
require.NoError(t, err)
testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID())
@ -638,11 +529,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem")
require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644))
// 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)
// 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)
require.NoError(t, err)
testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID())
@ -653,7 +543,7 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
require.NoError(t, err)
bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
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)
}
@ -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) {
// The gun to test
gun := "docker.com/notary"
tempBaseDir, certStore, cs, certificates := filestoreWithTwoCerts(t, gun, keyAlg)
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)
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
cs := cryptoservice.NewCryptoService(memKeyStore)
// Tuf key with PEM-encoded x509 certificate
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
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)
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)
require.NoError(t, err)
// This call to ValidateRoot will succeed since we are using a valid PEM
// encoded certificate, and have no other certificates for this CN
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
typedSignedRoot, err := data.RootFromSigned(signedTestRoot)
require.NoError(t, err)
// Finally, validate the only trusted certificate that exists is the new one
certificates = certStore.GetCertificates()
require.Len(t, certificates, 1)
require.Equal(t, certificates[0], replRootCert)
// This call to ValidateRoot will succeed since we are using a valid PEM
// encoded certificate, and have no other certificates for this CN
validatedRoot, err := ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
require.NoError(t, err)
generateRootKeyIDs(typedSignedRoot)
require.Equal(t, typedSignedRoot, validatedRoot)
}
// 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) {
gun := "docker.com/notary"
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts(
t, gun, keyAlg)
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)
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
cs := cryptoservice.NewCryptoService(memKeyStore)
// 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)
require.NoError(t, err)
@ -801,19 +691,13 @@ func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType st
require.NoError(t, err)
// 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)
// This call to ValidateRoot will succeed since we are using a valid PEM
// 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")
// 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
@ -829,22 +713,39 @@ func TestValidateRootRotationMissingNewSig(t *testing.T) {
func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) {
gun := "docker.com/notary"
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts(
t, gun, keyAlg)
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)
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
cs := cryptoservice.NewCryptoService(memKeyStore)
// Tuf key with PEM-encoded x509 certificate
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
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)
require.NoError(t, err)
@ -865,19 +766,13 @@ func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType str
require.NoError(t, err)
// 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)
// This call to ValidateRoot will succeed since we are using a valid PEM
// 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")
// 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) {
@ -889,3 +784,12 @@ func generateExpiredTestingCertificate(rootKey data.PrivateKey, gun string) (*x5
startTime := time.Now().AddDate(-10, 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 (
"crypto/x509"
"fmt"
"github.com/Sirupsen/logrus"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/utils"
"strings"
@ -67,17 +68,12 @@ func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker,
func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
// 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 {
logrus.Debug("error creating cert bundle: ", err.Error())
return false
}
rootKeys := trustmanager.CertsToKeys([]*x509.Certificate{leafCert}, map[string][]*x509.Certificate{leafCertID: intCerts})
for keyID := range rootKeys {
if utils.StrSliceContains(t.pinnedCertIDs, keyID) {
return true
}
}
return false
return utils.StrSliceContains(t.pinnedCertIDs, key.ID())
}
func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {

View File

@ -1,6 +1,7 @@
package testutils
import (
"fmt"
"math/rand"
"sort"
"time"
@ -19,8 +20,8 @@ import (
// 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.
func CreateKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error) {
key, err := cs.Create(role, gun, data.ECDSAKey)
func CreateKey(cs signed.CryptoService, gun, role, keyAlgorithm string) (data.PublicKey, error) {
key, err := cs.Create(role, gun, keyAlgorithm)
if err != nil {
return nil, err
}
@ -36,7 +37,17 @@ func CreateKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error
if err != nil {
return nil, err
}
// 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
}
@ -50,7 +61,7 @@ func EmptyRepo(gun string, delegationRoles ...string) (*tuf.Repo, signed.CryptoS
baseRoles := map[string]data.BaseRole{}
for _, role := range data.BaseRoles {
key, err := CreateKey(cs, gun, role)
key, err := CreateKey(cs, gun, role, data.ECDSAKey)
if err != nil {
return nil, nil, err
}
@ -77,7 +88,7 @@ func EmptyRepo(gun string, delegationRoles ...string) (*tuf.Repo, signed.CryptoS
sort.Strings(delegationRoles)
for _, delgName := range delegationRoles {
// 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 {
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
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 {
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
// with the new key
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 {
return err
}