// GetOrCreateSnapshotKey either creates a new snapshot key, or returns // the existing one. Only the PublicKey is returned. The private part // is held by the CryptoService. func GetOrCreateSnapshotKey(gun string, store storage.MetaStore, crypto signed.CryptoService, createAlgorithm string) (data.PublicKey, error) { _, rootJSON, err := store.GetCurrent(gun, data.CanonicalRootRole) if err != nil { // If the error indicates we couldn't find the root, create a new key if _, ok := err.(storage.ErrNotFound); !ok { logrus.Errorf("Error when retrieving root role for GUN %s: %v", gun, err) return nil, err } return crypto.Create(data.CanonicalSnapshotRole, gun, createAlgorithm) } // If we have a current root, parse out the public key for the snapshot role, and return it repoSignedRoot := new(data.SignedRoot) if err := json.Unmarshal(rootJSON, repoSignedRoot); err != nil { logrus.Errorf("Failed to unmarshal existing root for GUN %s to retrieve snapshot key ID", gun) return nil, err } snapshotRole, err := repoSignedRoot.BuildBaseRole(data.CanonicalSnapshotRole) if err != nil { logrus.Errorf("Failed to extract snapshot role from root for GUN %s", gun) return nil, err } // We currently only support single keys for snapshot and timestamp, so we can return the first and only key in the map if the signer has it for keyID := range snapshotRole.Keys { if pubKey := crypto.GetKey(keyID); pubKey != nil { return pubKey, nil } } logrus.Debugf("Failed to find any snapshot keys in cryptosigner from root for GUN %s, generating new key", gun) return crypto.Create(data.CanonicalSnapshotRole, gun, createAlgorithm) }
// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted) // as well as available keys used to sign the previous version, if the public part is // carried in tr.Root.Keys and the private key is available (i.e. probably previously // trusted keys, to allow rollover). If there are any errors, attempt to put root // back to the way it was (so version won't be incremented, for instance). func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) { logrus.Debug("signing root...") // duplicate root and attempt to modify it rather than the existing root rootBytes, err := tr.Root.MarshalJSON() if err != nil { return nil, err } tempRoot := data.SignedRoot{} if err := json.Unmarshal(rootBytes, &tempRoot); err != nil { return nil, err } currRoot, err := tr.GetBaseRole(data.CanonicalRootRole) if err != nil { return nil, err } oldRootRoles := tr.getOldRootRoles() var latestSavedRole data.BaseRole rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles)) if len(oldRootRoles) > 0 { sort.Sort(oldRootRoles) for _, vRole := range oldRootRoles { rolesToSignWith = append(rolesToSignWith, vRole.BaseRole) } latest := rolesToSignWith[len(rolesToSignWith)-1] latestSavedRole = data.BaseRole{ Name: data.CanonicalRootRole, Threshold: latest.Threshold, Keys: latest.Keys, } } // if the root role has changed and original role had not been saved as a previous role, save it now if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) { rolesToSignWith = append(rolesToSignWith, tr.originalRootRole) latestSavedRole = tr.originalRootRole versionName := oldRootVersionName(tempRoot.Signed.Version) tempRoot.Signed.Roles[versionName] = &data.RootRole{ KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold} } tempRoot.Signed.Expires = expires tempRoot.Signed.Version++ // if the current role doesn't match with the latest saved role, save it if !currRoot.Equals(latestSavedRole) { rolesToSignWith = append(rolesToSignWith, currRoot) versionName := oldRootVersionName(tempRoot.Signed.Version) tempRoot.Signed.Roles[versionName] = &data.RootRole{ KeyIDs: currRoot.ListKeyIDs(), Threshold: currRoot.Threshold} } signed, err := tempRoot.ToSigned() if err != nil { return nil, err } signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith)) if err != nil { return nil, err } tr.Root = &tempRoot tr.Root.Signatures = signed.Signatures tr.originalRootRole = currRoot return signed, 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 }
// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted) // as well as available keys used to sign the previous version, if the public part is // carried in tr.Root.Keys and the private key is available (i.e. probably previously // trusted keys, to allow rollover). If there are any errors, attempt to put root // back to the way it was (so version won't be incremented, for instance). func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) { logrus.Debug("signing root...") // duplicate root and attempt to modify it rather than the existing root rootBytes, err := tr.Root.MarshalJSON() if err != nil { return nil, err } tempRoot := data.SignedRoot{} if err := json.Unmarshal(rootBytes, &tempRoot); err != nil { return nil, err } currRoot, err := tr.GetBaseRole(data.CanonicalRootRole) if err != nil { return nil, err } oldRootRoles := tr.getOldRootRoles() var latestSavedRole data.BaseRole rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles)) if len(oldRootRoles) > 0 { sort.Sort(oldRootRoles) for _, vRole := range oldRootRoles { rolesToSignWith = append(rolesToSignWith, vRole.BaseRole) } latest := rolesToSignWith[len(rolesToSignWith)-1] latestSavedRole = data.BaseRole{ Name: data.CanonicalRootRole, Threshold: latest.Threshold, Keys: latest.Keys, } } // If the root role (root keys or root threshold) has changed, save the // previous role under the role name "root.<n>", such that the "n" is the // latest root.json version for which previous root role was valid. // Also, guard against re-saving the previous role if the latest // saved role is the same (which should not happen). // n = root.json version of the originalRootRole (previous role) // n+1 = root.json version of the currRoot (current role) // n-m = root.json version of latestSavedRole (not necessarily n-1, because the // last root rotation could have happened several root.json versions ago if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) { rolesToSignWith = append(rolesToSignWith, tr.originalRootRole) latestSavedRole = tr.originalRootRole versionName := oldRootVersionName(tempRoot.Signed.Version) tempRoot.Signed.Roles[versionName] = &data.RootRole{ KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold} } tempRoot.Signed.Expires = expires tempRoot.Signed.Version++ rolesToSignWith = append(rolesToSignWith, currRoot) signed, err := tempRoot.ToSigned() if err != nil { return nil, err } signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith)) if err != nil { return nil, err } tr.Root = &tempRoot tr.Root.Signatures = signed.Signatures tr.originalRootRole = currRoot return signed, 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 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) }