// We cannot validate a new root if the old root is corrupt, because there might // have been a root key rotation. func TestValidateOldRootCorruptRootRole(t *testing.T) { gun := "docker.com/notary" repo, cs, err := testutils.EmptyRepo(gun) require.NoError(t, err) store := storage.NewMemStorage() r, tg, sn, ts, err := testutils.Sign(repo) require.NoError(t, err) root, targets, snapshot, timestamp, err := getUpdates(r, tg, sn, ts) require.NoError(t, err) // so a valid root, but missing the root role signedRoot, err := data.RootFromSigned(r) require.NoError(t, err) delete(signedRoot.Signed.Roles, data.CanonicalRootRole) badRootJSON, err := json.Marshal(signedRoot) require.NoError(t, err) badRoot := storage.MetaUpdate{ Version: root.Version, Role: root.Role, Data: badRootJSON, } store.UpdateCurrent(gun, badRoot) updates := []storage.MetaUpdate{root, targets, snapshot, timestamp} serverCrypto := testutils.CopyKeys(t, cs, data.CanonicalTimestampRole) _, err = validateUpdate(serverCrypto, gun, updates, store) require.Error(t, err) require.IsType(t, data.ErrInvalidMetadata{}, err) }
func TestValidateRootWithPinnedCert(t *testing.T) { var testSignedRoot data.Signed var signedRootBytes bytes.Buffer // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) // Execute our template templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) typedSignedRoot, err := data.RootFromSigned(&testSignedRoot) require.NoError(t, err) // This call to trustpinning.ValidateRoot should succeed with the correct Cert ID (same as root public key ID) validatedSignedRoot, err := trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID}}, DisableTOFU: true}) require.NoError(t, err) generateRootKeyIDs(typedSignedRoot) require.Equal(t, validatedSignedRoot, typedSignedRoot) // This call to trustpinning.ValidateRoot should also succeed with the correct Cert ID (same as root public key ID), even though we passed an extra bad one validatedSignedRoot, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {rootPubKeyID, "invalidID"}}, DisableTOFU: true}) require.NoError(t, err) require.Equal(t, validatedSignedRoot, typedSignedRoot) }
func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { var rootJSON []byte remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if err == nil { // if remote store successfully set up, try and get root from remote rootJSON, err = remote.GetMeta("root", maxSize) } // if remote store couldn't be setup, or we failed to get a root from it // load the root from cache (offline operation) if err != nil { if err, ok := err.(store.ErrMetaNotFound); ok { // if the error was MetaNotFound then we successfully contacted // the store and it doesn't know about the repo. return nil, err } result, cacheErr := r.fileStore.GetMeta("root", maxSize) if cacheErr != nil { // if cache didn't return a root, we cannot proceed - just return // the original error. return nil, err } rootJSON = result logrus.Debugf( "Using local cache instead of remote due to failure: %s", err.Error()) } // can't just unmarshal into SignedRoot because validate root // needs the root.Signed field to still be []byte for signature // validation root := &data.Signed{} err = json.Unmarshal(rootJSON, root) if err != nil { return nil, err } err = r.CertManager.ValidateRoot(root, r.gun) if err != nil { return nil, err } kdb := keys.NewDB() r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) signedRoot, err := data.RootFromSigned(root) if err != nil { return nil, err } err = r.tufRepo.SetRoot(signedRoot) if err != nil { return nil, err } return tufclient.NewClient( r.tufRepo, remote, kdb, r.fileStore, ), nil }
// RotateKey rotates the key for a role - this can invalidate that role's metadata // if it is not signed by that key. Particularly if the key being rotated is the // root key, because it is not signed by the new key, only the old key. func (m *MetadataSwizzler) RotateKey(role string, key data.PublicKey) error { roleSpecifier := data.CanonicalRootRole if data.IsDelegation(role) { roleSpecifier = path.Dir(role) } b, err := m.MetadataCache.GetSized(roleSpecifier, store.NoSizeLimit) if err != nil { return err } signedThing := &data.Signed{} if err := json.Unmarshal(b, signedThing); err != nil { return err } // get keys before the keys are rotated pubKeys, err := getPubKeys(m.CryptoService, signedThing, roleSpecifier) if err != nil { return err } if roleSpecifier == data.CanonicalRootRole { signedRoot, err := data.RootFromSigned(signedThing) if err != nil { return err } signedRoot.Signed.Roles[role].KeyIDs = []string{key.ID()} signedRoot.Signed.Keys[key.ID()] = key if signedThing, err = signedRoot.ToSigned(); err != nil { return err } } else { signedTargets, err := data.TargetsFromSigned(signedThing, roleSpecifier) if err != nil { return err } for _, roleObject := range signedTargets.Signed.Delegations.Roles { if roleObject.Name == role { roleObject.KeyIDs = []string{key.ID()} break } } signedTargets.Signed.Delegations.Keys[key.ID()] = key if signedThing, err = signedTargets.ToSigned(); err != nil { return err } } metaBytes, err := serializeMetadata(m.CryptoService, signedThing, roleSpecifier, pubKeys...) if err != nil { return err } return m.MetadataCache.Set(roleSpecifier, metaBytes) }
// SetThreshold sets a threshold for a metadata role - can invalidate metadata for which // the threshold is increased, if there aren't enough signatures or can be invalid because // the threshold is 0 func (m *MetadataSwizzler) SetThreshold(role string, newThreshold int) error { roleSpecifier := data.CanonicalRootRole if data.IsDelegation(role) { roleSpecifier = path.Dir(role) } b, err := m.MetadataCache.GetSized(roleSpecifier, store.NoSizeLimit) if err != nil { return err } signedThing := &data.Signed{} if err := json.Unmarshal(b, signedThing); err != nil { return err } if roleSpecifier == data.CanonicalRootRole { signedRoot, err := data.RootFromSigned(signedThing) if err != nil { return err } signedRoot.Signed.Roles[role].Threshold = newThreshold if signedThing, err = signedRoot.ToSigned(); err != nil { return err } } else { signedTargets, err := data.TargetsFromSigned(signedThing, roleSpecifier) if err != nil { return err } for _, roleObject := range signedTargets.Signed.Delegations.Roles { if roleObject.Name == role { roleObject.Threshold = newThreshold break } } if signedThing, err = signedTargets.ToSigned(); err != nil { return err } } var metaBytes []byte pubKeys, err := getPubKeys(m.CryptoService, signedThing, roleSpecifier) if err == nil { metaBytes, err = serializeMetadata(m.CryptoService, signedThing, roleSpecifier, pubKeys...) } if err != nil { return err } return m.MetadataCache.Set(roleSpecifier, metaBytes) }
func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error { // this will confirm that the root has been signed by the old root role // with the root keys we bootstrapped with. // Still need to determine if there has been a root key update and // confirm signature with new root key logrus.Debug("verifying root with existing keys") rootRole, err := c.local.GetBaseRole(role) if err != nil { logrus.Debug("no previous root role loaded") return err } // Verify using the rootRole loaded from the known root.json if err = signed.Verify(s, rootRole, minVersion); err != nil { logrus.Debug("root did not verify with existing keys") return err } logrus.Debug("updating known root roles and keys") root, err := data.RootFromSigned(s) if err != nil { logrus.Error(err.Error()) return err } // replace the existing root.json with the new one (just in memory, we // have another validation step before we fully accept the new root) err = c.local.SetRoot(root) if err != nil { logrus.Error(err.Error()) return err } // Verify the new root again having loaded the rootRole out of this new // file (verifies self-referential integrity) // TODO(endophage): be more intelligent and only re-verify if we detect // there has been a change in root keys logrus.Debug("verifying root with updated keys") rootRole, err = c.local.GetBaseRole(role) if err != nil { logrus.Debug("root role with new keys not loaded") return err } err = signed.Verify(s, rootRole, minVersion) if err != nil { logrus.Debug("root did not verify with new keys") return err } logrus.Debug("successfully verified root") return nil }
// validateRoot MUST only be used during bootstrapping. It will only validate // 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) { // can't just unmarshal into SignedRoot because validate root // needs the root.Signed field to still be []byte for signature // validation root := &data.Signed{} err := json.Unmarshal(rootJSON, root) if err != nil { return nil, err } err = r.CertManager.ValidateRoot(root, r.gun) if err != nil { return nil, err } return data.RootFromSigned(root) }
func TestValidateRootRotation(t *testing.T) { repo, crypto, err := testutils.EmptyRepo("docker.com/notary") assert.NoError(t, err) store := storage.NewMemStorage() r, tg, sn, ts, err := testutils.Sign(repo) assert.NoError(t, err) root, targets, snapshot, timestamp, err := getUpdates(r, tg, sn, ts) assert.NoError(t, err) store.UpdateCurrent("testGUN", root) oldRootRole := repo.Root.Signed.Roles["root"] oldRootKey := repo.Root.Signed.Keys[oldRootRole.KeyIDs[0]] rootKey, err := crypto.Create("root", data.ED25519Key) assert.NoError(t, err) rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil) assert.NoError(t, err) delete(repo.Root.Signed.Keys, oldRootRole.KeyIDs[0]) repo.Root.Signed.Roles["root"] = &rootRole.RootRole repo.Root.Signed.Keys[rootKey.ID()] = rootKey r, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) assert.NoError(t, err) err = signed.Sign(crypto, r, rootKey, oldRootKey) assert.NoError(t, err) rt, err := data.RootFromSigned(r) assert.NoError(t, err) repo.SetRoot(rt) sn, err = repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole)) assert.NoError(t, err) root, targets, snapshot, timestamp, err = getUpdates(r, tg, sn, ts) assert.NoError(t, err) updates := []storage.MetaUpdate{root, targets, snapshot, timestamp} copyTimestampKey(t, repo, store, "testGUN") _, err = validateUpdate(crypto, "testGUN", updates, store) assert.NoError(t, err) }
func TestValidateRootFailuresWithPinnedCert(t *testing.T) { var testSignedRoot data.Signed var signedRootBytes bytes.Buffer // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) // Execute our template templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) // Unmarshal our signedroot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) typedSignedRoot, err := data.RootFromSigned(&testSignedRoot) require.NoError(t, err) // This call to trustpinning.ValidateRoot should fail due to an incorrect cert ID _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"ABSOLUTELY NOT A CERT ID"}}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot should fail due to an empty cert ID _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {""}}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot should fail due to an invalid GUN (even though the cert ID is correct), and TOFUS is set to false _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot should fail due to an invalid cert ID, even though it's a valid key ID for targets _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {targetsPubKeyID}}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot should succeed because we fall through to TOFUS because we have no matching GUNs under Certs validatedRoot, err := trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"not_a_gun": {rootPubKeyID}}, DisableTOFU: false}) require.NoError(t, err) generateRootKeyIDs(typedSignedRoot) require.Equal(t, typedSignedRoot, validatedRoot) }
func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error { // this will confirm that the root has been signed by the old root role // as c.keysDB contains the root keys we bootstrapped with. // Still need to determine if there has been a root key update and // confirm signature with new root key logrus.Debug("verifying root with existing keys") err := signed.Verify(s, role, minVersion, c.keysDB) if err != nil { logrus.Debug("root did not verify with existing keys") return err } // This will cause keyDB to get updated, overwriting any keyIDs associated // with the roles in root.json logrus.Debug("updating known root roles and keys") root, err := data.RootFromSigned(s) if err != nil { logrus.Error(err.Error()) return err } err = c.local.SetRoot(root) if err != nil { logrus.Error(err.Error()) return err } // verify again now that the old keys have been replaced with the new keys. // TODO(endophage): be more intelligent and only re-verify if we detect // there has been a change in root keys logrus.Debug("verifying root with updated keys") err = signed.Verify(s, role, minVersion, c.keysDB) if err != nil { logrus.Debug("root did not verify with new keys") return err } logrus.Debug("successfully verified root") return nil }
/* ValidateRoot receives a new root, validates its correctness and attempts to do root key rotation if needed. First we list the current trusted certificates we have for a particular GUN. If that list is non-empty means that we've already seen this repository before, and have a list of trusted certificates for it. In this case, we use this list of certificates to attempt to validate this root file. If the previous validation succeeds, we check the integrity of the root by making sure that it is validated by itself. This means that we will attempt to validate the root data with the certificates that are included in the root keys themselves. However, if we do not have any current trusted certificates for this GUN, we check if there are any pinned certificates specified in the trust_pinning section of the notary client config. If this section specifies a Certs section with this GUN, we attempt to validate that the certificates present in the downloaded root file match the pinned ID. If the Certs section is empty for this GUN, we check if the trust_pinning section specifies a CA section specified in the config for this GUN. If so, we check that the specified CA is valid and has signed a certificate included in the downloaded root file. The specified CA can be a prefix for this GUN. If both the Certs and CA configs do not match this GUN, we fall back to the TOFU section in the config: if true, we trust certificates specified in the root for this GUN. If later we see a different certificate for that certificate, we return an ErrValidationFailed error. Note that since we only allow trust data to be downloaded over an HTTPS channel we are using the current public PKI to validate the first download of the certificate adding an extra layer of security over the normal (SSH style) trust model. 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 { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { return err } rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole) if err != nil { return err } // Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN allLeafCerts, allIntCerts := parseAllCerts(signedRoot) certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun) 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"} } } // If we have certificates that match this specific GUN, let's make sure to // use them first to validate that this new root is valid. if len(trustedCerts) != 0 { logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun, prettyFormatCertIDs(trustedCerts)) err = signed.VerifySignatures( root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1}) 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"} } } else { logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun) trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun) if err != nil { return &ErrValidationFail{Reason: err.Error()} } validPinnedCerts := []*x509.Certificate{} for _, cert := range certsFromRoot { certID, err := trustmanager.FingerprintCert(cert) if err != nil { continue } if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok { continue } validPinnedCerts = append(validPinnedCerts, cert) } if len(validPinnedCerts) == 0 { return &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"} } certsFromRoot = validPinnedCerts } // Validate the integrity of the new root (does it have valid signatures) // Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS // If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly err = signed.VerifySignatures(root, data.BaseRole{ Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return &ErrValidationFail{Reason: "failed to validate integrity of roots"} } // Getting here means: // A) we had trusted certificates and both the old and new validated this root. // or // B) we had no trusted certificates but the new set of certificates has integrity (self-signed). logrus.Debugf("entering root certificate rotation for: %s", gun) // Do root certificate rotation: we trust only the certs present in the new root // First we add all the new certificates (even if they already exist) for _, cert := range certsFromRoot { err := certStore.AddCert(cert) if err != nil { // If the error is already exists we don't fail the rotation if _, ok := err.(*trustmanager.ErrCertExists); ok { logrus.Debugf("ignoring certificate addition to: %s", gun) continue } logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err) } } // Now we delete old certificates that aren't present in the new root oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot) if err != nil { logrus.Debugf("inconsistency when removing old certificates: %v", err) return err } for certID, cert := range oldCertsToRemove { logrus.Debugf("removing certificate with certID: %s", certID) err = certStore.RemoveCert(cert) if err != nil { logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err) return &ErrRootRotationFail{Reason: "failed to rotate root keys"} } } logrus.Debugf("Root validation succeeded for %s", gun) return nil }
// checkRoot returns true if no rotation, or a valid // rotation has taken place, and the threshold number of signatures // are valid. func checkRoot(oldRoot, newRoot *data.SignedRoot) error { rootRole := data.RoleName(data.CanonicalRootRole) targetsRole := data.RoleName(data.CanonicalTargetsRole) snapshotRole := data.RoleName(data.CanonicalSnapshotRole) timestampRole := data.RoleName(data.CanonicalTimestampRole) var oldRootRole *data.RootRole newRootRole, ok := newRoot.Signed.Roles[rootRole] if !ok { return errors.New("new root is missing role entry for root role") } oldThreshold := 1 rotation := false oldKeys := map[string]data.PublicKey{} newKeys := map[string]data.PublicKey{} if oldRoot != nil { // check for matching root key IDs oldRootRole = oldRoot.Signed.Roles[rootRole] oldThreshold = oldRootRole.Threshold for _, kid := range oldRootRole.KeyIDs { k, ok := oldRoot.Signed.Keys[kid] if !ok { // if the key itself wasn't contained in the root // we're skipping it because it could never have // been used to validate this root. continue } oldKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public()) } // super simple check for possible rotation rotation = len(oldKeys) != len(newRootRole.KeyIDs) } // if old and new had the same number of keys, iterate // to see if there's a difference. for _, kid := range newRootRole.KeyIDs { k, ok := newRoot.Signed.Keys[kid] if !ok { // if the key itself wasn't contained in the root // we're skipping it because it could never have // been used to validate this root. continue } newKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public()) if oldRoot != nil { if _, ok := oldKeys[kid]; !ok { // if there is any difference in keys, a key rotation may have // occurred. rotation = true } } } newSigned, err := newRoot.ToSigned() if err != nil { return err } if rotation { err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys) if err != nil { return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold) } } err = signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys) if err != nil { return err } root, err := data.RootFromSigned(newSigned) if err != nil { return err } // at a minimum, check the 4 required roles are present for _, r := range []string{rootRole, targetsRole, snapshotRole, timestampRole} { role, ok := root.Signed.Roles[r] if !ok { return fmt.Errorf("missing required %s role from root", r) } if role.Threshold < 1 { return fmt.Errorf("%s role has invalid threshold", r) } if len(role.KeyIDs) < role.Threshold { return fmt.Errorf("%s role has insufficient number of keys", r) } } return nil }
// downloadRoot is responsible for downloading the root.json func (c *Client) downloadRoot() error { role := data.RoleName("root") size := maxSize var expectedSha256 []byte if c.local.Snapshot != nil { size = c.local.Snapshot.Signed.Meta[role].Length expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"] } // if we're bootstrapping we may not have a cached root, an // error will result in the "previous root version" being // interpreted as 0. var download bool var err error var cachedRoot []byte old := &data.Signed{} version := 0 if expectedSha256 != nil { // can only trust cache if we have an expected sha256 to trust cachedRoot, err = c.cache.GetMeta(role, size) } if cachedRoot == nil || err != nil { logrus.Debug("didn't find a cached root, must download") download = true } else { hash := sha256.Sum256(cachedRoot) if !bytes.Equal(hash[:], expectedSha256) { logrus.Debug("cached root's hash didn't match expected, must download") download = true } err := json.Unmarshal(cachedRoot, old) if err == nil { root, err := data.RootFromSigned(old) if err == nil { version = root.Signed.Version } else { logrus.Debug("couldn't parse Signed part of cached root, must download") download = true } } else { logrus.Debug("couldn't parse cached root, must download") download = true } } var s *data.Signed var raw []byte if download { raw, s, err = c.downloadSigned(role, size, expectedSha256) if err != nil { return err } } else { logrus.Debug("using cached root") s = old } if err := c.verifyRoot(role, s, version); err != nil { return err } if download { logrus.Debug("caching downloaded root") // Now that we have accepted new root, write it to cache if err = c.cache.SetMeta(role, raw); err != nil { logrus.Errorf("Failed to write root to local cache: %s", err.Error()) } } return nil }
func TestValidateRootRotation(t *testing.T) { _, repo, crypto := testutils.EmptyRepo() store := storage.NewMemStorage() r, tg, sn, ts, err := testutils.Sign(repo) assert.NoError(t, err) root, targets, snapshot, timestamp, err := testutils.Serialize(r, tg, sn, ts) assert.NoError(t, err) store.UpdateCurrent( "testGUN", storage.MetaUpdate{ Role: "root", Version: 1, Data: root, }, ) oldRootRole := repo.Root.Signed.Roles["root"] oldRootKey := repo.Root.Signed.Keys[oldRootRole.KeyIDs[0]] rootKey, err := crypto.Create("root", data.ED25519Key) assert.NoError(t, err) rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) assert.NoError(t, err) delete(repo.Root.Signed.Keys, oldRootRole.KeyIDs[0]) repo.Root.Signed.Roles["root"] = &rootRole.RootRole repo.Root.Signed.Keys[rootKey.ID()] = rootKey r, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole)) assert.NoError(t, err) err = signed.Sign(crypto, r, rootKey, oldRootKey) assert.NoError(t, err) rt, err := data.RootFromSigned(r) assert.NoError(t, err) repo.SetRoot(rt) sn, err = repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole)) assert.NoError(t, err) root, targets, snapshot, timestamp, err = testutils.Serialize(r, tg, sn, ts) assert.NoError(t, err) updates := []storage.MetaUpdate{ { Role: "root", Version: 1, Data: root, }, { Role: "targets", Version: 1, Data: targets, }, { Role: "snapshot", Version: 1, Data: snapshot, }, { Role: "timestamp", Version: 1, Data: timestamp, }, } err = validateUpdate("testGUN", updates, store) assert.NoError(t, err) }
/* ValidateRoot receives a new root, validates its correctness and attempts to do root key rotation if needed. First we check if we have any trusted certificates for a particular GUN in a previous root, if we have one. If the previous root is not nil and we find certificates for this GUN, we've already seen this repository before, and have a list of trusted certificates for it. In this case, we use this list of certificates to attempt to validate this root file. If the previous validation succeeds, we check the integrity of the root by making sure that it is validated by itself. This means that we will attempt to validate the root data with the certificates that are included in the root keys themselves. However, if we do not have any current trusted certificates for this GUN, we check if there are any pinned certificates specified in the trust_pinning section of the notary client config. If this section specifies a Certs section with this GUN, we attempt to validate that the certificates present in the downloaded root file match the pinned ID. If the Certs section is empty for this GUN, we check if the trust_pinning section specifies a CA section specified in the config for this GUN. If so, we check that the specified CA is valid and has signed a certificate included in the downloaded root file. The specified CA can be a prefix for this GUN. If both the Certs and CA configs do not match this GUN, we fall back to the TOFU section in the config: if true, we trust certificates specified in the root for this GUN. If later we see a different certificate for that certificate, we return an ErrValidationFailed error. Note that since we only allow trust data to be downloaded over an HTTPS channel we are using the current public PKI to validate the first download of the certificate adding an extra layer of security over the normal (SSH style) trust model. We shall call this: TOFUS. Validation failure at any step will result in an ErrValidationFailed error. */ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trustPinning TrustPinConfig) (*data.SignedRoot, error) { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { return nil, err } rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole) if err != nil { return nil, err } // Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN allLeafCerts, allIntCerts := parseAllCerts(signedRoot) certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true) validIntCerts := validRootIntCerts(allIntCerts) if err != nil { logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err) return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"} } logrus.Debugf("found %d leaf certs, of which %d are valid leaf certs for %s", len(allLeafCerts), len(certsFromRoot), gun) // If we have a previous root, let's try to use it to validate that this new root is valid. havePrevRoot := prevRoot != nil if havePrevRoot { // 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 err != nil { return nil, &ErrValidationFail{Reason: "could not retrieve trusted certs from previous root role data"} } // Use the certificates we found in the previous root for the GUN to verify its signatures // This could potentially be an empty set, in which case we will fail to verify logrus.Debugf("found %d valid root leaf certificates for %s: %s", len(trustedLeafCerts), gun, prettyFormatCertIDs(trustedLeafCerts)) // Extract the previous root's threshold for signature verification prevRootRoleData, ok := prevRoot.Signed.Roles[data.CanonicalRootRole] if !ok { return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"} } err = signed.VerifySignatures( root, data.BaseRole{Keys: utils.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"} } // Clear the IsValid marks we could have received from VerifySignatures for i := range root.Signatures { root.Signatures[i].IsValid = false } } // Regardless of having a previous root or not, confirm that the new root validates against the trust pinning logrus.Debugf("checking root against trust_pinning config for %s", gun) trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun, !havePrevRoot) if err != nil { return nil, &ErrValidationFail{Reason: err.Error()} } validPinnedCerts := map[string]*x509.Certificate{} for id, cert := range certsFromRoot { logrus.Debugf("checking trust-pinning for cert: %s", id) if ok := trustPinCheckFunc(cert, validIntCerts[id]); !ok { logrus.Debugf("trust-pinning check failed for cert: %s", id) continue } validPinnedCerts[id] = cert } if len(validPinnedCerts) == 0 { return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"} } certsFromRoot = validPinnedCerts // Validate the integrity of the new root (does it have valid signatures) // Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS // If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly err = signed.VerifySignatures(root, data.BaseRole{ Keys: utils.CertsToKeys(certsFromRoot, validIntCerts), Threshold: rootRole.Threshold}) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"} } logrus.Debugf("root validation succeeded for %s", gun) // Call RootFromSigned to make sure we pick up on the IsValid markings from VerifySignatures return data.RootFromSigned(root) }
func TestValidateRootWithPinnerCertAndIntermediates(t *testing.T) { now := time.Now() serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) pass := func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) { return "password", false, nil } memStore := trustmanager.NewKeyMemoryStore(pass) cs := cryptoservice.NewCryptoService(memStore) // generate CA cert serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) require.NoError(t, err) caTmpl := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "notary testing CA", }, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, MaxPathLen: 3, } caPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) _, err = x509.CreateCertificate( rand.Reader, &caTmpl, &caTmpl, caPrivKey.Public(), caPrivKey, ) // generate intermediate intTmpl := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "notary testing intermediate", }, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, MaxPathLen: 2, } intPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) intCert, err := x509.CreateCertificate( rand.Reader, &intTmpl, &caTmpl, intPrivKey.Public(), caPrivKey, ) require.NoError(t, err) // generate leaf serialNumber, err = rand.Int(rand.Reader, serialNumberLimit) require.NoError(t, err) leafTmpl := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "docker.io/notary/test", }, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, BasicConstraintsValid: true, } leafPubKey, err := cs.Create("root", "docker.io/notary/test", data.ECDSAKey) require.NoError(t, err) leafPrivKey, _, err := cs.GetPrivateKey(leafPubKey.ID()) require.NoError(t, err) signer := leafPrivKey.CryptoSigner() leafCert, err := x509.CreateCertificate( rand.Reader, &leafTmpl, &intTmpl, signer.Public(), intPrivKey, ) rootBundleWriter := bytes.NewBuffer(nil) pem.Encode( rootBundleWriter, &pem.Block{ Type: "CERTIFICATE", Bytes: leafCert, }, ) pem.Encode( rootBundleWriter, &pem.Block{ Type: "CERTIFICATE", Bytes: intCert, }, ) rootBundle := rootBundleWriter.Bytes() ecdsax509Key := data.NewECDSAx509PublicKey(rootBundle) otherKey, err := cs.Create("targets", "docker.io/notary/test", data.ED25519Key) require.NoError(t, err) root := data.SignedRoot{ Signatures: make([]data.Signature, 0), Signed: data.Root{ SignedCommon: data.SignedCommon{ Type: "Root", Expires: now.Add(time.Hour), Version: 1, }, Keys: map[string]data.PublicKey{ ecdsax509Key.ID(): ecdsax509Key, otherKey.ID(): otherKey, }, Roles: map[string]*data.RootRole{ "root": { KeyIDs: []string{ecdsax509Key.ID()}, Threshold: 1, }, "targets": { KeyIDs: []string{otherKey.ID()}, Threshold: 1, }, "snapshot": { KeyIDs: []string{otherKey.ID()}, Threshold: 1, }, "timestamp": { KeyIDs: []string{otherKey.ID()}, Threshold: 1, }, }, }, Dirty: true, } signedRoot, err := root.ToSigned() require.NoError(t, err) err = signed.Sign(cs, signedRoot, []data.PublicKey{ecdsax509Key}, 1, nil) require.NoError(t, err) typedSignedRoot, err := data.RootFromSigned(signedRoot) require.NoError(t, err) tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) validatedRoot, err := trustpinning.ValidateRoot( nil, signedRoot, "docker.io/notary/test", trustpinning.TrustPinConfig{ Certs: map[string][]string{ "docker.io/notary/test": {ecdsax509Key.ID()}, }, DisableTOFU: true, }, ) require.NoError(t, err, "failed to validate certID with intermediate") generateRootKeyIDs(typedSignedRoot) require.Equal(t, typedSignedRoot, validatedRoot) }
/* ValidateRoot receives a new root, validates its correctness and attempts to do root key rotation if needed. First we list the current trusted certificates we have for a particular GUN. If that list is non-empty means that we've already seen this repository before, and have a list of trusted certificates for it. In this case, we use this list of certificates to attempt to validate this root file. If the previous validation suceeds, or in the case where we found no trusted certificates for this particular GUN, we check the integrity of the root by making sure that it is validated by itself. This means that we will attempt to validate the root data with the certificates that are included in the root keys themselves. If this last steps succeeds, we attempt to do root rotation, by ensuring that we only trust the certificates that are present in the new root. This mechanism of operation is essentially Trust On First Use (TOFU): if we have never seen a certificate for a particular CN, we trust it. If later we see a different certificate for that certificate, we return an ErrValidationFailed error. Note that since we only allow trust data to be downloaded over an HTTPS channel we are using the current public PKI to validate the first download of the certificate adding an extra layer of security over the normal (SSH style) trust model. We shall call this: TOFUS. */ func (km *KeyStoreManager) ValidateRoot(root *data.Signed, gun string) error { logrus.Debugf("entered ValidateRoot with dns: %s", gun) signedRoot, err := data.RootFromSigned(root) if err != nil { return err } // Retrieve all the leaf certificates in root for which the CN matches the GUN allValidCerts, err := validRootLeafCerts(signedRoot, gun) 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 certsForCN, err := km.trustedCertificateStore.GetCertificatesByCN(gun) if err != nil { // If the error that we get back is different than ErrNoCertificatesFound // we couldn't check if there are any certificates with this CN already // trusted. Let's take the conservative approach and return a failed validation if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok { logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err) return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"} } } // If we have certificates that match this specific GUN, let's make sure to // use them first to validate that this new root is valid. if len(certsForCN) != 0 { logrus.Debugf("found %d valid root certificates for %s", len(certsForCN), gun) err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsForCN)) 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"} } } else { logrus.Debugf("found no currently valid root certificates for %s", gun) } // Validate the integrity of the new root (does it have valid signatures) err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(allValidCerts)) if err != nil { logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err) return &ErrValidationFail{Reason: "failed to validate integrity of roots"} } // Getting here means A) we had trusted certificates and both the // old and new validated this root; or B) we had no trusted certificates but // the new set of certificates has integrity (self-signed) logrus.Debugf("entering root certificate rotation for: %s", gun) // Do root certificate rotation: we trust only the certs present in the new root // First we add all the new certificates (even if they already exist) for _, cert := range allValidCerts { err := km.trustedCertificateStore.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 for certID, cert := range certsToRemove(certsForCN, allValidCerts) { logrus.Debugf("removing certificate with certID: %s", certID) err = km.trustedCertificateStore.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 }
// downloadRoot is responsible for downloading the root.json func (c *Client) downloadRoot() error { logrus.Debug("Downloading Root...") role := data.CanonicalRootRole // We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle // since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch var size int64 = -1 var expectedSha256 []byte if c.local.Snapshot != nil { size = c.local.Snapshot.Signed.Meta[role].Length expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"] } // if we're bootstrapping we may not have a cached root, an // error will result in the "previous root version" being // interpreted as 0. var download bool var err error var cachedRoot []byte old := &data.Signed{} version := 0 if expectedSha256 != nil { // can only trust cache if we have an expected sha256 to trust cachedRoot, err = c.cache.GetMeta(role, size) } if cachedRoot == nil || err != nil { logrus.Debug("didn't find a cached root, must download") download = true } else { hash := sha256.Sum256(cachedRoot) if !bytes.Equal(hash[:], expectedSha256) { logrus.Debug("cached root's hash didn't match expected, must download") download = true } err := json.Unmarshal(cachedRoot, old) if err == nil { root, err := data.RootFromSigned(old) if err == nil { version = root.Signed.Version } else { logrus.Debug("couldn't parse Signed part of cached root, must download") download = true } } else { logrus.Debug("couldn't parse cached root, must download") download = true } } var s *data.Signed var raw []byte if download { // use consistent download if we have the checksum. raw, s, err = c.downloadSigned(role, size, expectedSha256) if err != nil { return err } } else { logrus.Debug("using cached root") s = old } if err := c.verifyRoot(role, s, version); err != nil { return err } if download { logrus.Debug("caching downloaded root") // Now that we have accepted new root, write it to cache if err = c.cache.SetMeta(role, raw); err != nil { logrus.Errorf("Failed to write root to local cache: %s", err.Error()) } } return nil }
// checkRoot errors if an invalid rotation has taken place, if the // threshold number of signatures is invalid, if there are an invalid // number of roles and keys, or if the timestamp keys are invalid func checkRoot(oldRoot, newRoot *data.SignedRoot, timestampKey data.PublicKey) error { rootRole := data.CanonicalRootRole targetsRole := data.CanonicalTargetsRole snapshotRole := data.CanonicalSnapshotRole timestampRole := data.CanonicalTimestampRole var oldRootRole *data.RootRole newRootRole, ok := newRoot.Signed.Roles[rootRole] if !ok { return errors.New("new root is missing role entry for root role") } oldThreshold := 1 rotation := false oldKeys := map[string]data.PublicKey{} newKeys := map[string]data.PublicKey{} if oldRoot != nil { // check for matching root key IDs oldRootRole = oldRoot.Signed.Roles[rootRole] oldThreshold = oldRootRole.Threshold for _, kid := range oldRootRole.KeyIDs { k, ok := oldRoot.Signed.Keys[kid] if !ok { // if the key itself wasn't contained in the root // we're skipping it because it could never have // been used to validate this root. continue } oldKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public()) } // super simple check for possible rotation rotation = len(oldKeys) != len(newRootRole.KeyIDs) } // if old and new had the same number of keys, iterate // to see if there's a difference. for _, kid := range newRootRole.KeyIDs { k, ok := newRoot.Signed.Keys[kid] if !ok { // if the key itself wasn't contained in the root // we're skipping it because it could never have // been used to validate this root. continue } newKeys[kid] = data.NewPublicKey(k.Algorithm(), k.Public()) if oldRoot != nil { if _, ok := oldKeys[kid]; !ok { // if there is any difference in keys, a key rotation may have // occurred. rotation = true } } } newSigned, err := newRoot.ToSigned() if err != nil { return err } if rotation { err = signed.VerifyRoot(newSigned, oldThreshold, oldKeys) if err != nil { return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", oldThreshold) } } err = signed.VerifyRoot(newSigned, newRootRole.Threshold, newKeys) if err != nil { return err } root, err := data.RootFromSigned(newSigned) if err != nil { return err } var timestampKeyIDs []string // at a minimum, check the 4 required roles are present for _, r := range []string{rootRole, targetsRole, snapshotRole, timestampRole} { role, ok := root.Signed.Roles[r] if !ok { return fmt.Errorf("missing required %s role from root", r) } // According to the TUF spec, any role may have more than one signing // key and require a threshold signature. However, notary-server // creates the timestamp, and there is only ever one, so a threshold // greater than one would just always fail validation if (r == timestampRole && role.Threshold != 1) || role.Threshold < 1 { return fmt.Errorf("%s role has invalid threshold", r) } if len(role.KeyIDs) < role.Threshold { return fmt.Errorf("%s role has insufficient number of keys", r) } if r == timestampRole { timestampKeyIDs = role.KeyIDs } } // ensure that at least one of the timestamp keys specified in the role // actually exists for _, keyID := range timestampKeyIDs { if timestampKey.ID() == keyID { return nil } } return fmt.Errorf("none of the following timestamp keys exist: %s", strings.Join(timestampKeyIDs, ", ")) }
func TestValidateRootWithPinnedCA(t *testing.T) { var testSignedRoot data.Signed var signedRootBytes bytes.Buffer // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) // Unmarshal our signedRoot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) typedSignedRoot, err := data.RootFromSigned(&testSignedRoot) require.NoError(t, err) // This call to trustpinning.ValidateRoot will fail because we have an invalid path for the CA _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) require.Error(t, err) // This call to trustpinning.ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS validatedRoot, err := trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false}) require.NoError(t, err) generateRootKeyIDs(typedSignedRoot) require.Equal(t, typedSignedRoot, validatedRoot) // Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca") require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644)) // Using this invalid CA cert should fail on trustpinning.ValidateRoot _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.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 = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.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 leafCert, err := trustmanager.LoadCertFromFile("../fixtures/notary-signer.crt") require.NoError(t, err) intermediateCert, err := trustmanager.LoadCertFromFile("../fixtures/intermediate-ca.crt") require.NoError(t, err) pemChainBytes, err := trustmanager.CertChainToPEM([]*x509.Certificate{leafCert, intermediateCert}) require.NoError(t, err) newRootKey := data.NewPublicKey(data.RSAx509Key, pemChainBytes) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{newRootKey.ID()}, nil) require.NoError(t, err) testRoot, err := data.NewRoot( map[string]data.PublicKey{newRootKey.ID(): newRootKey}, map[string]*data.RootRole{ data.CanonicalRootRole: &rootRole.RootRole, data.CanonicalTimestampRole: &rootRole.RootRole, data.CanonicalTargetsRole: &rootRole.RootRole, data.CanonicalSnapshotRole: &rootRole.RootRole}, false, ) testRoot.Signed.Version = 1 require.NoError(t, err, "Failed to create new root") keyReader, err := os.Open("../fixtures/notary-signer.key") require.NoError(t, err, "could not open key file") pemBytes, err := ioutil.ReadAll(keyReader) require.NoError(t, err, "could not read key file") privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") require.NoError(t, err) store, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever) require.NoError(t, err) cs := cryptoservice.NewCryptoService(store) err = store.AddKey(trustmanager.KeyInfo{Role: data.CanonicalRootRole, Gun: "notary-signer"}, privKey) require.NoError(t, err) newTestSignedRoot, err := testRoot.ToSigned() require.NoError(t, err) err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil) require.NoError(t, err) newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot) require.NoError(t, err) // Check that we validate correctly against a pinned CA and provided bundle validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.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 goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath) require.NoError(t, err) memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) cryptoService := cryptoservice.NewCryptoService(memKeyStore) testPubKey, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey, _, err := memKeyStore.GetKey(testPubKey.ID()) require.NoError(t, err) expiredCert, err := generateExpiredTestingCertificate(testPrivKey, "notary-signer") require.NoError(t, err) bundleWithExpiredCert, err := trustmanager.CertChainToPEM(append(goodRootCABundle, expiredCert)) require.NoError(t, err) bundleWithExpiredCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644)) // Check that we validate correctly against a pinned CA and provided bundle validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true}) require.NoError(t, err) require.Equal(t, newTypedSignedRoot, validatedRoot) testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID()) require.NoError(t, err) expiredCert2, err := generateExpiredTestingCertificate(testPrivKey2, "notary-signer") require.NoError(t, err) allExpiredCertBundle, err := trustmanager.CertChainToPEM([]*x509.Certificate{expiredCert, expiredCert2}) require.NoError(t, err) 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 = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.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 testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID()) require.NoError(t, err) validCert, err := cryptoservice.GenerateCertificate(testPrivKey3, "notary-signer", time.Now(), time.Now().AddDate(1, 0, 0)) require.NoError(t, err) bundleWithWrongCert, err := trustmanager.CertChainToPEM([]*x509.Certificate{validCert}) require.NoError(t, err) bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644)) _, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true}) require.Error(t, err) }
// downloadRoot is responsible for downloading the root.json func (c *Client) downloadRoot() error { logrus.Debug("Downloading Root...") role := data.CanonicalRootRole // We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle // since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch var size int64 = -1 // We could not expect what the "snapshot" meta has specified. // // In some old clients, there is only the "sha256", // but both "sha256" and "sha512" in the newer ones. // // And possibly more in the future. var expectedHashes data.Hashes if c.local.Snapshot != nil { if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok { size = prevRootMeta.Length expectedHashes = prevRootMeta.Hashes } } // if we're bootstrapping we may not have a cached root, an // error will result in the "previous root version" being // interpreted as 0. var download bool var err error var cachedRoot []byte old := &data.Signed{} version := 0 // Due to the same reason, we don't really know how many hashes are there. if len(expectedHashes) != 0 { // can only trust cache if we have an expected sha256(for example) to trust cachedRoot, err = c.cache.GetMeta(role, size) } if cachedRoot == nil || err != nil { logrus.Debug("didn't find a cached root, must download") download = true } else { if err := data.CheckHashes(cachedRoot, expectedHashes); err != nil { logrus.Debug("cached root's hash didn't match expected, must download") download = true } err := json.Unmarshal(cachedRoot, old) if err == nil { root, err := data.RootFromSigned(old) if err == nil { version = root.Signed.Version } else { logrus.Debug("couldn't parse Signed part of cached root, must download") download = true } } else { logrus.Debug("couldn't parse cached root, must download") download = true } } var s *data.Signed var raw []byte if download { // use consistent download if we have the checksum. raw, s, err = c.downloadSigned(role, size, expectedHashes) if err != nil { return err } } else { logrus.Debug("using cached root") s = old } if err := c.verifyRoot(role, s, version); err != nil { return err } if download { logrus.Debug("caching downloaded root") // Now that we have accepted new root, write it to cache if err = c.cache.SetMeta(role, raw); err != nil { logrus.Errorf("Failed to write root to local cache: %s", err.Error()) } } return nil }
func testValidateRootRotationMissingNewSig(t *testing.T, keyAlg, rootKeyType string) { gun := "docker.com/notary" memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) cs := cryptoservice.NewCryptoService(memKeyStore) // TUF key with PEM-encoded x509 certificate 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, ) origTestRoot.Signed.Version = 1 require.NoError(t, err, "Failed to create new root") signedOrigTestRoot, err := origTestRoot.ToSigned() require.NoError(t, err) err = signed.Sign(cs, signedOrigTestRoot, []data.PublicKey{origRootKey}, 1, nil) require.NoError(t, err) prevRoot, err := data.RootFromSigned(signedOrigTestRoot) require.NoError(t, err) // TUF key with PEM-encoded x509 certificate replRootKey, err := testutils.CreateKey(cs, gun, data.CanonicalRootRole, keyAlg) require.NoError(t, err) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{replRootKey.ID()}, nil) require.NoError(t, err) testRoot, err := data.NewRoot( map[string]data.PublicKey{replRootKey.ID(): replRootKey}, map[string]*data.RootRole{ data.CanonicalRootRole: &rootRole.RootRole, data.CanonicalTargetsRole: &rootRole.RootRole, data.CanonicalSnapshotRole: &rootRole.RootRole, data.CanonicalTimestampRole: &rootRole.RootRole, }, false, ) require.NoError(t, err, "Failed to create new root") signedTestRoot, err := testRoot.ToSigned() require.NoError(t, err) // We only sign with the old key, and not with the new one err = signed.Sign(cs, signedTestRoot, []data.PublicKey{origRootKey}, 1, nil) require.NoError(t, err) // This call to trustpinning.ValidateRoot will succeed since we are using a valid PEM // encoded certificate, and have no other certificates for this CN _, err = trustpinning.ValidateRoot(prevRoot, signedTestRoot, gun, trustpinning.TrustPinConfig{}) require.Error(t, err, "insuficient signatures on root") }