mirror of https://github.com/docker/docs.git
commit
2874955337
|
@ -29,10 +29,6 @@ func requireValidFixture(t *testing.T, notaryRepo *NotaryRepository) {
|
||||||
for _, targetObj := range notaryRepo.tufRepo.Targets {
|
for _, targetObj := range notaryRepo.tufRepo.Targets {
|
||||||
require.True(t, targetObj.Signed.Expires.After(tenYearsInFuture))
|
require.True(t, targetObj.Signed.Expires.After(tenYearsInFuture))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cert := range notaryRepo.CertStore.GetCertificates() {
|
|
||||||
require.True(t, cert.NotAfter.After(tenYearsInFuture))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursively copies the contents of one directory into another - ignores
|
// recursively copies the contents of one directory into another - ignores
|
||||||
|
|
|
@ -2,7 +2,6 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -87,7 +86,6 @@ type NotaryRepository struct {
|
||||||
CryptoService signed.CryptoService
|
CryptoService signed.CryptoService
|
||||||
tufRepo *tuf.Repo
|
tufRepo *tuf.Repo
|
||||||
roundTrip http.RoundTripper
|
roundTrip http.RoundTripper
|
||||||
CertStore trustmanager.X509Store
|
|
||||||
trustPinning trustpinning.TrustPinConfig
|
trustPinning trustpinning.TrustPinConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,15 +95,6 @@ type NotaryRepository struct {
|
||||||
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||||
keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) {
|
keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) {
|
||||||
|
|
||||||
certPath := filepath.Join(baseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
certPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoService := cryptoservice.NewCryptoService(keyStores...)
|
cryptoService := cryptoservice.NewCryptoService(keyStores...)
|
||||||
|
|
||||||
nRepo := &NotaryRepository{
|
nRepo := &NotaryRepository{
|
||||||
|
@ -115,7 +104,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||||
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
|
tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)),
|
||||||
CryptoService: cryptoService,
|
CryptoService: cryptoService,
|
||||||
roundTrip: rt,
|
roundTrip: rt,
|
||||||
CertStore: certStore,
|
|
||||||
trustPinning: trustPin,
|
trustPinning: trustPin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,22 +150,22 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
|
||||||
return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
|
return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootCertKey(gun string, privKey data.PrivateKey) (*x509.Certificate, data.PublicKey, error) {
|
func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) {
|
||||||
// Hard-coded policy: the generated certificate expires in 10 years.
|
// Hard-coded policy: the generated certificate expires in 10 years.
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
cert, err := cryptoservice.GenerateCertificate(
|
cert, err := cryptoservice.GenerateCertificate(
|
||||||
privKey, gun, startTime, startTime.Add(notary.Year*10))
|
privKey, gun, startTime, startTime.Add(notary.Year*10))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
x509PublicKey := trustmanager.CertToKey(cert)
|
x509PublicKey := trustmanager.CertToKey(cert)
|
||||||
if x509PublicKey == nil {
|
if x509PublicKey == nil {
|
||||||
return nil, nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
|
"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cert, x509PublicKey, nil
|
return x509PublicKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize creates a new repository by using rootKey as the root Key for the
|
// Initialize creates a new repository by using rootKey as the root Key for the
|
||||||
|
@ -218,11 +206,10 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCert, rootKey, err := rootCertKey(r.gun, privKey)
|
rootKey, err := rootCertKey(r.gun, privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.CertStore.AddCert(rootCert)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rootRole = data.NewBaseRole(
|
rootRole = data.NewBaseRole(
|
||||||
|
@ -790,9 +777,6 @@ func (r *NotaryRepository) Update(forWrite bool) error {
|
||||||
// Partially populates r.tufRepo with this root metadata (only; use
|
// Partially populates r.tufRepo with this root metadata (only; use
|
||||||
// tufclient.Client.Update to load the rest).
|
// tufclient.Client.Update to load the rest).
|
||||||
//
|
//
|
||||||
// As another side effect, r.CertManager's list of trusted certificates
|
|
||||||
// is updated with data from the loaded root.json.
|
|
||||||
//
|
|
||||||
// Fails if the remote server is reachable and does not know the repo
|
// Fails if the remote server is reachable and does not know the repo
|
||||||
// (i.e. before the first r.Publish()), in which case the error is
|
// (i.e. before the first r.Publish()), in which case the error is
|
||||||
// store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
|
// store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
|
||||||
|
@ -812,7 +796,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
||||||
|
|
||||||
if cachedRootErr == nil {
|
if cachedRootErr == nil {
|
||||||
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
|
signedRoot, cachedRootErr = r.validateRoot(rootJSON, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
|
remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
|
||||||
|
@ -833,7 +817,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
if cachedRootErr != nil {
|
if cachedRootErr != nil {
|
||||||
// we always want to use the downloaded root if there was a cache
|
// we always want to use the downloaded root if there was a cache
|
||||||
// error.
|
// error.
|
||||||
signedRoot, err = r.validateRoot(tmpJSON)
|
signedRoot, err = r.validateRoot(tmpJSON, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -868,7 +852,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
// signatures of the root based on known keys, not expiry or other metadata.
|
// signatures of the root based on known keys, not expiry or other metadata.
|
||||||
// This is so that an out of date root can be loaded to be used in a rotation
|
// This is so that an out of date root can be loaded to be used in a rotation
|
||||||
// should the TUF update process detect a problem.
|
// should the TUF update process detect a problem.
|
||||||
func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, error) {
|
func (r *NotaryRepository) validateRoot(rootJSON []byte, fromRemote bool) (*data.SignedRoot, error) {
|
||||||
// can't just unmarshal into SignedRoot because validate root
|
// can't just unmarshal into SignedRoot because validate root
|
||||||
// needs the root.Signed field to still be []byte for signature
|
// needs the root.Signed field to still be []byte for signature
|
||||||
// validation
|
// validation
|
||||||
|
@ -878,12 +862,26 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = trustpinning.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning)
|
// If we're downloading a root from a remote source, attempt to load a local root
|
||||||
if err != nil {
|
// to ensure that we consider old roots when validating this new one
|
||||||
return nil, err
|
var prevRoot *data.SignedRoot
|
||||||
|
if fromRemote {
|
||||||
|
prevRootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
||||||
|
// A previous root exists, so we attempt to use it
|
||||||
|
// If for some reason we can't extract it (ex: it's corrupted), we should error client-side to be conservative
|
||||||
|
if err == nil {
|
||||||
|
prevSignedRoot := &data.Signed{}
|
||||||
|
err = json.Unmarshal(prevRootJSON, prevSignedRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &trustpinning.ErrValidationFail{fmt.Sprintf("unable to unmarshal previously trusted root from disk: %v", err)}
|
||||||
|
}
|
||||||
|
prevRoot, err = data.RootFromSigned(prevSignedRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &trustpinning.ErrValidationFail{fmt.Sprintf("error loading previously trusted root into valid role format: %v", err)}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return trustpinning.ValidateRoot(prevRoot, root, r.gun, r.trustPinning)
|
||||||
return data.RootFromSigned(root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RotateKey removes all existing keys associated with the role, and either
|
// RotateKey removes all existing keys associated with the role, and either
|
||||||
|
@ -929,7 +927,7 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, pubKey, err = rootCertKey(r.gun, privKey)
|
pubKey, err = rootCertKey(r.gun, privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -964,26 +962,12 @@ func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, act
|
||||||
return cl.Add(c)
|
return cl.Add(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side
|
// DeleteTrustData removes the trust data stored for this repo in the TUF cache on the client side
|
||||||
func (r *NotaryRepository) DeleteTrustData() error {
|
func (r *NotaryRepository) DeleteTrustData() error {
|
||||||
// Clear TUF files and cache
|
// Clear TUF files and cache
|
||||||
if err := r.fileStore.RemoveAll(); err != nil {
|
if err := r.fileStore.RemoveAll(); err != nil {
|
||||||
return fmt.Errorf("error clearing TUF repo data: %v", err)
|
return fmt.Errorf("error clearing TUF repo data: %v", err)
|
||||||
}
|
}
|
||||||
r.tufRepo = tuf.NewRepo(nil)
|
r.tufRepo = tuf.NewRepo(nil)
|
||||||
// Clear certificates
|
|
||||||
certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
|
|
||||||
if err != nil {
|
|
||||||
// If there were no certificates to delete, we're done
|
|
||||||
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("error retrieving certificates for %s: %v", r.gun, err)
|
|
||||||
}
|
|
||||||
for _, cert := range certificates {
|
|
||||||
if err := r.CertStore.RemoveCert(cert); err != nil {
|
|
||||||
return fmt.Errorf("error removing certificate: %v: %v", cert, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -425,26 +425,6 @@ func requireRepoHasExpectedKeys(t *testing.T, repo *NotaryRepository,
|
||||||
"there should be no timestamp key because the server manages it")
|
"there should be no timestamp key because the server manages it")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This creates a new certificate store in the repo's base directory and
|
|
||||||
// makes sure the repo has the right certificates
|
|
||||||
func requireRepoHasExpectedCerts(t *testing.T, repo *NotaryRepository) {
|
|
||||||
// The repo should have a certificate store and have created certs using
|
|
||||||
// it, so create a new store, and check that the certs do exist and
|
|
||||||
// are valid
|
|
||||||
trustPath := filepath.Join(repo.baseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
certificates := certStore.GetCertificates()
|
|
||||||
require.Len(t, certificates, 1, "unexpected number of trusted certificates")
|
|
||||||
|
|
||||||
certID, err := trustmanager.FingerprintCert(certificates[0])
|
|
||||||
require.NoError(t, err, "unable to fingerprint the trusted certificate")
|
|
||||||
require.NotEqual(t, certID, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check the TUF metadata files. Verify that it exists for a particular
|
// Sanity check the TUF metadata files. Verify that it exists for a particular
|
||||||
// role, the JSON is well-formed, and the signatures exist.
|
// role, the JSON is well-formed, and the signatures exist.
|
||||||
// For the root.json file, also check that the root, snapshot, and
|
// For the root.json file, also check that the root, snapshot, and
|
||||||
|
@ -515,7 +495,6 @@ func testInitRepoMetadata(t *testing.T, rootType string, serverManagesSnapshot b
|
||||||
defer os.RemoveAll(repo.baseDir)
|
defer os.RemoveAll(repo.baseDir)
|
||||||
|
|
||||||
requireRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot)
|
requireRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot)
|
||||||
requireRepoHasExpectedCerts(t, repo)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole,
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole,
|
||||||
|
@ -1932,17 +1911,15 @@ func TestPublishRootCorrupt(t *testing.T) {
|
||||||
defer os.RemoveAll(repo.baseDir)
|
defer os.RemoveAll(repo.baseDir)
|
||||||
testPublishBadMetadata(t, data.CanonicalRootRole, repo, false, false)
|
testPublishBadMetadata(t, data.CanonicalRootRole, repo, false, false)
|
||||||
|
|
||||||
// publish first - publish should still succeed if root corrupt since the
|
// publish first - publish should still fail if the local root is corrupt since
|
||||||
// remote root is signed with the same key.
|
// we can't determine whether remote root is signed with the same key.
|
||||||
repo, _ = initializeRepo(t, data.ECDSAKey, "docker.com/notary2", ts.URL, false)
|
repo, _ = initializeRepo(t, data.ECDSAKey, "docker.com/notary2", ts.URL, false)
|
||||||
defer os.RemoveAll(repo.baseDir)
|
defer os.RemoveAll(repo.baseDir)
|
||||||
testPublishBadMetadata(t, data.CanonicalRootRole, repo, true, true)
|
testPublishBadMetadata(t, data.CanonicalRootRole, repo, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When publishing snapshot, root, or target, if the repo hasn't been published
|
// When publishing snapshot, root, or target, if the repo hasn't been published
|
||||||
// before, if the metadata is corrupt, it can't be published. If it has been
|
// before, if the metadata is corrupt, it can't be published.
|
||||||
// published already, then the corrupt metadata can just be re-downloaded, so
|
|
||||||
// publishing is successful.
|
|
||||||
func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepository,
|
func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepository,
|
||||||
publishFirst, succeeds bool) {
|
publishFirst, succeeds bool) {
|
||||||
|
|
||||||
|
@ -1959,7 +1936,11 @@ func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepositor
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.IsType(t, ®Json.SyntaxError{}, err)
|
if roleName == data.CanonicalRootRole && publishFirst {
|
||||||
|
require.IsType(t, &trustpinning.ErrValidationFail{}, err)
|
||||||
|
} else {
|
||||||
|
require.IsType(t, ®Json.SyntaxError{}, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make an unreadable file by creating a directory instead of a file
|
// make an unreadable file by creating a directory instead of a file
|
||||||
|
@ -1971,7 +1952,7 @@ func testPublishBadMetadata(t *testing.T, roleName string, repo *NotaryRepositor
|
||||||
defer os.RemoveAll(path)
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
err = repo.Publish()
|
err = repo.Publish()
|
||||||
if succeeds {
|
if succeeds || publishFirst {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
@ -2757,14 +2738,6 @@ func logRepoTrustRoot(t *testing.T, prefix string, repo *NotaryRepository) {
|
||||||
for _, k := range root.Signed.Roles[data.CanonicalRootRole].KeyIDs {
|
for _, k := range root.Signed.Roles[data.CanonicalRootRole].KeyIDs {
|
||||||
logrus.Debugf("\t%s", k)
|
logrus.Debugf("\t%s", k)
|
||||||
}
|
}
|
||||||
logrus.Debugf("All trusted certs:")
|
|
||||||
certs, err := repo.CertStore.GetCertificatesByCN(repo.gun)
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, cert := range certs {
|
|
||||||
id, err := trustmanager.FingerprintCert(cert)
|
|
||||||
require.NoError(t, err)
|
|
||||||
logrus.Debugf("\t%s", id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID of the (only) certificate trusted by the root role metadata
|
// ID of the (only) certificate trusted by the root role metadata
|
||||||
|
@ -2774,15 +2747,6 @@ func rootRoleCertID(t *testing.T, repo *NotaryRepository) string {
|
||||||
return rootKeys[0]
|
return rootKeys[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyOnlyTrustedCertificate(t *testing.T, repo *NotaryRepository, certID string) {
|
|
||||||
certs, err := repo.CertStore.GetCertificatesByCN(repo.gun)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, certs, 1)
|
|
||||||
id, err := trustmanager.FingerprintCert(certs[0])
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, certID, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRotateRootKey(t *testing.T) {
|
func TestRotateRootKey(t *testing.T) {
|
||||||
ts := fullTestServer(t)
|
ts := fullTestServer(t)
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
@ -2825,27 +2789,12 @@ func TestRotateRootKey(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
addTarget(t, authorRepo, "current", "../fixtures/intermediate-ca.crt")
|
addTarget(t, authorRepo, "current", "../fixtures/intermediate-ca.crt")
|
||||||
|
|
||||||
// NotaryRepository.Update's handling of certificate rotation is weird:
|
|
||||||
//
|
|
||||||
// On every run, NotaryRepository.bootstrapClient rotates the trusted certificates
|
|
||||||
// based on CACHED root data.
|
|
||||||
// Then the client calls Repo.Update, which fetches a new timestmap,
|
|
||||||
// notices an updated root.json, validates it and stores it into the cache.
|
|
||||||
//
|
|
||||||
// So, the locally trusted certificates are rotated only on the SECOND call of
|
|
||||||
// NotaryRepository.Update after the rotation is pushed to the server.
|
|
||||||
//
|
|
||||||
// This would be nice to fix eventually (breaking down the NotaryRepository.Update
|
|
||||||
// / Repo.Update separation which causes this), but for now, just ensure that the
|
|
||||||
// second update does result in updated certificates.
|
|
||||||
|
|
||||||
// Publish the target, which does an update and pulls down the latest metadata, and
|
// Publish the target, which does an update and pulls down the latest metadata, and
|
||||||
// should update the cert store now
|
// should update the trusted root
|
||||||
logRepoTrustRoot(t, "pre-publish", authorRepo)
|
logRepoTrustRoot(t, "pre-publish", authorRepo)
|
||||||
err = authorRepo.Publish()
|
err = authorRepo.Publish()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
logRepoTrustRoot(t, "post-publish", authorRepo)
|
logRepoTrustRoot(t, "post-publish", authorRepo)
|
||||||
verifyOnlyTrustedCertificate(t, authorRepo, newRootCertID)
|
|
||||||
|
|
||||||
// Verify the user can use the rotated repo, and see the added target.
|
// Verify the user can use the rotated repo, and see the added target.
|
||||||
_, err = userRepo.GetTargetByName("current")
|
_, err = userRepo.GetTargetByName("current")
|
||||||
|
@ -2859,7 +2808,6 @@ func TestRotateRootKey(t *testing.T) {
|
||||||
_, err = freshUserRepo.GetTargetByName("current")
|
_, err = freshUserRepo.GetTargetByName("current")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, newRootCertID, rootRoleCertID(t, freshUserRepo))
|
require.Equal(t, newRootCertID, rootRoleCertID(t, freshUserRepo))
|
||||||
verifyOnlyTrustedCertificate(t, freshUserRepo, newRootCertID)
|
|
||||||
logRepoTrustRoot(t, "fresh client", freshUserRepo)
|
logRepoTrustRoot(t, "fresh client", freshUserRepo)
|
||||||
|
|
||||||
// Verify that the user initialized with the original certificate eventually
|
// Verify that the user initialized with the original certificate eventually
|
||||||
|
@ -3307,7 +3255,6 @@ func TestDeleteRepo(t *testing.T) {
|
||||||
|
|
||||||
// Assert initialization was successful before we delete
|
// Assert initialization was successful before we delete
|
||||||
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
||||||
requireRepoHasExpectedCerts(t, repo)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
|
||||||
|
@ -3322,12 +3269,6 @@ func TestDeleteRepo(t *testing.T) {
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
|
||||||
|
|
||||||
// Assert no certs for this repo exist locally
|
|
||||||
_, err = repo.CertStore.GetCertificatesByCN(gun)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.IsType(t, &trustmanager.ErrNoCertificatesFound{}, err)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
// Assert keys for this repo exist locally
|
// Assert keys for this repo exist locally
|
||||||
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
||||||
}
|
}
|
||||||
|
@ -3352,7 +3293,6 @@ func TestDeleteRepoBadFilestore(t *testing.T) {
|
||||||
|
|
||||||
// Assert initialization was successful before we delete
|
// Assert initialization was successful before we delete
|
||||||
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
||||||
requireRepoHasExpectedCerts(t, repo)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
|
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
|
||||||
|
@ -3365,50 +3305,6 @@ func TestDeleteRepoBadFilestore(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDeleteRepoNoCerts tests that local repo data is deleted successfully without an error even when we do not have certificates
|
|
||||||
func TestDeleteRepoNoCerts(t *testing.T) {
|
|
||||||
gun := "docker.com/notary"
|
|
||||||
|
|
||||||
ts, _, _ := simpleTestServer(t)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
repo, rootKeyID := initializeRepo(t, data.ECDSAKey, gun, ts.URL, false)
|
|
||||||
defer os.RemoveAll(repo.baseDir)
|
|
||||||
|
|
||||||
// Assert initialization was successful before we delete
|
|
||||||
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
|
||||||
requireRepoHasExpectedCerts(t, repo)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
|
|
||||||
|
|
||||||
// Delete the certificate store contents and require it has been fully deleted
|
|
||||||
repo.CertStore.RemoveAll()
|
|
||||||
_, err := repo.CertStore.GetCertificatesByCN(gun)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.IsType(t, &trustmanager.ErrNoCertificatesFound{}, err)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
// Delete all client trust data for repo
|
|
||||||
err = repo.DeleteTrustData()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Assert no metadata for this repo exists locally
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, false)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, false)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
|
|
||||||
requireRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
|
|
||||||
|
|
||||||
// Assert no certs for this repo exist locally
|
|
||||||
_, err = repo.CertStore.GetCertificatesByCN(gun)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.IsType(t, &trustmanager.ErrNoCertificatesFound{}, err)
|
|
||||||
require.NotNil(t, err)
|
|
||||||
|
|
||||||
// Assert keys for this repo exist locally
|
|
||||||
requireRepoHasExpectedKeys(t, repo, rootKeyID, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that we get a correct list of roles with keys and signatures
|
// Test that we get a correct list of roles with keys and signatures
|
||||||
func TestListRoles(t *testing.T) {
|
func TestListRoles(t *testing.T) {
|
||||||
ts := fullTestServer(t)
|
ts := fullTestServer(t)
|
||||||
|
|
|
@ -208,8 +208,9 @@ var waysToMessUpLocalMetadata = []swizzleExpectations{
|
||||||
// actively sabotage and break their own local repo (particularly the root.json)
|
// actively sabotage and break their own local repo (particularly the root.json)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a repo has corrupt metadata (in that the hash doesn't match the snapshot) or
|
// If a repo has missing metadata, an update will replace all of it
|
||||||
// missing metadata, an update will replace all of it
|
// If a repo has corrupt metadata for root, the update will fail
|
||||||
|
// For other roles, corrupt metadata will be replaced
|
||||||
func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
|
func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping test in short mode")
|
t.Skip("skipping test in short mode")
|
||||||
|
@ -230,18 +231,29 @@ func TestUpdateReplacesCorruptOrMissingMetadata(t *testing.T) {
|
||||||
repoSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs)
|
repoSwizzler := testutils.NewMetadataSwizzler("docker.com/notary", serverMeta, cs)
|
||||||
repoSwizzler.MetadataCache = repo.fileStore
|
repoSwizzler.MetadataCache = repo.fileStore
|
||||||
|
|
||||||
|
origMeta := testutils.CopyRepoMetadata(serverMeta)
|
||||||
|
|
||||||
for _, role := range repoSwizzler.Roles {
|
for _, role := range repoSwizzler.Roles {
|
||||||
for _, expt := range waysToMessUpLocalMetadata {
|
for _, expt := range waysToMessUpLocalMetadata {
|
||||||
text, messItUp := expt.desc, expt.swizzle
|
text, messItUp := expt.desc, expt.swizzle
|
||||||
for _, forWrite := range []bool{true, false} {
|
for _, forWrite := range []bool{true, false} {
|
||||||
require.NoError(t, messItUp(repoSwizzler, role), "could not fuzz %s (%s)", role, text)
|
require.NoError(t, messItUp(repoSwizzler, role), "could not fuzz %s (%s)", role, text)
|
||||||
err := repo.Update(forWrite)
|
err := repo.Update(forWrite)
|
||||||
require.NoError(t, err)
|
// if this is a root role, we should error if it's corrupted data
|
||||||
for r, expected := range serverMeta {
|
if role == data.CanonicalRootRole && expt.desc == "invalid JSON" {
|
||||||
actual, err := repo.fileStore.GetMeta(r, -1)
|
require.Error(t, err)
|
||||||
require.NoError(t, err, "problem getting repo metadata for %s", role)
|
// revert our original metadata
|
||||||
require.True(t, bytes.Equal(expected, actual),
|
for role := range origMeta {
|
||||||
"%s for %s: expected to recover after update", text, role)
|
require.NoError(t, repo.fileStore.SetMeta(role, origMeta[role]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
for r, expected := range serverMeta {
|
||||||
|
actual, err := repo.fileStore.GetMeta(r, -1)
|
||||||
|
require.NoError(t, err, "problem getting repo metadata for %s", role)
|
||||||
|
require.True(t, bytes.Equal(expected, actual),
|
||||||
|
"%s for %s: expected to recover after update", text, role)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,8 +303,12 @@ func TestUpdateFailsIfServerRootKeyChangedWithoutMultiSign(t *testing.T) {
|
||||||
if _, ok := err.(store.ErrMetaNotFound); ok { // one of the ways to mess up is to delete metadata
|
if _, ok := err.(store.ErrMetaNotFound); ok { // one of the ways to mess up is to delete metadata
|
||||||
|
|
||||||
err = repo.Update(forWrite)
|
err = repo.Update(forWrite)
|
||||||
require.Error(t, err) // the new server has a different root key, update fails
|
// the new server has a different root key, but we don't have any way of pinning against a previous root
|
||||||
|
require.NoError(t, err)
|
||||||
|
// revert our original metadata
|
||||||
|
for role := range origMeta {
|
||||||
|
require.NoError(t, repo.fileStore.SetMeta(role, origMeta[role]))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -312,7 +328,7 @@ func TestUpdateFailsIfServerRootKeyChangedWithoutMultiSign(t *testing.T) {
|
||||||
expected = messedUpMeta
|
expected = messedUpMeta
|
||||||
}
|
}
|
||||||
require.True(t, bytes.Equal(expected, actual),
|
require.True(t, bytes.Equal(expected, actual),
|
||||||
"%s for %s: expected to not have updated", text, role)
|
"%s for %s: expected to not have updated -- swizzle method %s", text, role, expt.desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,15 +786,15 @@ func testUpdateRemoteFileChecksumWrong(t *testing.T, opts updateOpts, errExpecte
|
||||||
// this does not include delete, which is tested separately so we can try to get
|
// this does not include delete, which is tested separately so we can try to get
|
||||||
// 404s and 503s
|
// 404s and 503s
|
||||||
var waysToMessUpServer = []swizzleExpectations{
|
var waysToMessUpServer = []swizzleExpectations{
|
||||||
{desc: "invalid JSON", expectErrs: []interface{}{&json.SyntaxError{}},
|
{desc: "invalid JSON", expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.SyntaxError{}},
|
||||||
swizzle: (*testutils.MetadataSwizzler).SetInvalidJSON},
|
swizzle: (*testutils.MetadataSwizzler).SetInvalidJSON},
|
||||||
|
|
||||||
{desc: "an invalid Signed", expectErrs: []interface{}{&json.UnmarshalTypeError{}},
|
{desc: "an invalid Signed", expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.UnmarshalTypeError{}},
|
||||||
swizzle: (*testutils.MetadataSwizzler).SetInvalidSigned},
|
swizzle: (*testutils.MetadataSwizzler).SetInvalidSigned},
|
||||||
|
|
||||||
{desc: "an invalid SignedMeta",
|
{desc: "an invalid SignedMeta",
|
||||||
// it depends which field gets unmarshalled first
|
// it depends which field gets unmarshalled first
|
||||||
expectErrs: []interface{}{&json.UnmarshalTypeError{}, &time.ParseError{}},
|
expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.UnmarshalTypeError{}, &time.ParseError{}},
|
||||||
swizzle: (*testutils.MetadataSwizzler).SetInvalidSignedMeta},
|
swizzle: (*testutils.MetadataSwizzler).SetInvalidSignedMeta},
|
||||||
|
|
||||||
// for the errors below, when we bootstrap root, we get cert.ErrValidationFail failures
|
// for the errors below, when we bootstrap root, we get cert.ErrValidationFail failures
|
||||||
|
@ -789,11 +805,11 @@ var waysToMessUpServer = []swizzleExpectations{
|
||||||
swizzle: (*testutils.MetadataSwizzler).SetInvalidMetadataType},
|
swizzle: (*testutils.MetadataSwizzler).SetInvalidMetadataType},
|
||||||
|
|
||||||
{desc: "invalid signatures", expectErrs: []interface{}{
|
{desc: "invalid signatures", expectErrs: []interface{}{
|
||||||
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}},
|
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
|
||||||
swizzle: (*testutils.MetadataSwizzler).InvalidateMetadataSignatures},
|
swizzle: (*testutils.MetadataSwizzler).InvalidateMetadataSignatures},
|
||||||
|
|
||||||
{desc: "meta signed by wrong key", expectErrs: []interface{}{
|
{desc: "meta signed by wrong key", expectErrs: []interface{}{
|
||||||
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}},
|
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
|
||||||
swizzle: (*testutils.MetadataSwizzler).SignMetadataWithInvalidKey},
|
swizzle: (*testutils.MetadataSwizzler).SignMetadataWithInvalidKey},
|
||||||
|
|
||||||
{desc: "expired metadata", expectErrs: []interface{}{
|
{desc: "expired metadata", expectErrs: []interface{}{
|
||||||
|
@ -1197,12 +1213,6 @@ func TestUpdateLocalAndRemoteRootCorrupt(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, localExpt := range waysToMessUpLocalMetadata {
|
for _, localExpt := range waysToMessUpLocalMetadata {
|
||||||
for _, serverExpt := range waysToMessUpServer {
|
for _, serverExpt := range waysToMessUpServer {
|
||||||
if localExpt.desc == "expired metadata" && serverExpt.desc == "lower metadata version" {
|
|
||||||
// TODO: bug right now where if the local metadata is invalid, we just download a
|
|
||||||
// new version - we verify the signatures and everything, but don't check the version
|
|
||||||
// against the previous if we can
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
testUpdateLocalAndRemoteRootCorrupt(t, true, localExpt, serverExpt)
|
testUpdateLocalAndRemoteRootCorrupt(t, true, localExpt, serverExpt)
|
||||||
testUpdateLocalAndRemoteRootCorrupt(t, false, localExpt, serverExpt)
|
testUpdateLocalAndRemoteRootCorrupt(t, false, localExpt, serverExpt)
|
||||||
}
|
}
|
||||||
|
@ -1364,7 +1374,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
|
||||||
keyIDs := make([]string, len(threeKeys))
|
keyIDs := make([]string, len(threeKeys))
|
||||||
for i := 0; i < len(threeKeys); i++ {
|
for i := 0; i < len(threeKeys); i++ {
|
||||||
threeKeys[i], err = testutils.CreateKey(
|
threeKeys[i], err = testutils.CreateKey(
|
||||||
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole)
|
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole, data.ECDSAKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
keyIDs[i] = threeKeys[i].ID()
|
keyIDs[i] = threeKeys[i].ID()
|
||||||
signedRoot.Signed.Keys[keyIDs[i]] = threeKeys[i]
|
signedRoot.Signed.Keys[keyIDs[i]] = threeKeys[i]
|
||||||
|
@ -1382,7 +1392,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
|
||||||
// --- threshold back to 1
|
// --- threshold back to 1
|
||||||
|
|
||||||
replacementKey, err := testutils.CreateKey(
|
replacementKey, err := testutils.CreateKey(
|
||||||
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole)
|
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalRootRole, data.ECDSAKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedRoot.Signed.Version++
|
signedRoot.Signed.Version++
|
||||||
signedRoot.Signed.Keys[replacementKey.ID()] = replacementKey
|
signedRoot.Signed.Keys[replacementKey.ID()] = replacementKey
|
||||||
|
@ -1406,7 +1416,7 @@ func TestValidateRootRotationWithOldRole(t *testing.T) {
|
||||||
// --- latest root role)
|
// --- latest root role)
|
||||||
signedRoot.Signed.Version++
|
signedRoot.Signed.Version++
|
||||||
snapKey, err := testutils.CreateKey(
|
snapKey, err := testutils.CreateKey(
|
||||||
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalSnapshotRole)
|
serverSwizzler.CryptoService, "docker.com/notary", data.CanonicalSnapshotRole, data.ECDSAKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
signedRoot.Signed.Keys[snapKey.ID()] = snapKey
|
signedRoot.Signed.Keys[snapKey.ID()] = snapKey
|
||||||
signedRoot.Signed.Roles[data.CanonicalSnapshotRole].KeyIDs = []string{snapKey.ID()}
|
signedRoot.Signed.Roles[data.CanonicalSnapshotRole].KeyIDs = []string{snapKey.ID()}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1346,98 +1346,6 @@ func TestClientKeyImportExportAllRoles(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertNumCerts(t *testing.T, tempDir string, expectedNum int) []string {
|
|
||||||
output, err := runCommand(t, tempDir, "cert", "list")
|
|
||||||
require.NoError(t, err)
|
|
||||||
lines := splitLines(strings.TrimSpace(output))
|
|
||||||
|
|
||||||
if expectedNum == 0 {
|
|
||||||
require.Len(t, lines, 1)
|
|
||||||
require.Equal(t, "No trusted root certificates present.", lines[0])
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Len(t, lines, expectedNum+2)
|
|
||||||
return lines[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestClientCertInteraction
|
|
||||||
func TestClientCertInteraction(t *testing.T) {
|
|
||||||
// -- setup --
|
|
||||||
setUp(t)
|
|
||||||
|
|
||||||
tempDir := tempDirWithConfig(t, "{}")
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
server := setupServer()
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
// -- tests --
|
|
||||||
_, err := runCommand(t, tempDir, "-s", server.URL, "init", "gun1")
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2")
|
|
||||||
require.NoError(t, err)
|
|
||||||
certs := assertNumCerts(t, tempDir, 2)
|
|
||||||
// root is always on disk, because even if there's a yubikey a backup is created
|
|
||||||
assertNumKeys(t, tempDir, 1, 4, true)
|
|
||||||
|
|
||||||
// remove certs for one gun
|
|
||||||
_, err = runCommand(t, tempDir, "cert", "remove", "-g", "gun1", "-y")
|
|
||||||
require.NoError(t, err)
|
|
||||||
certs = assertNumCerts(t, tempDir, 1)
|
|
||||||
// assert that when we remove cert by gun, we do not remove repo signing keys
|
|
||||||
// (root is always on disk, because even if there's a yubikey a backup is created)
|
|
||||||
assertNumKeys(t, tempDir, 1, 4, true)
|
|
||||||
// assert that when we remove cert by gun, we also remove TUF metadata
|
|
||||||
_, err = os.Stat(filepath.Join(tempDir, "tuf", "gun1"))
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
// remove a single cert
|
|
||||||
certID := strings.Fields(certs[0])[1]
|
|
||||||
// passing an empty gun here because the string for the previous gun has
|
|
||||||
// has already been stored (a drawback of running these commands without)
|
|
||||||
// shelling out
|
|
||||||
_, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "")
|
|
||||||
require.NoError(t, err)
|
|
||||||
assertNumCerts(t, tempDir, 0)
|
|
||||||
// assert that when we remove the last cert ID for a gun, we also remove TUF metadata
|
|
||||||
_, err = os.Stat(filepath.Join(tempDir, "tuf", "gun2"))
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
// Setup certificate with nonexistent repo GUN
|
|
||||||
// Check that we can only remove one certificate when specifying one ID
|
|
||||||
startTime := time.Now()
|
|
||||||
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
noGunCert, err := cryptoservice.GenerateCertificate(
|
|
||||||
privKey, "nonexistent", startTime, startTime.AddDate(10, 0, 0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
certStore, err := trustmanager.NewX509FileStore(filepath.Join(tempDir, "trusted_certificates"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = certStore.AddCert(noGunCert)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
certs = assertNumCerts(t, tempDir, 1)
|
|
||||||
certID = strings.Fields(certs[0])[1]
|
|
||||||
|
|
||||||
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
noGunCert2, err := cryptoservice.GenerateCertificate(
|
|
||||||
privKey, "nonexistent", startTime, startTime.AddDate(10, 0, 0))
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = certStore.AddCert(noGunCert2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
certs = assertNumCerts(t, tempDir, 2)
|
|
||||||
|
|
||||||
// passing an empty gun to overwrite previously stored gun
|
|
||||||
_, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Since another cert with the same GUN exists, we didn't remove everything
|
|
||||||
assertNumCerts(t, tempDir, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests default root key generation
|
// Tests default root key generation
|
||||||
func TestDefaultRootKeyGeneration(t *testing.T) {
|
func TestDefaultRootKeyGeneration(t *testing.T) {
|
||||||
// -- setup --
|
// -- setup --
|
||||||
|
|
|
@ -180,11 +180,6 @@ func (n *notaryCommander) GetCommand() *cobra.Command {
|
||||||
retriever: n.getRetriever(),
|
retriever: n.getRetriever(),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdCertGenerator := &certCommander{
|
|
||||||
configGetter: n.parseConfig,
|
|
||||||
retriever: n.getRetriever(),
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdTufGenerator := &tufCommander{
|
cmdTufGenerator := &tufCommander{
|
||||||
configGetter: n.parseConfig,
|
configGetter: n.parseConfig,
|
||||||
retriever: n.getRetriever(),
|
retriever: n.getRetriever(),
|
||||||
|
@ -192,7 +187,6 @@ func (n *notaryCommander) GetCommand() *cobra.Command {
|
||||||
|
|
||||||
notaryCmd.AddCommand(cmdKeyGenerator.GetCommand())
|
notaryCmd.AddCommand(cmdKeyGenerator.GetCommand())
|
||||||
notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand())
|
notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand())
|
||||||
notaryCmd.AddCommand(cmdCertGenerator.GetCommand())
|
|
||||||
|
|
||||||
cmdTufGenerator.AddToCommand(¬aryCmd)
|
cmdTufGenerator.AddToCommand(¬aryCmd)
|
||||||
|
|
||||||
|
|
|
@ -164,8 +164,6 @@ var exampleValidCommands = []string{
|
||||||
"key import backup.pem",
|
"key import backup.pem",
|
||||||
"key remove e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
"key remove e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
"key passwd e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
"key passwd e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
"cert list",
|
|
||||||
"cert remove e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
||||||
"delegation list repo",
|
"delegation list repo",
|
||||||
"delegation add repo targets/releases path/to/pem/file.pem",
|
"delegation add repo targets/releases path/to/pem/file.pem",
|
||||||
"delegation remove repo targets/releases",
|
"delegation remove repo targets/releases",
|
||||||
|
@ -205,8 +203,8 @@ func TestInsufficientArgumentsReturnsErrorAndPrintsUsage(t *testing.T) {
|
||||||
cmd.SetOutput(b)
|
cmd.SetOutput(b)
|
||||||
|
|
||||||
arglist := strings.Fields(args)
|
arglist := strings.Fields(args)
|
||||||
if args == "key list" || args == "cert list" || args == "key generate rsa" {
|
if args == "key list" || args == "key generate rsa" {
|
||||||
// in these case, "key" or "cert" or "key generate" are valid commands, so add an arg to them instead
|
// in these case, "key" or "key generate" are valid commands, so add an arg to them instead
|
||||||
arglist = append(arglist, "extraArg")
|
arglist = append(arglist, "extraArg")
|
||||||
} else {
|
} else {
|
||||||
arglist = arglist[:len(arglist)-1]
|
arglist = arglist[:len(arglist)-1]
|
||||||
|
@ -240,8 +238,8 @@ func TestBareCommandPrintsUsageAndNoError(t *testing.T) {
|
||||||
// usage is printed
|
// usage is printed
|
||||||
require.Contains(t, b.String(), "Usage:", "expected usage when running `notary`")
|
require.Contains(t, b.String(), "Usage:", "expected usage when running `notary`")
|
||||||
|
|
||||||
// notary key, notary cert, and notary delegation
|
// notary key and notary delegation
|
||||||
for _, bareCommand := range []string{"key", "cert", "delegation"} {
|
for _, bareCommand := range []string{"key", "delegation"} {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
cmd := NewNotaryCommand()
|
cmd := NewNotaryCommand()
|
||||||
cmd.SetOutput(b)
|
cmd.SetOutput(b)
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/notary/client"
|
"github.com/docker/notary/client"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
@ -204,52 +201,3 @@ func prettyPrintPaths(paths []string) string {
|
||||||
}
|
}
|
||||||
return strings.Join(prettyPaths, "\n")
|
return strings.Join(prettyPaths, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- pretty printing certs ---
|
|
||||||
|
|
||||||
// cert by repo name then expiry time. Don't bother sorting by fingerprint.
|
|
||||||
type certSorter []*x509.Certificate
|
|
||||||
|
|
||||||
func (t certSorter) Len() int { return len(t) }
|
|
||||||
func (t certSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
|
||||||
func (t certSorter) Less(i, j int) bool {
|
|
||||||
if t[i].Subject.CommonName < t[j].Subject.CommonName {
|
|
||||||
return true
|
|
||||||
} else if t[i].Subject.CommonName > t[j].Subject.CommonName {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return t[i].NotAfter.Before(t[j].NotAfter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a list of Ceritifcates in order of listing preference, pretty-prints
|
|
||||||
// the cert common name, fingerprint, and expiry
|
|
||||||
func prettyPrintCerts(certs []*x509.Certificate, writer io.Writer) {
|
|
||||||
if len(certs) == 0 {
|
|
||||||
writer.Write([]byte("\nNo trusted root certificates present.\n\n"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Stable(certSorter(certs))
|
|
||||||
|
|
||||||
table := getTable([]string{
|
|
||||||
"GUN", "Fingerprint of Trusted Root Certificate", "Expires In"}, writer)
|
|
||||||
|
|
||||||
for _, c := range certs {
|
|
||||||
days := math.Floor(c.NotAfter.Sub(time.Now()).Hours() / 24)
|
|
||||||
expiryString := "< 1 day"
|
|
||||||
if days == 1 {
|
|
||||||
expiryString = "1 day"
|
|
||||||
} else if days > 1 {
|
|
||||||
expiryString = fmt.Sprintf("%d days", int(days))
|
|
||||||
}
|
|
||||||
|
|
||||||
certID, err := trustmanager.FingerprintCert(c)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("Could not fingerprint certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
table.Append([]string{c.Subject.CommonName, certID, expiryString})
|
|
||||||
}
|
|
||||||
table.Render()
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -11,10 +10,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/notary/client"
|
"github.com/docker/notary/client"
|
||||||
"github.com/docker/notary/cryptoservice"
|
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
|
@ -201,19 +198,6 @@ func TestPrettyPrintSortedTargets(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- tests for pretty printing certs ---
|
|
||||||
|
|
||||||
func generateCertificate(t *testing.T, gun string, expireInHours int64) *x509.Certificate {
|
|
||||||
ecdsaPrivKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
startTime := time.Now()
|
|
||||||
endTime := startTime.Add(time.Hour * time.Duration(expireInHours))
|
|
||||||
cert, err := cryptoservice.GenerateCertificate(ecdsaPrivKey, gun, startTime, endTime)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return cert
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- tests for pretty printing roles ---
|
// --- tests for pretty printing roles ---
|
||||||
|
|
||||||
// If there are no roles, no table is printed, only a line saying that there
|
// If there are no roles, no table is printed, only a line saying that there
|
||||||
|
@ -268,53 +252,3 @@ func TestPrettyPrintSortedRoles(t *testing.T) {
|
||||||
require.Equal(t, expected[i], splitted)
|
require.Equal(t, expected[i], splitted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are no certs in the cert store store, a message that there are no
|
|
||||||
// certs should be displayed.
|
|
||||||
func TestPrettyPrintZeroCerts(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
prettyPrintCerts([]*x509.Certificate{}, &b)
|
|
||||||
text, err := ioutil.ReadAll(&b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(string(text)), "\n")
|
|
||||||
require.Len(t, lines, 1)
|
|
||||||
require.Equal(t, "No trusted root certificates present.", lines[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificates are pretty-printed in table form sorted by gun and then expiry
|
|
||||||
func TestPrettyPrintSortedCerts(t *testing.T) {
|
|
||||||
unsorted := []*x509.Certificate{
|
|
||||||
generateCertificate(t, "xylitol", 77), // 3 days 5 hours
|
|
||||||
generateCertificate(t, "xylitol", 12), // less than 1 day
|
|
||||||
generateCertificate(t, "cheesecake", 25), // a little more than 1 day
|
|
||||||
generateCertificate(t, "baklava", 239), // almost 10 days
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
prettyPrintCerts(unsorted, &b)
|
|
||||||
text, err := ioutil.ReadAll(&b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expected := [][]string{
|
|
||||||
{"baklava", "9 days"},
|
|
||||||
{"cheesecake", "1 day"},
|
|
||||||
{"xylitol", "< 1 day"},
|
|
||||||
{"xylitol", "3 days"},
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(string(text)), "\n")
|
|
||||||
require.Len(t, lines, len(expected)+2)
|
|
||||||
|
|
||||||
// starts with headers
|
|
||||||
require.True(t, reflect.DeepEqual(strings.Fields(lines[0]), strings.Fields(
|
|
||||||
"GUN FINGERPRINT OF TRUSTED ROOT CERTIFICATE EXPIRES IN")))
|
|
||||||
require.Equal(t, "----", lines[1][:4])
|
|
||||||
|
|
||||||
for i, line := range lines[2:] {
|
|
||||||
splitted := strings.Fields(line)
|
|
||||||
require.True(t, len(splitted) >= 3)
|
|
||||||
require.Equal(t, expected[i][0], splitted[0])
|
|
||||||
require.Equal(t, expected[i][1], strings.Join(splitted[2:], " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,272 +0,0 @@
|
||||||
package trustmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// X509FileStore implements X509Store that persists on disk
|
|
||||||
type X509FileStore struct {
|
|
||||||
validate Validator
|
|
||||||
fileMap map[CertID]string
|
|
||||||
fingerprintMap map[CertID]*x509.Certificate
|
|
||||||
nameMap map[string][]CertID
|
|
||||||
fileStore Storage
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewX509FileStore returns a new X509FileStore.
|
|
||||||
func NewX509FileStore(directory string) (*X509FileStore, error) {
|
|
||||||
validate := ValidatorFunc(func(cert *x509.Certificate) bool { return true })
|
|
||||||
return newX509FileStore(directory, validate)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewX509FilteredFileStore returns a new X509FileStore that validates certificates
|
|
||||||
// that are added.
|
|
||||||
func NewX509FilteredFileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) {
|
|
||||||
return newX509FileStore(directory, validate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newX509FileStore(directory string, validate func(*x509.Certificate) bool) (*X509FileStore, error) {
|
|
||||||
fileStore, err := NewSimpleFileStore(directory, certExtension)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &X509FileStore{
|
|
||||||
validate: ValidatorFunc(validate),
|
|
||||||
fileMap: make(map[CertID]string),
|
|
||||||
fingerprintMap: make(map[CertID]*x509.Certificate),
|
|
||||||
nameMap: make(map[string][]CertID),
|
|
||||||
fileStore: fileStore,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = loadCertsFromDir(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCert creates a filename for a given cert and adds a certificate with that name
|
|
||||||
func (s *X509FileStore) AddCert(cert *x509.Certificate) error {
|
|
||||||
if cert == nil {
|
|
||||||
return errors.New("adding nil Certificate to X509Store")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this certificate meets our validation criteria
|
|
||||||
if !s.validate.Validate(cert) {
|
|
||||||
return &ErrCertValidation{}
|
|
||||||
}
|
|
||||||
// Attempt to write the certificate to the file
|
|
||||||
if err := s.addNamedCert(cert); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addNamedCert allows adding a certificate while controlling the filename it gets
|
|
||||||
// stored under. If the file does not exist on disk, saves it.
|
|
||||||
func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
|
||||||
fileName, certID, err := fileName(cert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Debug("Adding cert with certID: ", certID)
|
|
||||||
// Validate if we already added this certificate before
|
|
||||||
if _, ok := s.fingerprintMap[certID]; ok {
|
|
||||||
return &ErrCertExists{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert certificate to PEM
|
|
||||||
certBytes := CertToPEM(cert)
|
|
||||||
|
|
||||||
// Save the file to disk if not already there.
|
|
||||||
if _, err = s.fileStore.Get(fileName); os.IsNotExist(err) {
|
|
||||||
if err := s.fileStore.Add(fileName, certBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We wrote the certificate succcessfully, add it to our in-memory storage
|
|
||||||
s.fingerprintMap[certID] = cert
|
|
||||||
s.fileMap[certID] = fileName
|
|
||||||
|
|
||||||
name := string(cert.Subject.CommonName)
|
|
||||||
s.nameMap[name] = append(s.nameMap[name], certID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveCert removes a certificate from a X509FileStore.
|
|
||||||
func (s *X509FileStore) RemoveCert(cert *x509.Certificate) error {
|
|
||||||
if cert == nil {
|
|
||||||
return errors.New("removing nil Certificate from X509Store")
|
|
||||||
}
|
|
||||||
|
|
||||||
certID, err := fingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(s.fingerprintMap, certID)
|
|
||||||
filename := s.fileMap[certID]
|
|
||||||
delete(s.fileMap, certID)
|
|
||||||
|
|
||||||
name := string(cert.Subject.CommonName)
|
|
||||||
|
|
||||||
// Filter the fingerprint out of this name entry
|
|
||||||
fpList := s.nameMap[name]
|
|
||||||
newfpList := fpList[:0]
|
|
||||||
for _, x := range fpList {
|
|
||||||
if x != certID {
|
|
||||||
newfpList = append(newfpList, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.nameMap[name] = newfpList
|
|
||||||
|
|
||||||
if err := s.fileStore.Remove(filename); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAll removes all the certificates from the store
|
|
||||||
func (s *X509FileStore) RemoveAll() error {
|
|
||||||
for _, filename := range s.fileMap {
|
|
||||||
if err := s.fileStore.Remove(filename); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.fileMap = make(map[CertID]string)
|
|
||||||
s.fingerprintMap = make(map[CertID]*x509.Certificate)
|
|
||||||
s.nameMap = make(map[string][]CertID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCertFromPEM adds the first certificate that it finds in the byte[], returning
|
|
||||||
// an error if no Certificates are found
|
|
||||||
func (s X509FileStore) AddCertFromPEM(pemBytes []byte) error {
|
|
||||||
cert, err := LoadCertFromPEM(pemBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCertFromFile tries to adds a X509 certificate to the store given a filename
|
|
||||||
func (s *X509FileStore) AddCertFromFile(filename string) error {
|
|
||||||
cert, err := LoadCertFromFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificates returns an array with all of the current X509 Certificates.
|
|
||||||
func (s *X509FileStore) GetCertificates() []*x509.Certificate {
|
|
||||||
certs := make([]*x509.Certificate, len(s.fingerprintMap))
|
|
||||||
i := 0
|
|
||||||
for _, v := range s.fingerprintMap {
|
|
||||||
certs[i] = v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return certs
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificatePool returns an x509 CertPool loaded with all the certificates
|
|
||||||
// in the store.
|
|
||||||
func (s *X509FileStore) GetCertificatePool() *x509.CertPool {
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
|
|
||||||
for _, v := range s.fingerprintMap {
|
|
||||||
pool.AddCert(v)
|
|
||||||
}
|
|
||||||
return pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificateByCertID returns the certificate that matches a certain certID
|
|
||||||
func (s *X509FileStore) GetCertificateByCertID(certID string) (*x509.Certificate, error) {
|
|
||||||
return s.getCertificateByCertID(CertID(certID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCertificateByCertID returns the certificate that matches a certain certID
|
|
||||||
func (s *X509FileStore) getCertificateByCertID(certID CertID) (*x509.Certificate, error) {
|
|
||||||
// If it does not look like a hex encoded sha256 hash, error
|
|
||||||
if len(certID) != 64 {
|
|
||||||
return nil, errors.New("invalid Subject Key Identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if this subject key identifier exists
|
|
||||||
if cert, ok := s.fingerprintMap[CertID(certID)]; ok {
|
|
||||||
return cert, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil, &ErrNoCertificatesFound{query: string(certID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificatesByCN returns all the certificates that match a specific
|
|
||||||
// CommonName
|
|
||||||
func (s *X509FileStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, error) {
|
|
||||||
var certs []*x509.Certificate
|
|
||||||
if ids, ok := s.nameMap[cn]; ok {
|
|
||||||
for _, v := range ids {
|
|
||||||
cert, err := s.getCertificateByCertID(v)
|
|
||||||
if err != nil {
|
|
||||||
// This error should never happen. This would mean that we have
|
|
||||||
// an inconsistent X509FileStore
|
|
||||||
return nil, &ErrBadCertificateStore{}
|
|
||||||
}
|
|
||||||
certs = append(certs, cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(certs) == 0 {
|
|
||||||
return nil, &ErrNoCertificatesFound{query: cn}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
|
|
||||||
// as part of the roots list. This never allows the use of system roots, returning
|
|
||||||
// an error if there are no root CAs.
|
|
||||||
func (s *X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
|
||||||
// If we have no Certificates loaded return error (we don't want to revert to using
|
|
||||||
// system CAs).
|
|
||||||
if len(s.fingerprintMap) == 0 {
|
|
||||||
return x509.VerifyOptions{}, errors.New("no root CAs available")
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
DNSName: dnsName,
|
|
||||||
Roots: s.GetCertificatePool(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty returns true if there are no certificates in the X509FileStore, false
|
|
||||||
// otherwise.
|
|
||||||
func (s *X509FileStore) Empty() bool {
|
|
||||||
return len(s.fingerprintMap) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileName(cert *x509.Certificate) (string, CertID, error) {
|
|
||||||
certID, err := fingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.Join(cert.Subject.CommonName, string(certID)), certID, nil
|
|
||||||
}
|
|
|
@ -1,428 +0,0 @@
|
||||||
package trustmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
store, err := NewX509FileStore(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create a new X509FileStore: %v", store)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewX509FileStore loads any existing certs from the directory, and does
|
|
||||||
// not overwrite any of the.
|
|
||||||
func TestNewX509FileStoreLoadsExistingCerts(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
certBytes, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
require.NoError(t, err)
|
|
||||||
out, err := os.Create(filepath.Join(tempDir, "root-ca.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// to distinguish it from the canonical format
|
|
||||||
distinguishingBytes := []byte{'\n', '\n', '\n', '\n', '\n', '\n'}
|
|
||||||
nBytes, err := out.Write(distinguishingBytes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, distinguishingBytes, nBytes)
|
|
||||||
|
|
||||||
nBytes, err = out.Write(certBytes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, certBytes, nBytes)
|
|
||||||
|
|
||||||
err = out.Close()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
store, err := NewX509FileStore(tempDir)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expectedCert, err := LoadCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []*x509.Certificate{expectedCert}, store.GetCertificates())
|
|
||||||
|
|
||||||
outBytes, err := ioutil.ReadFile(filepath.Join(tempDir, "root-ca.crt"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, distinguishingBytes, outBytes[:6], "original file overwritten")
|
|
||||||
require.Equal(t, certBytes, outBytes[6:], "original file overwritten")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddCertX509FileStore(t *testing.T) {
|
|
||||||
// Read certificate from file
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
// Decode PEM block
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
// Load X509 Certificate
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create a Store and add the certificate to it
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate: %v", err)
|
|
||||||
}
|
|
||||||
// Retrieve all the certificates
|
|
||||||
certs := store.GetCertificates()
|
|
||||||
// Check to see if certificate is present and total number of certs is correct
|
|
||||||
numCerts := len(certs)
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
if certs[0] != cert {
|
|
||||||
t.Fatalf("expected certificates to be the same")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddCertFromFileX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
require.NoError(t, err, "failed to create temporary directory")
|
|
||||||
|
|
||||||
store, err := NewX509FileStore(tempDir)
|
|
||||||
require.NoError(t, err, "failed to load x509 filestore")
|
|
||||||
|
|
||||||
err = store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
require.NoError(t, err, "failed to add certificate from file")
|
|
||||||
require.Len(t, store.GetCertificates(), 1)
|
|
||||||
|
|
||||||
// Now load the x509 filestore with the same path and expect the same result
|
|
||||||
newStore, err := NewX509FileStore(tempDir)
|
|
||||||
require.NoError(t, err, "failed to load x509 filestore")
|
|
||||||
require.Len(t, newStore.GetCertificates(), 1)
|
|
||||||
|
|
||||||
// Test that adding the same certificate returns an error
|
|
||||||
err = newStore.AddCert(newStore.GetCertificates()[0])
|
|
||||||
require.Error(t, err, "expected error when adding certificate twice")
|
|
||||||
require.Equal(t, err, &ErrCertExists{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestNewX509FileStoreEmpty verifies the behavior of the Empty function
|
|
||||||
func TestNewX509FileStoreEmpty(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
store, err := NewX509FileStore(tempDir)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, store.Empty())
|
|
||||||
|
|
||||||
err = store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.False(t, store.Empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddCertFromPEMX509FileStore(t *testing.T) {
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCertFromPEM(b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from PEM: %v", err)
|
|
||||||
}
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveCertX509FileStore(t *testing.T) {
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of certificates should be 1 since we added the cert
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the cert from the store
|
|
||||||
err = store.RemoveCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to remove certificate: %v", err)
|
|
||||||
}
|
|
||||||
// Number of certificates should be 0 since we added and removed the cert
|
|
||||||
numCerts = len(store.GetCertificates())
|
|
||||||
if numCerts != 0 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveAllX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add three certificates to store
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
certFiles := [3]string{"../fixtures/root-ca.crt",
|
|
||||||
"../fixtures/intermediate-ca.crt",
|
|
||||||
"../fixtures/secure.example.com.crt"}
|
|
||||||
for _, file := range certFiles {
|
|
||||||
b, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of certificates should be 3 since we added the cert
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 3 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the cert from the store
|
|
||||||
err = store.RemoveAll()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to remove all certificates: %v", err)
|
|
||||||
}
|
|
||||||
// Number of certificates should be 0 since we added and removed the cert
|
|
||||||
numCerts = len(store.GetCertificates())
|
|
||||||
if numCerts != 0 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestInexistentGetCertificateByKeyIDX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("no error returned for inexistent certificate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCertificateByKeyIDX509FileStore(t *testing.T) {
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from PEM: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyID, err := FingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to fingerprint the certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to retrieve cert by Subject Key IDs
|
|
||||||
_, err = store.GetCertificateByCertID(keyID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected certificate in store: %s", keyID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetVerifyOpsErrorsWithoutCertsX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create empty Store
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
|
|
||||||
// Try to get VerifyOptions without certs added
|
|
||||||
_, err = store.GetVerifyOptions("example.com")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expecting an error when getting empty VerifyOptions")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyLeafCertFromIntermediateX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create a store and add a root
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCertFromFile("../fixtures/intermediate-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the VerifyOptions from our Store
|
|
||||||
opts, err := store.GetVerifyOptions("secure.example.com")
|
|
||||||
|
|
||||||
// Get leaf certificate
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/secure.example.com.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find a valid chain for cert
|
|
||||||
_, err = cert.Verify(opts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't find a valid chain for this certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyIntermediateFromRootX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create a store and add a root
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the VerifyOptions from our Store
|
|
||||||
opts, err := store.GetVerifyOptions("Notary Testing CA")
|
|
||||||
|
|
||||||
// Get leaf certificate
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/intermediate-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find a valid chain for cert
|
|
||||||
_, err = cert.Verify(opts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't find a valid chain for this certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewX509FilteredFileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
store, err := NewX509FilteredFileStore(tempDir, func(cert *x509.Certificate) bool {
|
|
||||||
return cert.IsCA
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create new X509FilteredFileStore: %v", err)
|
|
||||||
}
|
|
||||||
// AddCert should succeed because this is a CA being added
|
|
||||||
err = store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCert should fail because this is a leaf cert being added
|
|
||||||
err = store.AddCertFromFile("../fixtures/secure.example.com.crt")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("was expecting non-CA certificate to be rejected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCertificatePoolX509FileStore(t *testing.T) {
|
|
||||||
tempDir, err := ioutil.TempDir("", "cert-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Create a store and add a root
|
|
||||||
store, _ := NewX509FileStore(tempDir)
|
|
||||||
err = store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pool := store.GetCertificatePool()
|
|
||||||
numCerts := len(pool.Subjects())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in pool: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
package trustmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// X509MemStore implements X509Store as an in-memory object with no persistence
|
|
||||||
type X509MemStore struct {
|
|
||||||
validate Validator
|
|
||||||
fingerprintMap map[CertID]*x509.Certificate
|
|
||||||
nameMap map[string][]CertID
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewX509MemStore returns a new X509MemStore.
|
|
||||||
func NewX509MemStore() *X509MemStore {
|
|
||||||
validate := ValidatorFunc(func(cert *x509.Certificate) bool { return true })
|
|
||||||
|
|
||||||
return &X509MemStore{
|
|
||||||
validate: validate,
|
|
||||||
fingerprintMap: make(map[CertID]*x509.Certificate),
|
|
||||||
nameMap: make(map[string][]CertID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewX509FilteredMemStore returns a new X509Memstore that validates certificates
|
|
||||||
// that are added.
|
|
||||||
func NewX509FilteredMemStore(validate func(*x509.Certificate) bool) *X509MemStore {
|
|
||||||
s := &X509MemStore{
|
|
||||||
|
|
||||||
validate: ValidatorFunc(validate),
|
|
||||||
fingerprintMap: make(map[CertID]*x509.Certificate),
|
|
||||||
nameMap: make(map[string][]CertID),
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCert adds a certificate to the store
|
|
||||||
func (s *X509MemStore) AddCert(cert *x509.Certificate) error {
|
|
||||||
if cert == nil {
|
|
||||||
return errors.New("adding nil Certificate to X509Store")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.validate.Validate(cert) {
|
|
||||||
return &ErrCertValidation{}
|
|
||||||
}
|
|
||||||
|
|
||||||
certID, err := fingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Debug("Adding cert with certID: ", certID)
|
|
||||||
|
|
||||||
// In this store we overwrite the certificate if it already exists
|
|
||||||
s.fingerprintMap[certID] = cert
|
|
||||||
name := string(cert.RawSubject)
|
|
||||||
s.nameMap[name] = append(s.nameMap[name], certID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveCert removes a certificate from a X509MemStore.
|
|
||||||
func (s *X509MemStore) RemoveCert(cert *x509.Certificate) error {
|
|
||||||
if cert == nil {
|
|
||||||
return errors.New("removing nil Certificate to X509Store")
|
|
||||||
}
|
|
||||||
|
|
||||||
certID, err := fingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(s.fingerprintMap, certID)
|
|
||||||
name := string(cert.RawSubject)
|
|
||||||
|
|
||||||
// Filter the fingerprint out of this name entry
|
|
||||||
fpList := s.nameMap[name]
|
|
||||||
newfpList := fpList[:0]
|
|
||||||
for _, x := range fpList {
|
|
||||||
if x != certID {
|
|
||||||
newfpList = append(newfpList, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.nameMap[name] = newfpList
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAll removes all the certificates from the store
|
|
||||||
func (s *X509MemStore) RemoveAll() error {
|
|
||||||
|
|
||||||
for _, cert := range s.fingerprintMap {
|
|
||||||
if err := s.RemoveCert(cert); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCertFromPEM adds a certificate to the store from a PEM blob
|
|
||||||
func (s *X509MemStore) AddCertFromPEM(pemBytes []byte) error {
|
|
||||||
cert, err := LoadCertFromPEM(pemBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCertFromFile tries to adds a X509 certificate to the store given a filename
|
|
||||||
func (s *X509MemStore) AddCertFromFile(originFilname string) error {
|
|
||||||
cert, err := LoadCertFromFile(originFilname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificates returns an array with all of the current X509 Certificates.
|
|
||||||
func (s *X509MemStore) GetCertificates() []*x509.Certificate {
|
|
||||||
certs := make([]*x509.Certificate, len(s.fingerprintMap))
|
|
||||||
i := 0
|
|
||||||
for _, v := range s.fingerprintMap {
|
|
||||||
certs[i] = v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return certs
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificatePool returns an x509 CertPool loaded with all the certificates
|
|
||||||
// in the store.
|
|
||||||
func (s *X509MemStore) GetCertificatePool() *x509.CertPool {
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
|
|
||||||
for _, v := range s.fingerprintMap {
|
|
||||||
pool.AddCert(v)
|
|
||||||
}
|
|
||||||
return pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificateByCertID returns the certificate that matches a certain certID
|
|
||||||
func (s *X509MemStore) GetCertificateByCertID(certID string) (*x509.Certificate, error) {
|
|
||||||
return s.getCertificateByCertID(CertID(certID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCertificateByCertID returns the certificate that matches a certain certID or error
|
|
||||||
func (s *X509MemStore) getCertificateByCertID(certID CertID) (*x509.Certificate, error) {
|
|
||||||
// If it does not look like a hex encoded sha256 hash, error
|
|
||||||
if len(certID) != 64 {
|
|
||||||
return nil, errors.New("invalid Subject Key Identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if this subject key identifier exists
|
|
||||||
if cert, ok := s.fingerprintMap[CertID(certID)]; ok {
|
|
||||||
return cert, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil, &ErrNoCertificatesFound{query: string(certID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCertificatesByCN returns all the certificates that match a specific
|
|
||||||
// CommonName
|
|
||||||
func (s *X509MemStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, error) {
|
|
||||||
var certs []*x509.Certificate
|
|
||||||
if ids, ok := s.nameMap[cn]; ok {
|
|
||||||
for _, v := range ids {
|
|
||||||
cert, err := s.getCertificateByCertID(v)
|
|
||||||
if err != nil {
|
|
||||||
// This error should never happen. This would mean that we have
|
|
||||||
// an inconsistent X509MemStore
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certs = append(certs, cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(certs) == 0 {
|
|
||||||
return nil, &ErrNoCertificatesFound{query: cn}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVerifyOptions returns VerifyOptions with the certificates within the KeyStore
|
|
||||||
// as part of the roots list. This never allows the use of system roots, returning
|
|
||||||
// an error if there are no root CAs.
|
|
||||||
func (s *X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
|
||||||
// If we have no Certificates loaded return error (we don't want to revert to using
|
|
||||||
// system CAs).
|
|
||||||
if len(s.fingerprintMap) == 0 {
|
|
||||||
return x509.VerifyOptions{}, errors.New("no root CAs available")
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
DNSName: dnsName,
|
|
||||||
Roots: s.GetCertificatePool(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts, nil
|
|
||||||
}
|
|
|
@ -1,302 +0,0 @@
|
||||||
package trustmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddCert(t *testing.T) {
|
|
||||||
// Read certificate from file
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
// Decode PEM block
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
// Load X509 Certificate
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
// Create a Store and add the certificate to it
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate: %v", err)
|
|
||||||
}
|
|
||||||
// Retrieve all the certificates
|
|
||||||
certs := store.GetCertificates()
|
|
||||||
// Check to see if certificate is present and total number of certs is correct
|
|
||||||
numCerts := len(certs)
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
if certs[0] != cert {
|
|
||||||
t.Fatalf("expected certificates to be the same")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddCertFromFile(t *testing.T) {
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddCertFromPEM(t *testing.T) {
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err = store.AddCertFromPEM(b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from PEM: %v", err)
|
|
||||||
}
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveCert(t *testing.T) {
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of certificates should be 1 since we added the cert
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the cert from the store
|
|
||||||
err = store.RemoveCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to remove certificate: %v", err)
|
|
||||||
}
|
|
||||||
// Number of certificates should be 0 since we added and removed the cert
|
|
||||||
numCerts = len(store.GetCertificates())
|
|
||||||
if numCerts != 0 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveAllX509MemStore(t *testing.T) {
|
|
||||||
// Add three certificates to store
|
|
||||||
store := NewX509MemStore()
|
|
||||||
certFiles := [3]string{"../fixtures/root-ca.crt",
|
|
||||||
"../fixtures/intermediate-ca.crt",
|
|
||||||
"../fixtures/secure.example.com.crt"}
|
|
||||||
for _, file := range certFiles {
|
|
||||||
b, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of certificates should be 3 since we added the cert
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 3 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the cert from the store
|
|
||||||
err := store.RemoveAll()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to remove all certificates: %v", err)
|
|
||||||
}
|
|
||||||
// Number of certificates should be 0 since we added and removed the cert
|
|
||||||
numCerts = len(store.GetCertificates())
|
|
||||||
if numCerts != 0 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestInexistentGetCertificateByCertID(t *testing.T) {
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = store.GetCertificateByCertID("4d06afd30b8bed131d2a84c97d00b37f422021598bfae34285ce98e77b708b5a")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("no error returned for inexistent certificate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCertificateByKeyID(t *testing.T) {
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err = store.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from PEM: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certID, err := FingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to fingerprint the certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to retrieve cert by Subject Key IDs
|
|
||||||
_, err = store.GetCertificateByCertID(certID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected certificate in store: %s", certID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetVerifyOpsErrorsWithoutCerts(t *testing.T) {
|
|
||||||
// Create empty Store
|
|
||||||
store := NewX509MemStore()
|
|
||||||
|
|
||||||
// Try to get VerifyOptions without certs added
|
|
||||||
_, err := store.GetVerifyOptions("example.com")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expecting an error when getting empty VerifyOptions")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyLeafCertFromIntermediate(t *testing.T) {
|
|
||||||
// Create a store and add a root
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err := store.AddCertFromFile("../fixtures/intermediate-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the VerifyOptions from our Store
|
|
||||||
opts, err := store.GetVerifyOptions("secure.example.com")
|
|
||||||
|
|
||||||
// Get leaf certificate
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/secure.example.com.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find a valid chain for cert
|
|
||||||
_, err = cert.Verify(opts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't find a valid chain for this certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyIntermediateFromRoot(t *testing.T) {
|
|
||||||
// Create a store and add a root
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the VerifyOptions from our Store
|
|
||||||
opts, err := store.GetVerifyOptions("Notary Testing CA")
|
|
||||||
|
|
||||||
// Get leaf certificate
|
|
||||||
b, err := ioutil.ReadFile("../fixtures/intermediate-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't load fixture: %v", err)
|
|
||||||
}
|
|
||||||
var block *pem.Block
|
|
||||||
block, _ = pem.Decode(b)
|
|
||||||
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't parse certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to find a valid chain for cert
|
|
||||||
_, err = cert.Verify(opts)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't find a valid chain for this certificate: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewX509FilteredMemStore(t *testing.T) {
|
|
||||||
store := NewX509FilteredMemStore(func(cert *x509.Certificate) bool {
|
|
||||||
return cert.IsCA
|
|
||||||
})
|
|
||||||
|
|
||||||
// AddCert should succeed because this is a CA being added
|
|
||||||
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
numCerts := len(store.GetCertificates())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in store: %d", numCerts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCert should fail because this is a leaf cert being added
|
|
||||||
err = store.AddCertFromFile("../fixtures/secure.example.com.crt")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("was expecting non-CA certificate to be rejected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetCertificatePool(t *testing.T) {
|
|
||||||
// Create a store and add a root
|
|
||||||
store := NewX509MemStore()
|
|
||||||
err := store.AddCertFromFile("../fixtures/root-ca.crt")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load certificate from file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pool := store.GetCertificatePool()
|
|
||||||
numCerts := len(pool.Subjects())
|
|
||||||
if numCerts != 1 {
|
|
||||||
t.Fatalf("unexpected number of certificates in pool: %d", numCerts)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package trustmanager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const certExtension string = "crt"
|
|
||||||
|
|
||||||
// ErrNoCertificatesFound is returned when no certificates are found for a
|
|
||||||
// GetCertificatesBy*
|
|
||||||
type ErrNoCertificatesFound struct {
|
|
||||||
query string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrNoCertificatesFound is returned when no certificates are found for a
|
|
||||||
// GetCertificatesBy*
|
|
||||||
func (err ErrNoCertificatesFound) Error() string {
|
|
||||||
return fmt.Sprintf("error, no certificates found in the keystore match: %s", err.query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrCertValidation is returned when a certificate doesn't pass the store specific
|
|
||||||
// validations
|
|
||||||
type ErrCertValidation struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrCertValidation is returned when a certificate doesn't pass the store specific
|
|
||||||
// validations
|
|
||||||
func (err ErrCertValidation) Error() string {
|
|
||||||
return fmt.Sprintf("store-specific certificate validations failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrCertExists is returned when a Certificate already exists in the key store
|
|
||||||
type ErrCertExists struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrCertExists is returned when a Certificate already exists in the key store
|
|
||||||
func (err ErrCertExists) Error() string {
|
|
||||||
return fmt.Sprintf("certificate already in the store")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrBadCertificateStore is returned when there is an internal inconsistency
|
|
||||||
// in our x509 store
|
|
||||||
type ErrBadCertificateStore struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrBadCertificateStore is returned when there is an internal inconsistency
|
|
||||||
// in our x509 store
|
|
||||||
func (err ErrBadCertificateStore) Error() string {
|
|
||||||
return fmt.Sprintf("inconsistent certificate store")
|
|
||||||
}
|
|
||||||
|
|
||||||
// X509Store is the interface for all X509Stores
|
|
||||||
type X509Store interface {
|
|
||||||
AddCert(cert *x509.Certificate) error
|
|
||||||
AddCertFromPEM(pemCerts []byte) error
|
|
||||||
AddCertFromFile(filename string) error
|
|
||||||
RemoveCert(cert *x509.Certificate) error
|
|
||||||
RemoveAll() error
|
|
||||||
GetCertificateByCertID(certID string) (*x509.Certificate, error)
|
|
||||||
GetCertificatesByCN(cn string) ([]*x509.Certificate, error)
|
|
||||||
GetCertificates() []*x509.Certificate
|
|
||||||
GetCertificatePool() *x509.CertPool
|
|
||||||
GetVerifyOptions(dnsName string) (x509.VerifyOptions, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertID represent the ID used to identify certificates
|
|
||||||
type CertID string
|
|
||||||
|
|
||||||
// Validator is a convenience type to create validating function that filters
|
|
||||||
// certificates that get added to the store
|
|
||||||
type Validator interface {
|
|
||||||
Validate(cert *x509.Certificate) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidatorFunc is a convenience type to create functions that implement
|
|
||||||
// the Validator interface
|
|
||||||
type ValidatorFunc func(cert *x509.Certificate) bool
|
|
||||||
|
|
||||||
// Validate implements the Validator interface to allow for any func() bool method
|
|
||||||
// to be passed as a Validator
|
|
||||||
func (vf ValidatorFunc) Validate(cert *x509.Certificate) bool {
|
|
||||||
return vf(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify operates on an X509Store and validates the existence of a chain of trust
|
|
||||||
// between a leafCertificate and a CA present inside of the X509 Store.
|
|
||||||
// It requires at least two certificates in certList, a leaf Certificate and an
|
|
||||||
// intermediate CA certificate.
|
|
||||||
func Verify(s X509Store, dnsName string, certList []*x509.Certificate) error {
|
|
||||||
// If we have no Certificates loaded return error (we don't want to revert to using
|
|
||||||
// system CAs).
|
|
||||||
if len(s.GetCertificates()) == 0 {
|
|
||||||
return errors.New("no root CAs available")
|
|
||||||
}
|
|
||||||
|
|
||||||
// At a minimum we should be provided a leaf cert and an intermediate.
|
|
||||||
if len(certList) < 2 {
|
|
||||||
return errors.New("certificate and at least one intermediate needed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the VerifyOptions from the keystore for a base dnsName
|
|
||||||
opts, err := s.GetVerifyOptions(dnsName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a Certificate Pool for our intermediate certificates
|
|
||||||
intPool := x509.NewCertPool()
|
|
||||||
var leafCert *x509.Certificate
|
|
||||||
|
|
||||||
// Iterate through all the certificates
|
|
||||||
for _, c := range certList {
|
|
||||||
// If the cert is a CA, we add it to the intermediates pool. If not, we call
|
|
||||||
// it the leaf cert
|
|
||||||
if c.IsCA {
|
|
||||||
intPool.AddCert(c)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Certificate is not a CA, it must be our leaf certificate.
|
|
||||||
// If we already found one, bail with error
|
|
||||||
if leafCert != nil {
|
|
||||||
return errors.New("more than one leaf certificate found")
|
|
||||||
}
|
|
||||||
leafCert = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// We exited the loop with no leaf certificates
|
|
||||||
if leafCert == nil {
|
|
||||||
return errors.New("no leaf certificates found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have one leaf certificate and at least one intermediate. Lets add this
|
|
||||||
// Cert Pool as the Intermediates list on our VerifyOptions
|
|
||||||
opts.Intermediates = intPool
|
|
||||||
|
|
||||||
// Finally, let's call Verify on our leafCert with our fully configured options
|
|
||||||
chains, err := leafCert.Verify(opts)
|
|
||||||
if len(chains) == 0 || err != nil {
|
|
||||||
return fmt.Errorf("certificate verification failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,8 +14,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -24,40 +22,6 @@ import (
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCertFromURL tries to get a X509 certificate given a HTTPS URL
|
|
||||||
func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
|
|
||||||
url, err := url.Parse(urlStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are adding via HTTPS
|
|
||||||
if url.Scheme != "https" {
|
|
||||||
return nil, errors.New("only HTTPS URLs allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the certificate and write to directory
|
|
||||||
resp, err := http.Get(url.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the content to certBytes
|
|
||||||
defer resp.Body.Close()
|
|
||||||
certBytes, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to extract the first valid PEM certificate from the bytes
|
|
||||||
cert, err := LoadCertFromPEM(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertToPEM is a utility function returns a PEM encoded x509 Certificate
|
// CertToPEM is a utility function returns a PEM encoded x509 Certificate
|
||||||
func CertToPEM(cert *x509.Certificate) []byte {
|
func CertToPEM(cert *x509.Certificate) []byte {
|
||||||
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||||
|
@ -100,60 +64,6 @@ func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||||
return nil, errors.New("no certificates found in PEM data")
|
return nil, errors.New("no certificates found in PEM data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
|
|
||||||
func FingerprintCert(cert *x509.Certificate) (string, error) {
|
|
||||||
certID, err := fingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(certID), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fingerprintCert(cert *x509.Certificate) (CertID, error) {
|
|
||||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
|
||||||
pemdata := pem.EncodeToMemory(&block)
|
|
||||||
|
|
||||||
var tufKey data.PublicKey
|
|
||||||
switch cert.PublicKeyAlgorithm {
|
|
||||||
case x509.RSA:
|
|
||||||
tufKey = data.NewRSAx509PublicKey(pemdata)
|
|
||||||
case x509.ECDSA:
|
|
||||||
tufKey = data.NewECDSAx509PublicKey(pemdata)
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("got Unknown key type while fingerprinting certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
return CertID(tufKey.ID()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
|
|
||||||
func loadCertsFromDir(s *X509FileStore) error {
|
|
||||||
for _, f := range s.fileStore.ListFiles() {
|
|
||||||
// ListFiles returns relative paths
|
|
||||||
data, err := s.fileStore.Get(f)
|
|
||||||
if err != nil {
|
|
||||||
// the filestore told us it had a file that it then couldn't serve.
|
|
||||||
// this is a serious problem so error immediately
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.AddCertFromPEM(data)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(*ErrCertValidation); ok {
|
|
||||||
logrus.Debugf("ignoring certificate, did not pass validation: %s", f)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := err.(*ErrCertExists); ok {
|
|
||||||
logrus.Debugf("ignoring certificate, already exists in the store: %s", f)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCertFromFile loads the first certificate from the file provided. The
|
// LoadCertFromFile loads the first certificate from the file provided. The
|
||||||
// data is expected to be PEM Encoded and contain one of more certificates
|
// data is expected to be PEM Encoded and contain one of more certificates
|
||||||
// with PEM type "CERTIFICATE"
|
// with PEM type "CERTIFICATE"
|
||||||
|
@ -533,37 +443,39 @@ func CertToKey(cert *x509.Certificate) data.PublicKey {
|
||||||
|
|
||||||
// CertsToKeys transforms each of the input certificate chains into its corresponding
|
// CertsToKeys transforms each of the input certificate chains into its corresponding
|
||||||
// PublicKey
|
// PublicKey
|
||||||
func CertsToKeys(leafCerts []*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey {
|
func CertsToKeys(leafCerts map[string]*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey {
|
||||||
keys := make(map[string]data.PublicKey)
|
keys := make(map[string]data.PublicKey)
|
||||||
for _, leafCert := range leafCerts {
|
for id, leafCert := range leafCerts {
|
||||||
certBundle := []*x509.Certificate{leafCert}
|
if key, err := CertBundleToKey(leafCert, intCerts[id]); err == nil {
|
||||||
certID, err := FingerprintCert(leafCert)
|
keys[key.ID()] = key
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if intCertsForLeaves, ok := intCerts[certID]; ok {
|
|
||||||
certBundle = append(certBundle, intCertsForLeaves...)
|
|
||||||
}
|
|
||||||
certChainPEM, err := CertChainToPEM(certBundle)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var newKey data.PublicKey
|
|
||||||
// Use the leaf cert's public key algorithm for typing
|
|
||||||
switch leafCert.PublicKeyAlgorithm {
|
|
||||||
case x509.RSA:
|
|
||||||
newKey = data.NewRSAx509PublicKey(certChainPEM)
|
|
||||||
case x509.ECDSA:
|
|
||||||
newKey = data.NewECDSAx509PublicKey(certChainPEM)
|
|
||||||
default:
|
|
||||||
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keys[newKey.ID()] = newKey
|
|
||||||
}
|
}
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertBundleToKey creates a TUF key from a leaf certs and a list of
|
||||||
|
// intermediates
|
||||||
|
func CertBundleToKey(leafCert *x509.Certificate, intCerts []*x509.Certificate) (data.PublicKey, error) {
|
||||||
|
certBundle := []*x509.Certificate{leafCert}
|
||||||
|
certBundle = append(certBundle, intCerts...)
|
||||||
|
certChainPEM, err := CertChainToPEM(certBundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var newKey data.PublicKey
|
||||||
|
// Use the leaf cert's public key algorithm for typing
|
||||||
|
switch leafCert.PublicKeyAlgorithm {
|
||||||
|
case x509.RSA:
|
||||||
|
newKey = data.NewRSAx509PublicKey(certChainPEM)
|
||||||
|
case x509.ECDSA:
|
||||||
|
newKey = data.NewECDSAx509PublicKey(certChainPEM)
|
||||||
|
default:
|
||||||
|
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
|
||||||
|
return nil, x509.ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
return newKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval.
|
// NewCertificate returns an X509 Certificate following a template, given a GUN and validity interval.
|
||||||
func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
|
func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
@ -610,14 +522,3 @@ func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
|
||||||
|
|
||||||
return key.ID(), nil
|
return key.ID(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterCertsExpiredSha1 can be used as the filter function to cert store
|
|
||||||
// initializers to filter out all expired or SHA-1 certificate that we
|
|
||||||
// shouldn't load.
|
|
||||||
func FilterCertsExpiredSha1(cert *x509.Certificate) bool {
|
|
||||||
return !cert.IsCA &&
|
|
||||||
time.Now().Before(cert.NotAfter) &&
|
|
||||||
cert.SignatureAlgorithm != x509.SHA1WithRSA &&
|
|
||||||
cert.SignatureAlgorithm != x509.DSAWithSHA1 &&
|
|
||||||
cert.SignatureAlgorithm != x509.ECDSAWithSHA1
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,10 +28,19 @@ func TestCertsToKeys(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Get our certList with Leaf Cert and Intermediate
|
// Get our certList with Leaf Cert and Intermediate
|
||||||
certList := []*x509.Certificate{leafCert, intermediateCA, rootCA}
|
certMap := map[string]*x509.Certificate{
|
||||||
|
"a": leafCert,
|
||||||
|
"b": intermediateCA,
|
||||||
|
"c": rootCA,
|
||||||
|
}
|
||||||
|
certList := []*x509.Certificate{
|
||||||
|
leafCert,
|
||||||
|
intermediateCA,
|
||||||
|
rootCA,
|
||||||
|
}
|
||||||
|
|
||||||
// Call CertsToKeys
|
// Call CertsToKeys
|
||||||
keys := CertsToKeys(certList, make(map[string][]*x509.Certificate))
|
keys := CertsToKeys(certMap, make(map[string][]*x509.Certificate))
|
||||||
require.NotNil(t, keys)
|
require.NotNil(t, keys)
|
||||||
require.Len(t, keys, 3)
|
require.Len(t, keys, 3)
|
||||||
|
|
||||||
|
@ -49,7 +58,7 @@ func TestCertsToKeys(t *testing.T) {
|
||||||
emptyCert := x509.Certificate{}
|
emptyCert := x509.Certificate{}
|
||||||
// Also try changing the pre-existing leaf cert into an invalid algorithm
|
// Also try changing the pre-existing leaf cert into an invalid algorithm
|
||||||
leafCert.PublicKeyAlgorithm = x509.DSA
|
leafCert.PublicKeyAlgorithm = x509.DSA
|
||||||
keys = CertsToKeys([]*x509.Certificate{&emptyCert, leafCert}, make(map[string][]*x509.Certificate))
|
keys = CertsToKeys(map[string]*x509.Certificate{"d": &emptyCert, "e": leafCert}, make(map[string][]*x509.Certificate))
|
||||||
require.Empty(t, keys)
|
require.Empty(t, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,9 @@ func (err ErrRootRotationFail) Error() string {
|
||||||
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
|
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prettyFormatCertIDs(certs []*x509.Certificate) string {
|
func prettyFormatCertIDs(certs map[string]*x509.Certificate) string {
|
||||||
ids := make([]string, 0, len(certs))
|
ids := make([]string, 0, len(certs))
|
||||||
for _, cert := range certs {
|
for id := range certs {
|
||||||
id, err := trustmanager.FingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
id = fmt.Sprintf("[Error %s]", err)
|
|
||||||
}
|
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
return strings.Join(ids, ", ")
|
return strings.Join(ids, ", ")
|
||||||
|
@ -53,8 +49,9 @@ func prettyFormatCertIDs(certs []*x509.Certificate) string {
|
||||||
ValidateRoot receives a new root, validates its correctness and attempts to
|
ValidateRoot receives a new root, validates its correctness and attempts to
|
||||||
do root key rotation if needed.
|
do root key rotation if needed.
|
||||||
|
|
||||||
First we list the current trusted certificates we have for a particular GUN. If
|
First we check if we have any trusted certificates for a particular GUN in
|
||||||
that list is non-empty means that we've already seen this repository before, and
|
a previous root, if we have one. If the previous root is not nil and we find
|
||||||
|
certificates for this GUN, we've already seen this repository before, and
|
||||||
have a list of trusted certificates for it. In this case, we use this list of
|
have a list of trusted certificates for it. In this case, we use this list of
|
||||||
certificates to attempt to validate this root file.
|
certificates to attempt to validate this root file.
|
||||||
|
|
||||||
|
@ -86,68 +83,66 @@ We shall call this: TOFUS.
|
||||||
|
|
||||||
Validation failure at any step will result in an ErrValidationFailed error.
|
Validation failure at any step will result in an ErrValidationFailed error.
|
||||||
*/
|
*/
|
||||||
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error {
|
func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) (*data.SignedRoot, error) {
|
||||||
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
||||||
signedRoot, err := data.RootFromSigned(root)
|
signedRoot, err := data.RootFromSigned(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
|
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
|
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
|
||||||
allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
|
allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
|
||||||
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun)
|
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
|
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
|
||||||
return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
|
return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve all the trusted certificates that match this gun
|
// If we have a previous root, let's try to use it to validate that this new root is valid.
|
||||||
trustedCerts, err := certStore.GetCertificatesByCN(gun)
|
if prevRoot != nil {
|
||||||
if err != nil {
|
// Retrieve all the trusted certificates from our previous root
|
||||||
// If the error that we get back is different than ErrNoCertificatesFound
|
// Note that we do not validate expiries here since our originally trusted root might have expired certs
|
||||||
// we couldn't check if there are any certificates with this CN already
|
allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
|
||||||
// trusted. Let's take the conservative approach and return a failed validation
|
trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
|
||||||
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
|
|
||||||
logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err)
|
// Use the certificates we found in the previous root for the GUN to verify its signatures
|
||||||
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
|
// This could potentially be an empty set, in which case we will fail to verify
|
||||||
|
logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun,
|
||||||
|
prettyFormatCertIDs(trustedLeafCerts))
|
||||||
|
|
||||||
|
// Extract the previous root's threshold for signature verification
|
||||||
|
prevRootRoleData, ok := prevRoot.Signed.Roles[data.CanonicalRootRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// If we have certificates that match this specific GUN, let's make sure to
|
|
||||||
// use them first to validate that this new root is valid.
|
|
||||||
if len(trustedCerts) != 0 {
|
|
||||||
logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun,
|
|
||||||
prettyFormatCertIDs(trustedCerts))
|
|
||||||
err = signed.VerifySignatures(
|
err = signed.VerifySignatures(
|
||||||
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1})
|
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||||
return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}
|
return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
|
logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
|
||||||
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
|
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ErrValidationFail{Reason: err.Error()}
|
return nil, &ErrValidationFail{Reason: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
validPinnedCerts := []*x509.Certificate{}
|
validPinnedCerts := map[string]*x509.Certificate{}
|
||||||
for _, cert := range certsFromRoot {
|
for id, cert := range certsFromRoot {
|
||||||
certID, err := trustmanager.FingerprintCert(cert)
|
if ok := trustPinCheckFunc(cert, allIntCerts[id]); !ok {
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok {
|
validPinnedCerts[id] = cert
|
||||||
continue
|
|
||||||
}
|
|
||||||
validPinnedCerts = append(validPinnedCerts, cert)
|
|
||||||
}
|
}
|
||||||
if len(validPinnedCerts) == 0 {
|
if len(validPinnedCerts) == 0 {
|
||||||
return &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
|
return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
|
||||||
}
|
}
|
||||||
certsFromRoot = validPinnedCerts
|
certsFromRoot = validPinnedCerts
|
||||||
}
|
}
|
||||||
|
@ -159,64 +154,29 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
||||||
Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
|
Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||||
return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
|
return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"}
|
||||||
}
|
|
||||||
|
|
||||||
// Getting here means:
|
|
||||||
// A) we had trusted certificates and both the old and new validated this root.
|
|
||||||
// or
|
|
||||||
// B) we had no trusted certificates but the new set of certificates has integrity (self-signed).
|
|
||||||
logrus.Debugf("entering root certificate rotation for: %s", gun)
|
|
||||||
|
|
||||||
// Do root certificate rotation: we trust only the certs present in the new root
|
|
||||||
// First we add all the new certificates (even if they already exist)
|
|
||||||
for _, cert := range certsFromRoot {
|
|
||||||
err := certStore.AddCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
// If the error is already exists we don't fail the rotation
|
|
||||||
if _, ok := err.(*trustmanager.ErrCertExists); ok {
|
|
||||||
logrus.Debugf("ignoring certificate addition to: %s", gun)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we delete old certificates that aren't present in the new root
|
|
||||||
oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("inconsistency when removing old certificates: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for certID, cert := range oldCertsToRemove {
|
|
||||||
logrus.Debugf("removing certificate with certID: %s", certID)
|
|
||||||
err = certStore.RemoveCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
|
|
||||||
return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Root validation succeeded for %s", gun)
|
logrus.Debugf("Root validation succeeded for %s", gun)
|
||||||
return nil
|
return signedRoot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validRootLeafCerts returns a list of non-expired, non-sha1 certificates
|
// validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
|
||||||
// found in root whose Common-Names match the provided GUN. Note that this
|
// found in root whose Common-Names match the provided GUN. Note that this
|
||||||
// "validity" alone does not imply any measure of trust.
|
// "validity" alone does not imply any measure of trust.
|
||||||
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) ([]*x509.Certificate, error) {
|
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, checkExpiry bool) (map[string]*x509.Certificate, error) {
|
||||||
var validLeafCerts []*x509.Certificate
|
validLeafCerts := make(map[string]*x509.Certificate)
|
||||||
|
|
||||||
// Go through every leaf certificate and check that the CN matches the gun
|
// Go through every leaf certificate and check that the CN matches the gun
|
||||||
for _, cert := range allLeafCerts {
|
for id, cert := range allLeafCerts {
|
||||||
// Validate that this leaf certificate has a CN that matches the exact gun
|
// Validate that this leaf certificate has a CN that matches the exact gun
|
||||||
if cert.Subject.CommonName != gun {
|
if cert.Subject.CommonName != gun {
|
||||||
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
|
logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
|
||||||
cert.Subject.CommonName, gun)
|
cert.Subject.CommonName, gun)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Make sure the certificate is not expired
|
// Make sure the certificate is not expired if checkExpiry is true
|
||||||
if time.Now().After(cert.NotAfter) {
|
if checkExpiry && time.Now().After(cert.NotAfter) {
|
||||||
logrus.Debugf("error leaf certificate is expired")
|
logrus.Debugf("error leaf certificate is expired")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -230,7 +190,7 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
validLeafCerts = append(validLeafCerts, cert)
|
validLeafCerts[id] = cert
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(validLeafCerts) < 1 {
|
if len(validLeafCerts) < 1 {
|
||||||
|
@ -246,11 +206,15 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
|
||||||
// parseAllCerts returns two maps, one with all of the leafCertificates and one
|
// parseAllCerts returns two maps, one with all of the leafCertificates and one
|
||||||
// with all the intermediate certificates found in signedRoot
|
// with all the intermediate certificates found in signedRoot
|
||||||
func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
|
func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
|
||||||
|
if signedRoot == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
leafCerts := make(map[string]*x509.Certificate)
|
leafCerts := make(map[string]*x509.Certificate)
|
||||||
intCerts := make(map[string][]*x509.Certificate)
|
intCerts := make(map[string][]*x509.Certificate)
|
||||||
|
|
||||||
// Before we loop through all root keys available, make sure any exist
|
// Before we loop through all root keys available, make sure any exist
|
||||||
rootRoles, ok := signedRoot.Signed.Roles["root"]
|
rootRoles, ok := signedRoot.Signed.Roles[data.CanonicalRootRole]
|
||||||
if !ok {
|
if !ok {
|
||||||
logrus.Debugf("tried to parse certificates from invalid root signed data")
|
logrus.Debugf("tried to parse certificates from invalid root signed data")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -290,59 +254,14 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
|
||||||
|
|
||||||
// Get the ID of the leaf certificate
|
// Get the ID of the leaf certificate
|
||||||
leafCert := leafCertList[0]
|
leafCert := leafCertList[0]
|
||||||
leafID, err := trustmanager.FingerprintCert(leafCert)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the leaf cert in the map
|
// Store the leaf cert in the map
|
||||||
leafCerts[leafID] = leafCert
|
leafCerts[key.ID()] = leafCert
|
||||||
|
|
||||||
// Get all the remainder certificates marked as a CA to be used as intermediates
|
// Get all the remainder certificates marked as a CA to be used as intermediates
|
||||||
intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
|
intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
|
||||||
intCerts[leafID] = intermediateCerts
|
intCerts[key.ID()] = intermediateCerts
|
||||||
}
|
}
|
||||||
|
|
||||||
return leafCerts, intCerts
|
return leafCerts, intCerts
|
||||||
}
|
}
|
||||||
|
|
||||||
// certsToRemove returns all the certificates from oldCerts that aren't present
|
|
||||||
// in newCerts. Note that newCerts should never be empty, else this function will error.
|
|
||||||
// We expect newCerts to come from validateRootLeafCerts, which does not return empty sets.
|
|
||||||
func certsToRemove(oldCerts, newCerts []*x509.Certificate) (map[string]*x509.Certificate, error) {
|
|
||||||
certsToRemove := make(map[string]*x509.Certificate)
|
|
||||||
|
|
||||||
// Populate a map with all the IDs from newCert
|
|
||||||
var newCertMap = make(map[string]struct{})
|
|
||||||
for _, cert := range newCerts {
|
|
||||||
certID, err := trustmanager.FingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", certID, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newCertMap[certID] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't want to "rotate" certificates to an empty set, nor keep old certificates if the
|
|
||||||
// new root does not trust them. newCerts should come from validRootLeafCerts, which refuses
|
|
||||||
// to return an empty set, and they should all be fingerprintable, so this should never happen
|
|
||||||
// - fail just to be sure.
|
|
||||||
if len(newCertMap) == 0 {
|
|
||||||
return nil, &ErrRootRotationFail{Reason: "internal error, got no certificates to rotate to"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all the old certificates and check to see if we should remove them
|
|
||||||
for _, cert := range oldCerts {
|
|
||||||
certID, err := trustmanager.FingerprintCert(cert)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("error while fingerprinting root certificate with certID: %s, %v", certID, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := newCertMap[certID]; !ok {
|
|
||||||
certsToRemove[certID] = cert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certsToRemove, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,11 +18,11 @@ import (
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/notary"
|
|
||||||
"github.com/docker/notary/cryptoservice"
|
"github.com/docker/notary/cryptoservice"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
|
"github.com/docker/notary/tuf/testutils"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,86 +44,6 @@ const rootPubKeyID = "49cf5c6404a35fa41d5a5aa2ce539dfee0d7a2176d0da488914a38603b
|
||||||
|
|
||||||
const targetsPubKeyID = "1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670"
|
const targetsPubKeyID = "1fc4fdc38f66558658c5c59b67f1716bdc6a74ef138b023ae5931db69f51d670"
|
||||||
|
|
||||||
func TestCertsToRemove(t *testing.T) {
|
|
||||||
// Get a few certificates to test with
|
|
||||||
cert1, err := trustmanager.LoadCertFromFile("../fixtures/secure.example.com.crt")
|
|
||||||
require.NoError(t, err)
|
|
||||||
cert1KeyID, err := trustmanager.FingerprintCert(cert1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Get intermediate certificate
|
|
||||||
cert2, err := trustmanager.LoadCertFromFile("../fixtures/self-signed_secure.example.com.crt")
|
|
||||||
require.NoError(t, err)
|
|
||||||
cert2KeyID, err := trustmanager.FingerprintCert(cert2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Get leaf certificate
|
|
||||||
cert3, err := trustmanager.LoadCertFromFile("../fixtures/self-signed_docker.com-notary.crt")
|
|
||||||
require.NoError(t, err)
|
|
||||||
cert3KeyID, err := trustmanager.FingerprintCert(cert3)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Call CertsToRemove with only one old and one new
|
|
||||||
oldCerts := []*x509.Certificate{cert1}
|
|
||||||
newCerts := []*x509.Certificate{cert2}
|
|
||||||
|
|
||||||
certificates, err := certsToRemove(oldCerts, newCerts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, certificates, 1)
|
|
||||||
_, ok := certificates[cert1KeyID]
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
// Call CertsToRemove with two old and one new
|
|
||||||
oldCerts = []*x509.Certificate{cert1, cert2}
|
|
||||||
newCerts = []*x509.Certificate{cert3}
|
|
||||||
|
|
||||||
certificates, err = certsToRemove(oldCerts, newCerts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, certificates, 2)
|
|
||||||
_, ok = certificates[cert1KeyID]
|
|
||||||
require.True(t, ok)
|
|
||||||
_, ok = certificates[cert2KeyID]
|
|
||||||
require.True(t, ok)
|
|
||||||
_, ok = certificates[cert3KeyID]
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
// Call CertsToRemove with two new and one old
|
|
||||||
oldCerts = []*x509.Certificate{cert3}
|
|
||||||
newCerts = []*x509.Certificate{cert2, cert1}
|
|
||||||
|
|
||||||
certificates, err = certsToRemove(oldCerts, newCerts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, certificates, 1)
|
|
||||||
_, ok = certificates[cert3KeyID]
|
|
||||||
require.True(t, ok)
|
|
||||||
_, ok = certificates[cert1KeyID]
|
|
||||||
require.False(t, ok)
|
|
||||||
_, ok = certificates[cert2KeyID]
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
// Call CertsToRemove with three old certificates and no new
|
|
||||||
oldCerts = []*x509.Certificate{cert1, cert2, cert3}
|
|
||||||
newCerts = []*x509.Certificate{}
|
|
||||||
|
|
||||||
certificates, err = certsToRemove(oldCerts, newCerts)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
// Call CertsToRemove with three new certificates and no old
|
|
||||||
oldCerts = []*x509.Certificate{}
|
|
||||||
newCerts = []*x509.Certificate{cert1, cert2, cert3}
|
|
||||||
|
|
||||||
certificates, err = certsToRemove(oldCerts, newCerts)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, certificates, 0)
|
|
||||||
_, ok = certificates[cert1KeyID]
|
|
||||||
require.False(t, ok)
|
|
||||||
_, ok = certificates[cert2KeyID]
|
|
||||||
require.False(t, ok)
|
|
||||||
_, ok = certificates[cert3KeyID]
|
|
||||||
require.False(t, ok)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateRoot(t *testing.T) {
|
func TestValidateRoot(t *testing.T) {
|
||||||
var testSignedRoot data.Signed
|
var testSignedRoot data.Signed
|
||||||
var signedRootBytes bytes.Buffer
|
var signedRootBytes bytes.Buffer
|
||||||
|
@ -133,14 +53,6 @@ func TestValidateRoot(t *testing.T) {
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
// Create a X509Store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Execute our template
|
// Execute our template
|
||||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||||
|
@ -150,12 +62,12 @@ func TestValidateRoot(t *testing.T) {
|
||||||
|
|
||||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||||
// encoded certificate, and have no other certificates for this CN
|
// encoded certificate, and have no other certificates for this CN
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will fail since we are passing in a dnsName that
|
// This call to ValidateRoot will fail since we are passing in a dnsName that
|
||||||
// doesn't match the CN of the certificate.
|
// doesn't match the CN of the certificate.
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{})
|
_, err = ValidateRoot(nil, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{})
|
||||||
require.Error(t, err, "An error was expected")
|
require.Error(t, err, "An error was expected")
|
||||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||||
|
|
||||||
|
@ -169,7 +81,7 @@ func TestValidateRoot(t *testing.T) {
|
||||||
// Unmarshal our signedroot
|
// Unmarshal our signedroot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||||
require.Error(t, err, "illegal base64 data at input byte")
|
require.Error(t, err, "illegal base64 data at input byte")
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -182,7 +94,7 @@ func TestValidateRoot(t *testing.T) {
|
||||||
// Unmarshal our signedroot
|
// Unmarshal our signedroot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||||
require.Error(t, err, "An error was expected")
|
require.Error(t, err, "An error was expected")
|
||||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||||
|
|
||||||
|
@ -197,7 +109,7 @@ func TestValidateRoot(t *testing.T) {
|
||||||
// Unmarshal our signedroot
|
// Unmarshal our signedroot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{})
|
||||||
require.Error(t, err, "An error was expected")
|
require.Error(t, err, "An error was expected")
|
||||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||||
|
|
||||||
|
@ -216,7 +128,7 @@ func TestValidateRoot(t *testing.T) {
|
||||||
// Unmarshal our signedroot
|
// Unmarshal our signedroot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com", TrustPinConfig{})
|
_, err = ValidateRoot(nil, &testSignedRoot, "secure.example.com", TrustPinConfig{})
|
||||||
require.Error(t, err, "An error was expected")
|
require.Error(t, err, "An error was expected")
|
||||||
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"})
|
||||||
}
|
}
|
||||||
|
@ -230,14 +142,6 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
// Create a X509Store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Execute our template
|
// Execute our template
|
||||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||||
|
@ -246,7 +150,7 @@ func TestValidateRootWithoutTOFUS(t *testing.T) {
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
|
||||||
// This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs
|
// This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,31 +163,25 @@ func TestValidateRootWithPinnedCert(t *testing.T) {
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
// Create a X509Store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Execute our template
|
// Execute our template
|
||||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||||
|
|
||||||
// Unmarshal our signedroot
|
// Unmarshal our signedroot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot should succeed with the correct Cert ID (same as root public key ID)
|
// This call to ValidateRoot should succeed with the correct Cert ID (same as root public key ID)
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true})
|
validatedSignedRoot, err := ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
generateRootKeyIDs(typedSignedRoot)
|
||||||
// purge the cert store to check another valid certs trust pinning
|
require.Equal(t, validatedSignedRoot, typedSignedRoot)
|
||||||
certStore.RemoveAll()
|
|
||||||
|
|
||||||
// This call to ValidateRoot should also succeed with the correct Cert ID (same as root public key ID), even though we passed an extra bad one
|
// This call to ValidateRoot should also succeed with the correct Cert ID (same as root public key ID), even though we passed an extra bad one
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true})
|
validatedSignedRoot, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, validatedSignedRoot, typedSignedRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
|
func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
|
||||||
|
@ -436,19 +334,15 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
|
||||||
err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil)
|
err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
typedSignedRoot, err := data.RootFromSigned(signedRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
// Create a X509Store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = ValidateRoot(
|
validatedRoot, err := ValidateRoot(
|
||||||
certStore,
|
nil,
|
||||||
signedRoot,
|
signedRoot,
|
||||||
"docker.io/notary/test",
|
"docker.io/notary/test",
|
||||||
TrustPinConfig{
|
TrustPinConfig{
|
||||||
|
@ -459,6 +353,8 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "failed to validate certID with intermediate")
|
require.NoError(t, err, "failed to validate certID with intermediate")
|
||||||
|
generateRootKeyIDs(typedSignedRoot)
|
||||||
|
require.Equal(t, typedSignedRoot, validatedRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
|
func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
|
||||||
|
@ -470,40 +366,36 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
// Create a X509Store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Execute our template
|
// Execute our template
|
||||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||||
|
|
||||||
// Unmarshal our signedroot
|
// Unmarshal our signedroot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot should fail due to an incorrect cert ID
|
// This call to ValidateRoot should fail due to an incorrect cert ID
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot should fail due to an empty cert ID
|
// This call to ValidateRoot should fail due to an empty cert ID
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false
|
// This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets
|
// This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs
|
// This call to ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false})
|
validatedRoot, err := ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
generateRootKeyIDs(typedSignedRoot)
|
||||||
|
require.Equal(t, typedSignedRoot, validatedRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateRootWithPinnedCA(t *testing.T) {
|
func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||||
|
@ -515,44 +407,39 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||||
defer os.RemoveAll(tempBaseDir)
|
defer os.RemoveAll(tempBaseDir)
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
||||||
|
|
||||||
// Create a X509Store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate)
|
||||||
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot})
|
||||||
// Unmarshal our signedRoot
|
// Unmarshal our signedRoot
|
||||||
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
|
||||||
|
typedSignedRoot, err := data.RootFromSigned(&testSignedRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will fail because we have an invalid path for the CA
|
// This call to ValidateRoot will fail because we have an invalid path for the CA
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled
|
// This call to ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS
|
// This call to ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false})
|
validatedRoot, err := ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, certStore.RemoveAll())
|
generateRootKeyIDs(typedSignedRoot)
|
||||||
|
require.Equal(t, typedSignedRoot, validatedRoot)
|
||||||
|
|
||||||
// Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it
|
// Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it
|
||||||
invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca")
|
invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca")
|
||||||
require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644))
|
require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644))
|
||||||
|
|
||||||
// Using this invalid CA cert should fail on ValidateRoot
|
// Using this invalid CA cert should fail on ValidateRoot
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
validCAFilepath := "../fixtures/root-ca.crt"
|
validCAFilepath := "../fixtures/root-ca.crt"
|
||||||
|
|
||||||
// If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail
|
// If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail
|
||||||
err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate
|
// Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate
|
||||||
|
@ -601,12 +488,16 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||||
err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil)
|
err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Check that we validate correctly against a pinned CA and provided bundle
|
newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot)
|
||||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check that we validate correctly against a pinned CA and provided bundle
|
||||||
|
validatedRoot, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
|
||||||
|
require.NoError(t, err)
|
||||||
|
generateRootKeyIDs(newTypedSignedRoot)
|
||||||
|
require.Equal(t, newTypedSignedRoot, validatedRoot)
|
||||||
|
|
||||||
// Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly
|
// Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly
|
||||||
certStore.RemoveAll()
|
|
||||||
goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath)
|
goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
|
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
|
||||||
|
@ -623,10 +514,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||||
require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644))
|
require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644))
|
||||||
|
|
||||||
// Check that we validate correctly against a pinned CA and provided bundle
|
// Check that we validate correctly against a pinned CA and provided bundle
|
||||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
|
validatedRoot, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, newTypedSignedRoot, validatedRoot)
|
||||||
|
|
||||||
certStore.RemoveAll()
|
|
||||||
testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
|
testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID())
|
testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID())
|
||||||
|
@ -638,11 +529,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||||
allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem")
|
allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem")
|
||||||
require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644))
|
require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644))
|
||||||
// Now only use expired certs in the bundle, we should fail
|
// Now only use expired certs in the bundle, we should fail
|
||||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
// Add a CA cert for a that won't validate against the root leaf certificate
|
// Add a CA cert for a that won't validate against the root leaf certificate
|
||||||
certStore.RemoveAll()
|
|
||||||
testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
|
testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID())
|
testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID())
|
||||||
|
@ -653,7 +543,7 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
|
bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem")
|
||||||
require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644))
|
require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644))
|
||||||
err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true})
|
_, err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,62 +556,43 @@ func TestValidateSuccessfulRootRotation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a X509Store in a temporary directory and returns the
|
|
||||||
// store and certificates for two keys which have been added to the keystore.
|
|
||||||
// Also returns the temporary directory so it can be cleaned up.
|
|
||||||
func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
|
|
||||||
string, trustmanager.X509Store, *cryptoservice.CryptoService, []*x509.Certificate) {
|
|
||||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
||||||
require.NoError(t, err, "failed to create a temporary directory: %s", err)
|
|
||||||
|
|
||||||
fileKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cryptoService := cryptoservice.NewCryptoService(fileKeyStore)
|
|
||||||
|
|
||||||
// Create a store
|
|
||||||
trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir)
|
|
||||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
|
||||||
trustPath,
|
|
||||||
trustmanager.FilterCertsExpiredSha1,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
certificates := make([]*x509.Certificate, 2)
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
pubKey, err := cryptoService.Create("root", gun, keyAlg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
key, _, err := fileKeyStore.GetKey(pubKey.ID())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cert, err := generateTestingCertificate(key, gun)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
certificates[i] = cert
|
|
||||||
}
|
|
||||||
return tempBaseDir, certStore, cryptoService, certificates
|
|
||||||
}
|
|
||||||
|
|
||||||
func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string) {
|
func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string) {
|
||||||
// The gun to test
|
// The gun to test
|
||||||
gun := "docker.com/notary"
|
gun := "docker.com/notary"
|
||||||
|
|
||||||
tempBaseDir, certStore, cs, certificates := filestoreWithTwoCerts(t, gun, keyAlg)
|
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
|
||||||
defer os.RemoveAll(tempBaseDir)
|
cs := cryptoservice.NewCryptoService(memKeyStore)
|
||||||
origRootCert := certificates[0]
|
|
||||||
replRootCert := certificates[1]
|
|
||||||
|
|
||||||
// Add the old root cert part of trustedCertificates
|
|
||||||
certStore.AddCert(origRootCert)
|
|
||||||
|
|
||||||
// We need the PEM representation of the replacement key to put it into the TUF data
|
|
||||||
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
|
|
||||||
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
|
|
||||||
|
|
||||||
// Tuf key with PEM-encoded x509 certificate
|
// Tuf key with PEM-encoded x509 certificate
|
||||||
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
|
origRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
|
||||||
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
origTestRoot, err := data.NewRoot(
|
||||||
|
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
|
||||||
|
map[string]*data.RootRole{
|
||||||
|
data.CanonicalRootRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalTargetsRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalSnapshotRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalTimestampRole: &origRootRole.RootRole,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "Failed to create new root")
|
||||||
|
|
||||||
|
signedOrigTestRoot, err := origTestRoot.ToSigned()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Tuf key with PEM-encoded x509 certificate
|
||||||
|
replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -743,15 +614,15 @@ func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string
|
||||||
err = signed.Sign(cs, signedTestRoot, []data.PublicKey{replRootKey, origRootKey}, 2, nil)
|
err = signed.Sign(cs, signedTestRoot, []data.PublicKey{replRootKey, origRootKey}, 2, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
typedSignedRoot, err := data.RootFromSigned(signedTestRoot)
|
||||||
// encoded certificate, and have no other certificates for this CN
|
|
||||||
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Finally, validate the only trusted certificate that exists is the new one
|
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||||
certificates = certStore.GetCertificates()
|
// encoded certificate, and have no other certificates for this CN
|
||||||
require.Len(t, certificates, 1)
|
validatedRoot, err := ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
|
||||||
require.Equal(t, certificates[0], replRootCert)
|
require.NoError(t, err)
|
||||||
|
generateRootKeyIDs(typedSignedRoot)
|
||||||
|
require.Equal(t, typedSignedRoot, validatedRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation
|
// TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation
|
||||||
|
@ -767,20 +638,39 @@ func TestValidateRootRotationMissingOrigSig(t *testing.T) {
|
||||||
func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType string) {
|
func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType string) {
|
||||||
gun := "docker.com/notary"
|
gun := "docker.com/notary"
|
||||||
|
|
||||||
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts(
|
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
|
||||||
t, gun, keyAlg)
|
cs := cryptoservice.NewCryptoService(memKeyStore)
|
||||||
defer os.RemoveAll(tempBaseDir)
|
|
||||||
origRootCert := certificates[0]
|
|
||||||
replRootCert := certificates[1]
|
|
||||||
|
|
||||||
// Add the old root cert part of trustedCertificates
|
|
||||||
certStore.AddCert(origRootCert)
|
|
||||||
|
|
||||||
// We need the PEM representation of the replacement key to put it into the TUF data
|
|
||||||
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
|
|
||||||
|
|
||||||
// Tuf key with PEM-encoded x509 certificate
|
// Tuf key with PEM-encoded x509 certificate
|
||||||
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert)
|
origRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
origTestRoot, err := data.NewRoot(
|
||||||
|
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
|
||||||
|
map[string]*data.RootRole{
|
||||||
|
data.CanonicalRootRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalTargetsRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalSnapshotRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalTimestampRole: &origRootRole.RootRole,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "Failed to create new root")
|
||||||
|
|
||||||
|
signedOrigTestRoot, err := origTestRoot.ToSigned()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Tuf key with PEM-encoded x509 certificate
|
||||||
|
replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -801,19 +691,13 @@ func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType st
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// We only sign with the new key, and not with the original one.
|
// We only sign with the new key, and not with the original one.
|
||||||
err = signed.Sign(cryptoService, signedTestRoot, []data.PublicKey{replRootKey}, 1, nil)
|
err = signed.Sign(cs, signedTestRoot, []data.PublicKey{replRootKey}, 1, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||||
// encoded certificate, and have no other certificates for this CN
|
// encoded certificate, and have no other certificates for this CN
|
||||||
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
|
_, err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
|
||||||
require.Error(t, err, "insuficient signatures on root")
|
require.Error(t, err, "insuficient signatures on root")
|
||||||
|
|
||||||
// Finally, validate the only trusted certificate that exists is still
|
|
||||||
// the old one
|
|
||||||
certificates = certStore.GetCertificates()
|
|
||||||
require.Len(t, certificates, 1)
|
|
||||||
require.Equal(t, certificates[0], origRootCert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestValidateRootRotationMissingNewSig runs through a full root certificate rotation
|
// TestValidateRootRotationMissingNewSig runs through a full root certificate rotation
|
||||||
|
@ -829,22 +713,39 @@ func TestValidateRootRotationMissingNewSig(t *testing.T) {
|
||||||
func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) {
|
func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) {
|
||||||
gun := "docker.com/notary"
|
gun := "docker.com/notary"
|
||||||
|
|
||||||
tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts(
|
memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
|
||||||
t, gun, keyAlg)
|
cs := cryptoservice.NewCryptoService(memKeyStore)
|
||||||
defer os.RemoveAll(tempBaseDir)
|
|
||||||
origRootCert := certificates[0]
|
|
||||||
replRootCert := certificates[1]
|
|
||||||
|
|
||||||
// Add the old root cert part of trustedCertificates
|
|
||||||
certStore.AddCert(origRootCert)
|
|
||||||
|
|
||||||
// We need the PEM representation of the replacement key to put it into the TUF data
|
|
||||||
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
|
|
||||||
replRootPEMCert := trustmanager.CertToPEM(replRootCert)
|
|
||||||
|
|
||||||
// Tuf key with PEM-encoded x509 certificate
|
// Tuf key with PEM-encoded x509 certificate
|
||||||
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
|
origRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
|
||||||
replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
origRootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{origRootKey.ID()}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
origTestRoot, err := data.NewRoot(
|
||||||
|
map[string]data.PublicKey{origRootKey.ID(): origRootKey},
|
||||||
|
map[string]*data.RootRole{
|
||||||
|
data.CanonicalRootRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalTargetsRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalSnapshotRole: &origRootRole.RootRole,
|
||||||
|
data.CanonicalTimestampRole: &origRootRole.RootRole,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
require.NoError(t, err, "Failed to create new root")
|
||||||
|
|
||||||
|
signedOrigTestRoot, err := origTestRoot.ToSigned()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Tuf key with PEM-encoded x509 certificate
|
||||||
|
replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -865,19 +766,13 @@ func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType str
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// We only sign with the old key, and not with the new one
|
// We only sign with the old key, and not with the new one
|
||||||
err = signed.Sign(cryptoService, signedTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
err = signed.Sign(cs, signedTestRoot, []data.PublicKey{origRootKey}, 1, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// This call to ValidateRoot will succeed since we are using a valid PEM
|
// This call to ValidateRoot will succeed since we are using a valid PEM
|
||||||
// encoded certificate, and have no other certificates for this CN
|
// encoded certificate, and have no other certificates for this CN
|
||||||
err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{})
|
_, err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{})
|
||||||
require.Error(t, err, "insuficient signatures on root")
|
require.Error(t, err, "insuficient signatures on root")
|
||||||
|
|
||||||
// Finally, validate the only trusted certificate that exists is still
|
|
||||||
// the old one
|
|
||||||
certificates = certStore.GetCertificates()
|
|
||||||
require.Len(t, certificates, 1)
|
|
||||||
require.Equal(t, certificates[0], origRootCert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) {
|
func generateTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) {
|
||||||
|
@ -889,3 +784,12 @@ func generateExpiredTestingCertificate(rootKey data.PrivateKey, gun string) (*x5
|
||||||
startTime := time.Now().AddDate(-10, 0, 0)
|
startTime := time.Now().AddDate(-10, 0, 0)
|
||||||
return cryptoservice.GenerateCertificate(rootKey, gun, startTime, startTime.AddDate(1, 0, 0))
|
return cryptoservice.GenerateCertificate(rootKey, gun, startTime, startTime.AddDate(1, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function for explicitly generating key IDs and unexported fields for equality testing
|
||||||
|
func generateRootKeyIDs(r *data.SignedRoot) {
|
||||||
|
for _, keyID := range r.Signed.Roles[data.CanonicalRootRole].KeyIDs {
|
||||||
|
if k, ok := r.Signed.Keys[keyID]; ok {
|
||||||
|
_ = k.ID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package trustpinning
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/utils"
|
"github.com/docker/notary/tuf/utils"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -67,17 +68,12 @@ func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker,
|
||||||
func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||||
// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
|
// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
|
||||||
// in order to get the matching id in the root file
|
// in order to get the matching id in the root file
|
||||||
leafCertID, err := trustmanager.FingerprintCert(leafCert)
|
key, err := trustmanager.CertBundleToKey(leafCert, intCerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Debug("error creating cert bundle: ", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
rootKeys := trustmanager.CertsToKeys([]*x509.Certificate{leafCert}, map[string][]*x509.Certificate{leafCertID: intCerts})
|
return utils.StrSliceContains(t.pinnedCertIDs, key.ID())
|
||||||
for keyID := range rootKeys {
|
|
||||||
if utils.StrSliceContains(t.pinnedCertIDs, keyID) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package testutils
|
package testutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
@ -19,8 +20,8 @@ import (
|
||||||
|
|
||||||
// CreateKey creates a new key inside the cryptoservice for the given role and gun,
|
// CreateKey creates a new key inside the cryptoservice for the given role and gun,
|
||||||
// returning the public key. If the role is a root role, create an x509 key.
|
// returning the public key. If the role is a root role, create an x509 key.
|
||||||
func CreateKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error) {
|
func CreateKey(cs signed.CryptoService, gun, role, keyAlgorithm string) (data.PublicKey, error) {
|
||||||
key, err := cs.Create(role, gun, data.ECDSAKey)
|
key, err := cs.Create(role, gun, keyAlgorithm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -36,7 +37,17 @@ func CreateKey(cs signed.CryptoService, gun, role string) (data.PublicKey, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert))
|
// Keep the x509 key type consistent with the key's algorithm
|
||||||
|
switch keyAlgorithm {
|
||||||
|
case data.RSAKey:
|
||||||
|
key = data.NewRSAx509PublicKey(trustmanager.CertToPEM(cert))
|
||||||
|
case data.ECDSAKey:
|
||||||
|
key = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert))
|
||||||
|
default:
|
||||||
|
// This should be impossible because of the Create() call above, but just in case
|
||||||
|
return nil, fmt.Errorf("invalid key algorithm type")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
@ -50,7 +61,7 @@ func EmptyRepo(gun string, delegationRoles ...string) (*tuf.Repo, signed.CryptoS
|
||||||
|
|
||||||
baseRoles := map[string]data.BaseRole{}
|
baseRoles := map[string]data.BaseRole{}
|
||||||
for _, role := range data.BaseRoles {
|
for _, role := range data.BaseRoles {
|
||||||
key, err := CreateKey(cs, gun, role)
|
key, err := CreateKey(cs, gun, role, data.ECDSAKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -77,7 +88,7 @@ func EmptyRepo(gun string, delegationRoles ...string) (*tuf.Repo, signed.CryptoS
|
||||||
sort.Strings(delegationRoles)
|
sort.Strings(delegationRoles)
|
||||||
for _, delgName := range delegationRoles {
|
for _, delgName := range delegationRoles {
|
||||||
// create a delegations key and a delegation in the tuf repo
|
// create a delegations key and a delegation in the tuf repo
|
||||||
delgKey, err := CreateKey(cs, gun, delgName)
|
delgKey, err := CreateKey(cs, gun, delgName, data.ECDSAKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,7 +269,7 @@ func (m *MetadataSwizzler) SignMetadataWithInvalidKey(role string) error {
|
||||||
|
|
||||||
// create an invalid key, but not in the existing CryptoService
|
// create an invalid key, but not in the existing CryptoService
|
||||||
cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("")))
|
cs := cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("")))
|
||||||
key, err := CreateKey(cs, m.Gun, role)
|
key, err := CreateKey(cs, m.Gun, role, data.ECDSAKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -466,7 +466,7 @@ func (m *MetadataSwizzler) RotateKey(role string, key data.PublicKey) error {
|
||||||
// ChangeRootKey swaps out the root key with a new key, and re-signs the metadata
|
// ChangeRootKey swaps out the root key with a new key, and re-signs the metadata
|
||||||
// with the new key
|
// with the new key
|
||||||
func (m *MetadataSwizzler) ChangeRootKey() error {
|
func (m *MetadataSwizzler) ChangeRootKey() error {
|
||||||
key, err := CreateKey(m.CryptoService, m.Gun, data.CanonicalRootRole)
|
key, err := CreateKey(m.CryptoService, m.Gun, data.CanonicalRootRole, data.ECDSAKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue