Foundation for removing certstore

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-04-21 11:18:35 -07:00
parent dc445b4a3a
commit 9da40f07da
3 changed files with 134 additions and 184 deletions

View File

@ -812,7 +812,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 +833,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 +868,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,7 +878,25 @@ 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
// to ensure that we consider old roots when validating this new one
var prevRoot *data.SignedRoot
if fromRemote {
prevRootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
if err == nil {
prevSignedRoot := &data.Signed{}
err = json.Unmarshal(prevRootJSON, prevSignedRoot)
if err == nil {
prevRoot, err = data.RootFromSigned(prevSignedRoot)
}
}
}
// If we had any errors while trying to retrieve the previous root, just set it to nil
if err != nil {
prevRoot = nil
}
err = trustpinning.ValidateRoot(prevRoot, root, r.gun, r.trustPinning)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -86,7 +86,7 @@ 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) 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 {
@ -100,30 +100,23 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
// 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 &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
} }
// Retrieve all the trusted certificates that match this gun // Retrieve all the trusted certificates from our previous root
trustedCerts, err := certStore.GetCertificatesByCN(gun) // Note that we do not validate expiries here since our originally trusted root might have expired certs
if err != nil { allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
// If the error that we get back is different than ErrNoCertificatesFound trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
// we couldn't check if there are any certificates with this CN already
// trusted. Let's take the conservative approach and return a failed validation
if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err)
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
}
}
// If we have certificates that match this specific GUN, let's make sure to // 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. // use them first to validate that this new root is valid.
if len(trustedCerts) != 0 { if len(trustedLeafCerts) != 0 {
logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun, logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun,
prettyFormatCertIDs(trustedCerts)) prettyFormatCertIDs(trustedLeafCerts))
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: 1})
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 &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}
@ -162,49 +155,14 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
return &ErrValidationFail{Reason: "failed to validate integrity of roots"} return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
} }
// Getting here means:
// A) we had trusted certificates and both the old and new validated this root.
// or
// B) we had no trusted certificates but the new set of certificates has integrity (self-signed).
logrus.Debugf("entering root certificate rotation for: %s", gun)
// Do root certificate rotation: we trust only the certs present in the new root
// First we add all the new certificates (even if they already exist)
for _, cert := range certsFromRoot {
err := certStore.AddCert(cert)
if err != nil {
// If the error is already exists we don't fail the rotation
if _, ok := err.(*trustmanager.ErrCertExists); ok {
logrus.Debugf("ignoring certificate addition to: %s", gun)
continue
}
logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
}
}
// Now we delete old certificates that aren't present in the new root
oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot)
if err != nil {
logrus.Debugf("inconsistency when removing old certificates: %v", err)
return err
}
for certID, cert := range oldCertsToRemove {
logrus.Debugf("removing certificate with certID: %s", certID)
err = certStore.RemoveCert(cert)
if err != nil {
logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
}
}
logrus.Debugf("Root validation succeeded for %s", gun) logrus.Debugf("Root validation succeeded for %s", gun)
return nil return 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) ([]*x509.Certificate, error) {
var validLeafCerts []*x509.Certificate var validLeafCerts []*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
@ -215,8 +173,8 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) (
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
} }
@ -246,6 +204,10 @@ 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)

View File

