diff --git a/client/client.go b/client/client.go index 19284a3219..8a7c7230c3 100644 --- a/client/client.go +++ b/client/client.go @@ -812,7 +812,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1) if cachedRootErr == nil { - signedRoot, cachedRootErr = r.validateRoot(rootJSON) + signedRoot, cachedRootErr = r.validateRoot(rootJSON, false) } remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) @@ -833,7 +833,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl if cachedRootErr != nil { // we always want to use the downloaded root if there was a cache // error. - signedRoot, err = r.validateRoot(tmpJSON) + signedRoot, err = r.validateRoot(tmpJSON, true) if err != nil { return nil, err } @@ -868,7 +868,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl // signatures of the root based on known keys, not expiry or other metadata. // This is so that an out of date root can be loaded to be used in a rotation // should the TUF update process detect a problem. -func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, error) { +func (r *NotaryRepository) validateRoot(rootJSON []byte, fromRemote bool) (*data.SignedRoot, error) { // can't just unmarshal into SignedRoot because validate root // needs the root.Signed field to still be []byte for signature // validation @@ -878,7 +878,25 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro return nil, err } - err = trustpinning.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning) + // If we're downloading a root from a remote source, attempt to load a local root + // to ensure that we consider old roots when validating this new one + var prevRoot *data.SignedRoot + if fromRemote { + prevRootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1) + 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 { return nil, err } diff --git a/trustpinning/certs.go b/trustpinning/certs.go index bcfb27201e..fc9432de40 100644 --- a/trustpinning/certs.go +++ b/trustpinning/certs.go @@ -86,7 +86,7 @@ We shall call this: TOFUS. Validation failure at any step will result in an ErrValidationFailed error. */ -func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error { +func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) error { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) 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 allLeafCerts, allIntCerts := parseAllCerts(signedRoot) - certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun) + certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true) if err != nil { logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err) return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"} } - // Retrieve all the trusted certificates that match this gun - trustedCerts, err := certStore.GetCertificatesByCN(gun) - if err != nil { - // If the error that we get back is different than ErrNoCertificatesFound - // we couldn't check if there are any certificates with this CN already - // trusted. Let's take the conservative approach and return a failed validation - if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok { - logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err) - return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"} - } - } + // Retrieve all the trusted certificates from our previous root + // Note that we do not validate expiries here since our originally trusted root might have expired certs + allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot) + trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false) // 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)) + if len(trustedLeafCerts) != 0 { + logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun, + prettyFormatCertIDs(trustedLeafCerts)) 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 { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) 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"} } - // 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) 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 // "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 // 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) continue } - // Make sure the certificate is not expired - if time.Now().After(cert.NotAfter) { + // Make sure the certificate is not expired if checkExpiry is true + if checkExpiry && time.Now().After(cert.NotAfter) { logrus.Debugf("error leaf certificate is expired") continue } @@ -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 // with all the intermediate certificates found in signedRoot func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) { + if signedRoot == nil { + return nil, nil + } + leafCerts := make(map[string]*x509.Certificate) intCerts := make(map[string][]*x509.Certificate) diff --git a/trustpinning/certs_test.go b/trustpinning/certs_test.go index 4b64778931..d2f5a1fee3 100644 --- a/trustpinning/certs_test.go +++ b/trustpinning/certs_test.go @@ -18,7 +18,6 @@ import ( "time" - "github.com/docker/notary" "github.com/docker/notary/cryptoservice" "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" @@ -133,14 +132,6 @@ func TestValidateRoot(t *testing.T) { defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) - // Create a X509Store - trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir) - certStore, err := trustmanager.NewX509FilteredFileStore( - trustPath, - trustmanager.FilterCertsExpiredSha1, - ) - require.NoError(t, err) - // Execute our template templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) @@ -150,12 +141,12 @@ func TestValidateRoot(t *testing.T) { // This call to ValidateRoot will succeed since we are using a valid PEM // encoded certificate, and have no other certificates for this CN - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) require.NoError(t, err) // This call to ValidateRoot will fail since we are passing in a dnsName that // doesn't match the CN of the certificate. - err = ValidateRoot(certStore, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{}) + err = ValidateRoot(nil, &testSignedRoot, "diogomonica.com/notary", TrustPinConfig{}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) @@ -169,7 +160,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) require.Error(t, err, "illegal base64 data at input byte") // @@ -182,7 +173,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) @@ -197,7 +188,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) @@ -216,7 +207,7 @@ func TestValidateRoot(t *testing.T) { // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) - err = ValidateRoot(certStore, &testSignedRoot, "secure.example.com", TrustPinConfig{}) + err = ValidateRoot(nil, &testSignedRoot, "secure.example.com", TrustPinConfig{}) require.Error(t, err, "An error was expected") require.Equal(t, err, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}) } @@ -230,14 +221,6 @@ func TestValidateRootWithoutTOFUS(t *testing.T) { defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) - // Create a X509Store - trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir) - certStore, err := trustmanager.NewX509FilteredFileStore( - trustPath, - trustmanager.FilterCertsExpiredSha1, - ) - require.NoError(t, err) - // Execute our template templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) @@ -246,7 +229,7 @@ func TestValidateRootWithoutTOFUS(t *testing.T) { json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) // This call to ValidateRoot will fail since we are explicitly disabling TOFU and have no local certs - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{DisableTOFU: true}) require.Error(t, err) } @@ -259,14 +242,6 @@ func TestValidateRootWithPinnedCert(t *testing.T) { defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) - // Create a X509Store - trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir) - certStore, err := trustmanager.NewX509FilteredFileStore( - trustPath, - trustmanager.FilterCertsExpiredSha1, - ) - require.NoError(t, err) - // Execute our template templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) @@ -275,14 +250,11 @@ func TestValidateRootWithPinnedCert(t *testing.T) { json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) // 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) - // 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 - 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) } @@ -439,16 +411,9 @@ func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) { tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) - // Create a X509Store - trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir) - certStore, err := trustmanager.NewX509FilteredFileStore( - trustPath, - trustmanager.FilterCertsExpiredSha1, - ) - require.NoError(t, err) err = ValidateRoot( - certStore, + nil, signedRoot, "docker.io/notary/test", TrustPinConfig{ @@ -470,14 +435,6 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) { defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) - // Create a X509Store - trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir) - certStore, err := trustmanager.NewX509FilteredFileStore( - trustPath, - trustmanager.FilterCertsExpiredSha1, - ) - require.NoError(t, err) - // Execute our template templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) @@ -486,23 +443,23 @@ func TestValidateRootFailuresWithPinnedCert(t *testing.T) { json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) // This call to ValidateRoot should fail due to an incorrect cert ID - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true}) require.Error(t, err) // This call to ValidateRoot should fail due to an empty cert ID - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true}) require.Error(t, err) // This call to ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true}) require.Error(t, err) // This call to ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true}) require.Error(t, err) // This call to ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false}) require.NoError(t, err) } @@ -515,44 +472,35 @@ func TestValidateRootWithPinnedCA(t *testing.T) { defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) - // Create a X509Store - trustPath := filepath.Join(tempBaseDir, notary.TrustedCertsDir) - certStore, err := trustmanager.NewX509FilteredFileStore( - trustPath, - trustmanager.FilterCertsExpiredSha1, - ) - require.NoError(t, err) - templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) // Unmarshal our signedRoot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) // This call to ValidateRoot will fail because we have an invalid path for the CA - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) require.Error(t, err) // This call to ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true}) require.Error(t, err) // This call to ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false}) require.NoError(t, err) - require.NoError(t, certStore.RemoveAll()) // Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca") require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644)) // Using this invalid CA cert should fail on ValidateRoot - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true}) require.Error(t, err) validCAFilepath := "../fixtures/root-ca.crt" // If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail - err = ValidateRoot(certStore, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true}) + err = ValidateRoot(nil, &testSignedRoot, "docker.com/notary", TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true}) require.Error(t, err) // Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate @@ -602,11 +550,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) { require.NoError(t, err) // Check that we validate correctly against a pinned CA and provided bundle - err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true}) + err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true}) require.NoError(t, err) // Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly - certStore.RemoveAll() goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath) require.NoError(t, err) memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) @@ -623,10 +570,9 @@ func TestValidateRootWithPinnedCA(t *testing.T) { require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644)) // Check that we validate correctly against a pinned CA and provided bundle - err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true}) + err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true}) require.NoError(t, err) - certStore.RemoveAll() testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID()) @@ -638,11 +584,10 @@ func TestValidateRootWithPinnedCA(t *testing.T) { allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem") require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644)) // Now only use expired certs in the bundle, we should fail - err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true}) + err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true}) require.Error(t, err) // Add a CA cert for a that won't validate against the root leaf certificate - certStore.RemoveAll() testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID()) @@ -653,7 +598,7 @@ func TestValidateRootWithPinnedCA(t *testing.T) { require.NoError(t, err) bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644)) - err = ValidateRoot(certStore, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true}) + err = ValidateRoot(nil, newTestSignedRoot, "notary-signer", TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true}) require.Error(t, err) } @@ -666,11 +611,10 @@ 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. +// Generates 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) { +func generateTwoCerts(t *testing.T, gun, keyAlg string) ( + string, *cryptoservice.CryptoService, []*x509.Certificate) { tempBaseDir, err := ioutil.TempDir("", "notary-test-") 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) - // 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) @@ -700,21 +636,18 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) ( certificates[i] = cert } - return tempBaseDir, certStore, cryptoService, certificates + return tempBaseDir, cryptoService, certificates } func testValidateSuccessfulRootRotation(t *testing.T, keyAlg, rootKeyType string) { // The gun to test gun := "docker.com/notary" - tempBaseDir, certStore, cs, certificates := filestoreWithTwoCerts(t, gun, keyAlg) + tempBaseDir, cs, certificates := generateTwoCerts(t, gun, keyAlg) defer os.RemoveAll(tempBaseDir) origRootCert := certificates[0] replRootCert := certificates[1] - // Add the old root cert part of trustedCertificates - certStore.AddCert(origRootCert) - // We need the PEM representation of the replacement key to put it into the TUF data origRootPEMCert := trustmanager.CertToPEM(origRootCert) replRootPEMCert := trustmanager.CertToPEM(replRootCert) @@ -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 // 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) - - // 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 @@ -767,14 +695,42 @@ func TestValidateRootRotationMissingOrigSig(t *testing.T) { func testValidateRootRotationMissingOrigSig(t *testing.T, keyAlg, rootKeyType string) { gun := "docker.com/notary" - tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts( + tempBaseDir, cryptoService, certificates := generateTwoCerts( t, gun, keyAlg) defer os.RemoveAll(tempBaseDir) origRootCert := certificates[0] replRootCert := certificates[1] - // Add the old root cert part of trustedCertificates - certStore.AddCert(origRootCert) + // 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 + 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 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 // encoded certificate, and have no other certificates for this CN - err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{}) + err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{}) require.Error(t, err, "insuficient signatures on root") - - // Finally, validate the only trusted certificate that exists is still - // the old one - certificates = certStore.GetCertificates() - require.Len(t, certificates, 1) - require.Equal(t, certificates[0], origRootCert) } // TestValidateRootRotationMissingNewSig runs through a full root certificate rotation @@ -829,21 +779,47 @@ func TestValidateRootRotationMissingNewSig(t *testing.T) { func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) { gun := "docker.com/notary" - tempBaseDir, certStore, cryptoService, certificates := filestoreWithTwoCerts( + tempBaseDir, cryptoService, certificates := generateTwoCerts( t, gun, keyAlg) defer os.RemoveAll(tempBaseDir) origRootCert := certificates[0] replRootCert := certificates[1] - // Add the old root cert part of trustedCertificates - certStore.AddCert(origRootCert) - // We need the PEM representation of the replacement key to put it into the TUF data - origRootPEMCert := trustmanager.CertToPEM(origRootCert) replRootPEMCert := trustmanager.CertToPEM(replRootCert) + // 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 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) 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 // encoded certificate, and have no other certificates for this CN - err = ValidateRoot(certStore, signedTestRoot, gun, TrustPinConfig{}) + err = ValidateRoot(prevRoot, signedTestRoot, gun, TrustPinConfig{}) require.Error(t, err, "insuficient signatures on root") - - // Finally, validate the only trusted certificate that exists is still - // the old one - certificates = certStore.GetCertificates() - require.Len(t, certificates, 1) - require.Equal(t, certificates[0], origRootCert) } func generateTestingCertificate(rootKey data.PrivateKey, gun string) (*x509.Certificate, error) {