@ -18,7 +18,6 @@ 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"
@ -133,14 +132,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 +141,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 +160,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 +173,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 +188,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 +207,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 +221,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 +229,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,14 +242,6 @@ 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})
@ -275,14 +250,11 @@ func TestValidateRootWithPinnedCert(t *testing.T) {
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
// 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}) 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)
// purge the cert store to check another valid certs trust pinning
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}) 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)
} }
@ -439,16 +411,9 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) {
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( err = ValidateRoot(
certStore, nil,
signedRoot, signedRoot,
"docker.io/notary/test", "docker.io/notary/test",
TrustPinConfig{ TrustPinConfig{
@ -470,14 +435,6 @@ 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})
@ -486,23 +443,23 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) {
json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot)
// 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}) 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)
} }
@ -515,44 +472,35 @@ 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)
// 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}) 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())
// 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
@ -602,11 +550,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// 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": validCAFilepath}, DisableTOFU: true}) err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true})
require.NoError(t, err) require.NoError(t, err)
// 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 +570,9 @@ 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}) err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true})
require.NoError(t, err) require.NoError(t, err)
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 +584,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 +598,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,11 +611,10 @@ func TestValidateSuccessfulRootRotation(t *testing.T) {
} }
} }
// Generates a X509Store in a temporary directory and returns the // Generates certificates for two keys which have been added to the keystore.
// store and certificates for two keys which have been added to the keystore.
// Also returns the temporary directory so it can be cleaned up. // Also returns the temporary directory so it can be cleaned up.
func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) ( func generateTwoCerts(t *testing.T, gun, keyAlg string) (
string, trustmanager.X509Store, *cryptoservice.CryptoService, []*x509.Certificate) { string, *cryptoservice.CryptoService, []*x509.Certificate) {
tempBaseDir, err := ioutil.TempDir("", "notary-test-") tempBaseDir, err := ioutil.TempDir("", "notary-test-")
require.NoError(t, err, "failed to create a temporary directory: %s", err) require.NoError(t, err, "failed to create a temporary directory: %s", err)
@ -679,14 +623,6 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
cryptoService := cryptoservice.NewCryptoService(fileKeyStore) 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) certificates := make([]*x509.Certificate, 2)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
pubKey, err := cryptoService.Create("root", gun, keyAlg) pubKey, err := cryptoService.Create("root", gun, keyAlg)
@ -700,21 +636,18 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) (
certificates[i] = cert certificates[i] = cert
} }
return tempBaseDir, certStore, cryptoService, certificates return tempBaseDir, 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) tempBaseDir, cs, certificates := generateTwoCerts(t, gun, keyAlg)
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
origRootCert := certificates[0] origRootCert := certificates[0]
replRootCert := certificates[1] 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 // We need the PEM representation of the replacement key to put it into the TUF data
origRootPEMCert := trustmanager.CertToPEM(origRootCert) origRootPEMCert := trustmanager.CertToPEM(origRootCert)
replRootPEMCert := trustmanager.CertToPEM(replRootCert) replRootPEMCert := trustmanager.CertToPEM(replRootCert)
@ -745,13 +678,8 @@ func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string
// 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(nil, signedTestRoot, gun, TrustPinConfig{})
require.NoError(t, err) require.NoError(t, err)
// Finally, validate the only trusted certificate that exists is the new one
certificates = certStore.GetCertificates()
require.Len(t, certificates, 1)
require.Equal(t, certificates[0], replRootCert)
} }
// TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation // TestValidateRootRotationMissingOrigSig runs through a full root certificate rotation
@ -767,14 +695,42 @@ 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( tempBaseDir, cryptoService, certificates := generateTwoCerts(
t, gun, keyAlg) t, gun, keyAlg)
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
origRootCert := certificates[0] origRootCert := certificates[0]
replRootCert := certificates[1] replRootCert := certificates[1]
// Add the old root cert part of trustedCertificates // Set up the previous root prior to rotating
certStore.AddCert(origRootCert) // We need the PEM representation of the original key to put it into the TUF data
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
// Tuf key with PEM-encoded x509 certificate
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
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)
// We only sign with the new key, and not with the original one.
err = signed.Sign(cryptoService, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil)
require.NoError(t, err)
prevRoot, err := data.RootFromSigned(signedOrigTestRoot)
require.NoError(t, err)
// We need the PEM representation of the replacement key to put it into the TUF data // We need the PEM representation of the replacement key to put it into the TUF data
replRootPEMCert := trustmanager.CertToPEM(replRootCert) replRootPEMCert := trustmanager.CertToPEM(replRootCert)
@ -806,14 +762,8 @@ func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType st
// 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,21 +779,47 @@ 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( tempBaseDir, cryptoService, certificates := generateTwoCerts(
t, gun, keyAlg) t, gun, keyAlg)
defer os.RemoveAll(tempBaseDir) defer os.RemoveAll(tempBaseDir)
origRootCert := certificates[0] origRootCert := certificates[0]
replRootCert := certificates[1] 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 // We need the PEM representation of the replacement key to put it into the TUF data
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
replRootPEMCert := trustmanager.CertToPEM(replRootCert) replRootPEMCert := trustmanager.CertToPEM(replRootCert)
// Set up the previous root prior to rotating
// We need the PEM representation of the original key to put it into the TUF data
origRootPEMCert := trustmanager.CertToPEM(origRootCert)
// Tuf key with PEM-encoded x509 certificate // Tuf key with PEM-encoded x509 certificate
origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert) origRootKey := data.NewPublicKey(rootKeyType, origRootPEMCert)
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)
// We only sign with the new key, and not with the original one.
err = signed.Sign(cryptoService, 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 := data.NewPublicKey(rootKeyType, replRootPEMCert) replRootKey := data.NewPublicKey(rootKeyType, replRootPEMCert)
rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil)
@ -870,14 +846,8 @@ func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType str
// 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) {