From 302d2fc8e4b8e59d563d405290b801693925a7c9 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Wed, 9 Mar 2022 12:27:49 -0500 Subject: [PATCH 01/48] Support delegated targets roles in repo writer --- client/errors.go | 1 + errors.go | 8 + local_store.go | 4 +- repo.go | 654 ++++++++++++++++++++++++++++++++++++++++------- repo_test.go | 178 ++++++++++++- verify/db.go | 9 +- 6 files changed, 747 insertions(+), 107 deletions(-) diff --git a/client/errors.go b/client/errors.go index 44a36d24..40f52d1d 100644 --- a/client/errors.go +++ b/client/errors.go @@ -58,6 +58,7 @@ func isDecodeFailedWithErrRoleThreshold(err error) bool { return isErrRoleThreshold(e.Err) } +//lint:ignore U1000 unused func isErrRoleThreshold(err error) bool { _, ok := err.(verify.ErrRoleThreshold) return ok diff --git a/errors.go b/errors.go index 1a8ab9ff..09df0390 100644 --- a/errors.go +++ b/errors.go @@ -88,3 +88,11 @@ type ErrPassphraseRequired struct { func (e ErrPassphraseRequired) Error() string { return fmt.Sprintf("tuf: a passphrase is required to access the encrypted %s keys file", e.Role) } + +type ErrNoDelegatedTarget struct { + Path string +} + +func (e ErrNoDelegatedTarget) Error() string { + return fmt.Sprintf("tuf: no delegated target for path %s", e.Path) +} diff --git a/local_store.go b/local_store.go index 9456016a..425b1111 100644 --- a/local_store.go +++ b/local_store.go @@ -14,7 +14,6 @@ import ( "github.com/theupdateframework/go-tuf/data" "github.com/theupdateframework/go-tuf/encrypted" - "github.com/theupdateframework/go-tuf/internal/roles" "github.com/theupdateframework/go-tuf/internal/sets" "github.com/theupdateframework/go-tuf/pkg/keys" "github.com/theupdateframework/go-tuf/util" @@ -222,8 +221,7 @@ func (f *fileSystemStore) stagedDir() string { } func isMetaFile(e os.DirEntry) (bool, error) { - name := e.Name() - if e.IsDir() || !(filepath.Ext(name) == ".json" && roles.IsTopLevelManifest(name)) { + if e.IsDir() || filepath.Ext(e.Name()) != ".json" { return false, nil } diff --git a/repo.go b/repo.go index 10d13af3..0dc0feda 100644 --- a/repo.go +++ b/repo.go @@ -14,12 +14,19 @@ import ( "github.com/theupdateframework/go-tuf/data" "github.com/theupdateframework/go-tuf/internal/roles" "github.com/theupdateframework/go-tuf/internal/signer" + itargets "github.com/theupdateframework/go-tuf/internal/targets" // FIXME "github.com/theupdateframework/go-tuf/pkg/keys" + "github.com/theupdateframework/go-tuf/pkg/targets" "github.com/theupdateframework/go-tuf/sign" "github.com/theupdateframework/go-tuf/util" "github.com/theupdateframework/go-tuf/verify" ) +const ( + // The maximum number of delegations to visit while traversing the delegations graph. + defaultMaxDelegations = 32 +) + // topLevelMetadata determines the order signatures are verified when committing. var topLevelMetadata = []string{ "root.json", @@ -254,17 +261,21 @@ func (r *Repo) SnapshotVersion() (int64, error) { } func (r *Repo) topLevelTargets() (*data.Targets, error) { - targetsJSON, ok := r.meta["targets.json"] + return r.targets("targets") +} + +func (r *Repo) targets(metaName string) (*data.Targets, error) { + targetsJSON, ok := r.meta[metaName+".json"] if !ok { return data.NewTargets(), nil } s := &data.Signed{} if err := json.Unmarshal(targetsJSON, s); err != nil { - return nil, err + return nil, fmt.Errorf("error unmarshalling for targets %q: %w", metaName, err) } targets := &data.Targets{} if err := json.Unmarshal(s.Signed, targets); err != nil { - return nil, err + return nil, fmt.Errorf("error unmarshalling signed data for targets %q: %w", metaName, err) } return targets, nil } @@ -286,6 +297,8 @@ func (r *Repo) timestamp() (*data.Timestamp, error) { } func (r *Repo) ChangePassphrase(keyRole string) error { + // Not compatible with delegated roles. + if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{keyRole, "only support passphrases for top-level roles"} } @@ -298,10 +311,14 @@ func (r *Repo) ChangePassphrase(keyRole string) error { } func (r *Repo) GenKey(role string) ([]string, error) { + // Not compatible with delegated roles. + return r.GenKeyWithExpires(role, data.DefaultExpires(role)) } func (r *Repo) GenKeyWithExpires(keyRole string, expires time.Time) (keyids []string, err error) { + // Not compatible with delegated roles. + signer, err := keys.GenerateEd25519Key() if err != nil { return []string{}, err @@ -315,10 +332,13 @@ func (r *Repo) GenKeyWithExpires(keyRole string, expires time.Time) (keyids []st } func (r *Repo) AddPrivateKey(role string, signer keys.Signer) error { + // Not compatible with delegated roles. + return r.AddPrivateKeyWithExpires(role, signer, data.DefaultExpires(role)) } func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expires time.Time) error { + // Not compatible with delegated roles. if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{keyRole, "only support adding keys for top-level roles"} } @@ -327,6 +347,8 @@ func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expi return ErrInvalidExpires{expires} } + // Must add signer before adding verification key, so + // root.json can be signed when a new root key is added. if err := r.local.SaveSigner(keyRole, signer); err != nil { return err } @@ -339,10 +361,22 @@ func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expi } func (r *Repo) AddVerificationKey(keyRole string, pk *data.PublicKey) error { + // Not compatible with delegated roles. + return r.AddVerificationKeyWithExpiration(keyRole, pk, data.DefaultExpires(keyRole)) } func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicKey, expires time.Time) error { + // Not compatible with delegated roles. + + if !roles.IsTopLevelRole(keyRole) { + return ErrInvalidRole{keyRole} + } + + if !validExpires(expires) { + return ErrInvalidExpires{expires} + } + root, err := r.root() if err != nil { return err @@ -413,10 +447,14 @@ func (r *Repo) RootKeys() ([]*data.PublicKey, error) { } func (r *Repo) RevokeKey(role, id string) error { + // Not compatible with delegated roles. + return r.RevokeKeyWithExpires(role, id, data.DefaultExpires("root")) } func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error { + // Not compatible with delegated roles. + if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{keyRole, "only revocations for top-level roles supported"} } @@ -483,6 +521,122 @@ func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error return err } +// AddTargetsDelegation is equivalent to AddTargetsDelegationWithExpires, but +// with a default expiration time. +func (r *Repo) AddTargetsDelegation(delegator string, role data.DelegatedRole, keys []*data.PublicKey) error { + return r.AddTargetsDelegationWithExpires(delegator, role, keys, data.DefaultExpires("targets")) +} + +// AddTargetsDelegationWithExpires adds a delegation from the delegator to the +// role specified in the role argument. Key IDs referenced in role.KeyIDs +// should have corresponding Key entries in the keys argument. New metadata is +// written with the given expiration time. +func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error { + t, err := r.targets(delegator) + if err != nil { + return fmt.Errorf("error getting delegator (%q) metadata: %w", delegator, err) + } + + if t.Delegations == nil { + t.Delegations = &data.Delegations{} + t.Delegations.Keys = make(map[string]*data.PublicKey) + } + + for _, keyID := range role.KeyIDs { + keyLoop: + for _, key := range keys { + if key.ContainsID(keyID) { + t.Delegations.Keys[keyID] = key + break keyLoop + } + } + } + + for _, r := range t.Delegations.Roles { + if r.Name == role.Name { + return fmt.Errorf("role: %s is already delegated to by %s", role.Name, r.Name) + } + } + t.Delegations.Roles = append(t.Delegations.Roles, role) + t.Expires = expires.Round(time.Second) + + delegatorFile := delegator + ".json" + if !r.local.FileIsStaged(delegatorFile) { + t.Version++ + } + + delegatorSigners, err := r.local.GetSigners(delegator) + if err != nil { + return fmt.Errorf("error getting signers for delegator %v: %w", delegator, err) + } + err = r.setMetaWithSigners(delegatorFile, t, delegatorSigners) + if err != nil { + return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err) + } + + delegatee := role.Name + dt, err := r.targets(delegatee) + if err != nil { + return fmt.Errorf("error getting delegatee (%q) metadata: %w", delegatee, err) + } + + delegateeFile := delegatee + ".json" + if !r.local.FileIsStaged(delegateeFile) { + dt.Version++ + } + + delegateeSigners, err := r.local.GetSigners(delegatee) + if err != nil { + return fmt.Errorf("error getting signers for delegatee %v: %w", delegatee, err) + } + err = r.setMetaWithSigners(delegateeFile, dt, delegateeSigners) + if err != nil { + return fmt.Errorf("error setting metadata for %q: %w", delegateeFile, err) + } + + return nil +} + +// AddTargetsDelegationsForPathHashBins is equivalent to +// AddTargetsDelegationsForPathHashBinsWithExpires, but with a default +// expiration time. +func (r *Repo) AddTargetsDelegationsForPathHashBins(delegator string, binRolePrefix string, prefixBitLen int, keys []*data.PublicKey, threshold int) error { + return r.AddTargetsDelegationsForPathHashBinsWithExpires(delegator, binRolePrefix, prefixBitLen, keys, threshold, data.DefaultExpires("targets")) +} + +// AddTargetsDelegationsForPathHashBinsWithExpires adds 2^(prefixBitLen) +// delegations to the delegator role, which partition the target path hash +// space into bins using the PathHashPrefixes delegation mechanism. New +// metadata is written with the given expiration time. +func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, binRolePrefix string, prefixBitLen int, keys []*data.PublicKey, threshold int, expires time.Time) error { + bins, err := itargets.NewHashBins(binRolePrefix, prefixBitLen) + if err != nil { + return err + } + + keyIDs := []string{} + for _, key := range keys { + keyIDs = append(keyIDs, key.IDs()...) + } + + n := bins.NumBins() + for i := uint64(0); i < n; i += 1 { + bin := bins.GetBin(i) + name := bin.RoleName() + err := r.AddTargetsDelegationWithExpires(delegator, data.DelegatedRole{ + Name: name, + KeyIDs: keyIDs, + PathHashPrefixes: bin.HashPrefixes(), + Threshold: threshold, + }, keys, expires) + if err != nil { + return fmt.Errorf("error adding delegation from %v to %v: %w", delegator, name, err) + } + } + + return nil +} + func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) { if r.prefix == "" && r.indent == "" { return json.Marshal(v) @@ -491,11 +645,19 @@ func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) { } func (r *Repo) setTopLevelMeta(roleFilename string, meta interface{}) error { - keys, err := r.getSortedSigningKeys(strings.TrimSuffix(roleFilename, ".json")) + db, err := r.topLevelKeysDB() if err != nil { return err } - s, err := sign.Marshal(meta, keys...) + signers, err := r.getSignersInDB(strings.TrimSuffix(roleFilename, ".json"), db) + if err != nil { + return err + } + return r.setMetaWithSigners(roleFilename, meta, signers) +} + +func (r *Repo) setMetaWithSigners(roleFilename string, meta interface{}, signers []keys.Signer) error { + s, err := sign.Marshal(meta, signers...) if err != nil { return err } @@ -518,7 +680,11 @@ func (r *Repo) Sign(roleFilename string) error { return err } - keys, err := r.getSortedSigningKeys(role) + db, err := r.topLevelKeysDB() + if err != nil { + return err + } + keys, err := r.getSignersInDB(role, db) if err != nil { return err } @@ -594,46 +760,45 @@ func (r *Repo) AddOrUpdateSignature(roleFilename string, signature data.Signatur return r.local.SetMeta(roleFilename, b) } -// getSortedSigningKeys returns available signing keys, sorted by key ID. +// getSignersInDB returns available signing interfaces, sorted by key ID. // // Only keys contained in the keys db are returned (i.e. local keys which have // been revoked are omitted), except for the root role in which case all local // keys are returned (revoked root keys still need to sign new root metadata so // clients can verify the new root.json and update their keys db accordingly). -func (r *Repo) getSortedSigningKeys(name string) ([]keys.Signer, error) { - signingKeys, err := r.local.GetSigners(name) +func (r *Repo) getSignersInDB(roleName string, db *verify.DB) ([]keys.Signer, error) { + signers, err := r.local.GetSigners(roleName) if err != nil { return nil, err } - if name == "root" { - sorted := make([]keys.Signer, len(signingKeys)) - copy(sorted, signingKeys) + + if roleName == "root" { + sorted := make([]keys.Signer, len(signers)) + copy(sorted, signers) sort.Sort(signer.ByIDs(sorted)) return sorted, nil } - db, err := r.topLevelKeysDB() - if err != nil { - return nil, err - } - role := db.GetRole(name) + + role := db.GetRole(roleName) if role == nil { return nil, nil } if len(role.KeyIDs) == 0 { return nil, nil } - keys := make([]keys.Signer, 0, len(role.KeyIDs)) - for _, key := range signingKeys { - for _, id := range key.PublicData().IDs() { + + signersInDB := make([]keys.Signer, 0, len(role.KeyIDs)) + for _, s := range signers { + for _, id := range s.PublicData().IDs() { if _, ok := role.KeyIDs[id]; ok { - keys = append(keys, key) + signersInDB = append(signersInDB, s) } } } - sort.Sort(signer.ByIDs(keys)) + sort.Sort(signer.ByIDs(signersInDB)) - return keys, nil + return signersInDB, nil } // Used to retrieve the signable portion of the metadata when using an external signing tool. @@ -649,18 +814,132 @@ func (r *Repo) SignedMeta(roleFilename string) (*data.Signed, error) { return s, nil } +func (r *Repo) delegatorDBs(delegateeRole string) (map[string]*verify.DB, error) { + delegatorDBs := map[string]*verify.DB{} + for metaName := range r.meta { + if roles.IsTopLevelManifest(metaName) && metaName != "targets.json" { + continue + } + roleName := strings.TrimSuffix(metaName, ".json") + + t, err := r.targets(roleName) + if err != nil { + return nil, err + } + + if t.Delegations == nil { + continue + } + + delegatesToRole := false + for _, d := range t.Delegations.Roles { + if d.Name == delegateeRole { + delegatesToRole = true + break + } + } + if !delegatesToRole { + continue + } + + db, err := verify.NewDBFromDelegations(t.Delegations) + if err != nil { + return nil, err + } + + delegatorDBs[roleName] = db + } + + return delegatorDBs, nil +} + +// targetDelegationForPath finds the targets metadata for the role that should +// sign the given path. The final delegation that led to the returned target +// metadata is also returned. +// +// Since there may be multiple targets roles that are able to sign a specific +// path, we must choose which roles's metadata to return. If preferredRole is +// specified (non-empty string) and eligible to sign the given path by way of +// some delegation chain, targets metadata for that role is returned. If +// preferredRole is not specified (""), we return targets metadata for the +// final role visited in the depth-first delegation traversal. +func (r *Repo) targetDelegationForPath(path string, preferredRole string) (*data.Targets, *targets.Delegation, error) { + topLevelKeysDB, err := r.topLevelKeysDB() + if err != nil { + return nil, nil, err + } + + iterator, err := targets.NewDelegationsIterator(path, topLevelKeysDB) + if err != nil { + return nil, nil, err + } + d, ok := iterator.Next() + if !ok { + return nil, nil, ErrNoDelegatedTarget{Path: path} + } + + for i := 0; i < defaultMaxDelegations; i++ { + targetsMeta, err := r.targets(d.Delegatee.Name) + if err != nil { + return nil, nil, err + } + + if preferredRole != "" && d.Delegatee.Name == preferredRole { + // The preferredRole is eligible to sign for the given path, and we've + // found its metadata. Return it. + return targetsMeta, &d, nil + } + + if targetsMeta.Delegations != nil && len(targetsMeta.Delegations.Roles) > 0 { + db, err := verify.NewDBFromDelegations(targetsMeta.Delegations) + if err != nil { + return nil, nil, err + } + + // Add delegations to the iterator that are eligible to sign for the + // given path (there may be none). + iterator.Add(targetsMeta.Delegations.Roles, d.Delegatee.Name, db) + } + + next, ok := iterator.Next() + if !ok { // No more roles to traverse. + if preferredRole == "" { + // No preferredRole was given, so return metadata for the final role in the traversal. + return targetsMeta, &d, nil + } else { + // There are no more roles to traverse, so preferredRole is either an + // invalid role, or not eligible to sign the given path. + return nil, nil, ErrNoDelegatedTarget{Path: path} + } + } + + d = next + } + + return nil, nil, ErrNoDelegatedTarget{Path: path} +} + func (r *Repo) AddTarget(path string, custom json.RawMessage) error { return r.AddTargets([]string{path}, custom) } +func (r *Repo) AddTargetToPreferredRole(path string, custom json.RawMessage, preferredRole string) error { + return r.AddTargetsToPreferredRole([]string{path}, custom, preferredRole) +} + func (r *Repo) AddTargets(paths []string, custom json.RawMessage) error { - return r.AddTargetsWithExpires(paths, custom, data.DefaultExpires("targets")) + return r.AddTargetsToPreferredRole(paths, custom, "") +} + +func (r *Repo) AddTargetsToPreferredRole(paths []string, custom json.RawMessage, preferredRole string) error { + return r.AddTargetsWithExpiresToPreferredRole(paths, custom, data.DefaultExpires("targets"), preferredRole) } func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int64, path string, custom json.RawMessage) error { expires := data.DefaultExpires("targets") // TODO: support delegated targets + // FIXME in this PR t, err := r.topLevelTargets() if err != nil { return err @@ -682,7 +961,7 @@ func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int6 t.Targets[path] = data.TargetFileMeta{FileMeta: meta} - return r.writeTargetWithExpires(t, expires) + return r.writeTopLevelTargetWithExpires(t, expires) } func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error { @@ -690,20 +969,80 @@ func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires } func (r *Repo) AddTargetsWithExpires(paths []string, custom json.RawMessage, expires time.Time) error { + return r.AddTargetsWithExpiresToPreferredRole(paths, custom, expires, "") +} + +func (r *Repo) AddTargetWithExpiresToPreferredRole(path string, custom json.RawMessage, expires time.Time, preferredRole string) error { + return r.AddTargetsWithExpiresToPreferredRole([]string{path}, custom, expires, preferredRole) +} + +type targetsMetaWithSigners struct { + meta *data.Targets + signers []keys.Signer +} + +// AddTargetsWithExpiresToPreferredRole signs the staged targets at `paths`. If +// preferredRole is not nil, the target is added to the given role's manifest +// if delegations allow it. If delegations do not allow the preferredRole to +// sign the given path, an error is returned. +func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json.RawMessage, expires time.Time, preferredRole string) error { if !validExpires(expires) { return ErrInvalidExpires{expires} } - t, err := r.topLevelTargets() - if err != nil { - return err - } normalizedPaths := make([]string, len(paths)) for i, path := range paths { normalizedPaths[i] = util.NormalizeTarget(path) } + + // As we iterate through staged files, we accumulate changes to their + // corresponding targets manifests. + targetsMetaToWrite := map[string]*targetsMetaWithSigners{} + if err := r.local.WalkStagedTargets(normalizedPaths, func(path string, target io.Reader) (err error) { - meta, err := util.GenerateTargetFileMeta(target, r.hashAlgorithms...) + targetsMeta, delegation, err := r.targetDelegationForPath(path, preferredRole) + if err != nil { + return err + } + // This is the targets role that needs to sign the target file. + targetsRoleName := delegation.Delegatee.Name + + signers, err := r.getSignersInDB(targetsRoleName, delegation.DB) + if err != nil { + return err + } + + // The delegations determine the keys that need to be used to sign each + // target file. If a particular target role is reachable in the delegation + // graph through multiple paths, it may be the case that the targets + // manifest needs signatures from multiple sets of keys. Therefore, we + // merge the keys specified by the current delegation with keys specified + // bypreviously seen delegations. + if prevMetaWithSigners, ok := targetsMetaToWrite[targetsRoleName]; ok { + // Start with meta staged in targetsMetaToWrite instead of the committed meta. + targetsMeta = prevMetaWithSigners.meta + + mergedKeyIDs := map[string]struct{}{} + for _, signer := range signers { + for _, keyID := range signer.PublicData().IDs() { + mergedKeyIDs[keyID] = struct{}{} + } + } + + // Merge prevMetaWithSigners.signers with signers. + for _, prevSigner := range prevMetaWithSigners.signers { + keyIDLoop: + for _, keyID := range prevSigner.PublicData().IDs() { + if _, seen := mergedKeyIDs[keyID]; !seen { + signers = append(signers, prevSigner) + mergedKeyIDs[keyID] = struct{}{} + break keyIDLoop + } + } + } + } + + fileMeta, err := util.GenerateTargetFileMeta(target, r.hashAlgorithms...) if err != nil { return err } @@ -712,22 +1051,69 @@ func (r *Repo) AddTargetsWithExpires(paths []string, custom json.RawMessage, exp // if we have custom metadata, set it, otherwise maintain // existing metadata if present if len(custom) > 0 { - meta.Custom = &custom - } else if t, ok := t.Targets[path]; ok { - meta.Custom = t.Custom + fileMeta.Custom = &custom + } else if tf, ok := targetsMeta.Targets[path]; ok { + fileMeta.Custom = tf.Custom } // G2 -> we no longer desire any readers to ever observe non-prefix targets. - delete(t.Targets, "/"+path) - t.Targets[path] = meta + delete(targetsMeta.Targets, "/"+path) + targetsMeta.Targets[path] = fileMeta + + targetsMetaToWrite[targetsRoleName] = &targetsMetaWithSigners{ + meta: targetsMeta, + signers: signers, + } + return nil }); err != nil { return err } - return r.writeTargetWithExpires(t, expires) + + if len(targetsMetaToWrite) == 0 { + t, err := r.topLevelTargets() + if err != nil { + return err + } + + db, err := r.topLevelKeysDB() + if err != nil { + return err + } + + signers, err := r.getSignersInDB("targets", db) + if err != nil { + return err + } + + targetsMetaToWrite["targets"] = &targetsMetaWithSigners{ + meta: t, + signers: signers, + } + } + + exp := expires.Round(time.Second) + for roleName, tm := range targetsMetaToWrite { + meta := tm.meta + signers := tm.signers + + meta.Expires = exp + + manifestName := roleName + ".json" + if !r.local.FileIsStaged(manifestName) { + meta.Version++ + } + + err := r.setMetaWithSigners(manifestName, meta, signers) + if err != nil { + return err + } + } + + return nil } -func (r *Repo) writeTargetWithExpires(t *data.Targets, expires time.Time) error { +func (r *Repo) writeTopLevelTargetWithExpires(t *data.Targets, expires time.Time) error { t.Expires = expires.Round(time.Second) if !r.local.FileIsStaged("targets.json") { t.Version++ @@ -740,7 +1126,8 @@ func (r *Repo) writeTargetWithExpires(t *data.Targets, expires time.Time) error fmt.Println("*", k) } } - return err + + return nil } func (r *Repo) RemoveTarget(path string) error { @@ -817,7 +1204,16 @@ func (r *Repo) Snapshot() error { } func (r *Repo) snapshotMetadata() []string { - return []string{"targets.json"} + ret := []string{"targets.json"} + + for name := range r.meta { + if !roles.IsVersionedManifest(name) && + roles.IsDelegatedTargetsManifest(name) { + ret = append(ret, name) + } + } + + return ret } func (r *Repo) SnapshotWithExpires(expires time.Time) error { @@ -829,13 +1225,9 @@ func (r *Repo) SnapshotWithExpires(expires time.Time) error { if err != nil { return err } - db, err := r.topLevelKeysDB() - if err != nil { - return err - } for _, metaName := range r.snapshotMetadata() { - if err := r.verifySignature(metaName, db); err != nil { + if err := r.verifySignatures(metaName); err != nil { return err } var err error @@ -864,11 +1256,7 @@ func (r *Repo) TimestampWithExpires(expires time.Time) error { return ErrInvalidExpires{expires} } - db, err := r.topLevelKeysDB() - if err != nil { - return err - } - if err := r.verifySignature("snapshot.json", db); err != nil { + if err := r.verifySignatures("snapshot.json"); err != nil { return err } timestamp, err := r.timestamp() @@ -892,48 +1280,96 @@ func (r *Repo) TimestampWithExpires(expires time.Time) error { } func (r *Repo) fileVersions() (map[string]int64, error) { - root, err := r.root() - if err != nil { - return nil, err - } - targets, err := r.topLevelTargets() - if err != nil { - return nil, err - } - snapshot, err := r.snapshot() - if err != nil { - return nil, err - } versions := make(map[string]int64) - versions["root.json"] = root.Version - versions["targets.json"] = targets.Version - versions["snapshot.json"] = snapshot.Version + + for fileName := range r.meta { + if roles.IsVersionedManifest(fileName) { + continue + } + + roleName := strings.TrimSuffix(fileName, ".json") + + var version int64 + + switch roleName { + case "root": + root, err := r.root() + if err != nil { + return nil, err + } + version = root.Version + case "snapshot": + snapshot, err := r.snapshot() + if err != nil { + return nil, err + } + version = snapshot.Version + case "timestamp": + continue + default: + // Targets or delegated targets manifest. + targets, err := r.targets(roleName) + if err != nil { + return nil, err + } + + version = targets.Version + } + + versions[fileName] = version + } + return versions, nil } func (r *Repo) fileHashes() (map[string]data.Hashes, error) { hashes := make(map[string]data.Hashes) - timestamp, err := r.timestamp() - if err != nil { - return nil, err - } - snapshot, err := r.snapshot() - if err != nil { - return nil, err - } - if m, ok := snapshot.Meta["targets.json"]; ok { - hashes["targets.json"] = m.Hashes - } - if m, ok := timestamp.Meta["snapshot.json"]; ok { - hashes["snapshot.json"] = m.Hashes - } - t, err := r.topLevelTargets() - if err != nil { - return nil, err - } - for name, meta := range t.Targets { - hashes[path.Join("targets", name)] = meta.Hashes + + for fileName := range r.meta { + if roles.IsVersionedManifest(fileName) { + continue + } + + roleName := strings.TrimSuffix(fileName, ".json") + + switch roleName { + case "snapshot": + timestamp, err := r.timestamp() + if err != nil { + return nil, err + } + + if m, ok := timestamp.Meta[fileName]; ok { + hashes[fileName] = m.Hashes + } + case "timestamp": + continue + default: + snapshot, err := r.snapshot() + if err != nil { + return nil, err + } + if m, ok := snapshot.Meta[fileName]; ok { + hashes[fileName] = m.Hashes + } + + // FIXME: Loading all targets into memory is not scalable if + // there are many targets. This is used to Commit, so we should + // only need new targets here. + if roleName != "root" { + t, err := r.targets(roleName) + if err != nil { + return nil, err + } + for name, m := range t.Targets { + hashes[path.Join("targets", name)] = m.Hashes + } + } + + } + } + return hashes, nil } @@ -988,13 +1424,8 @@ func (r *Repo) Commit() error { return fmt.Errorf("tuf: invalid snapshot.json in timestamp.json: %s", err) } - // verify all signatures are correct - db, err := r.topLevelKeysDB() - if err != nil { - return err - } for _, name := range topLevelMetadata { - if err := r.verifySignature(name, db); err != nil { + if err := r.verifySignatures(name); err != nil { return err } } @@ -1023,15 +1454,36 @@ func (r *Repo) Clean() error { return err } -func (r *Repo) verifySignature(roleFilename string, db *verify.DB) error { - s, err := r.SignedMeta(roleFilename) +func (r *Repo) verifySignatures(metaFilename string) error { + s, err := r.SignedMeta(metaFilename) if err != nil { return err } - role := strings.TrimSuffix(roleFilename, ".json") - if err := db.Verify(s, role, 0); err != nil { - return ErrInsufficientSignatures{roleFilename, err} + + role := strings.TrimSuffix(metaFilename, ".json") + + if roles.IsTopLevelRole(role) { + db, err := r.topLevelKeysDB() + if err != nil { + return err + } + if err := db.Verify(s, role, 0); err != nil { + return ErrInsufficientSignatures{metaFilename, err} + } + } else { + dbs, err := r.delegatorDBs(role) + if err != nil { + return err + } + + for delegator, db := range dbs { + if err := db.Verify(s, role, 0); err != nil { + return ErrInsufficientSignatures{delegator, err} + } + } + } + return nil } @@ -1050,3 +1502,15 @@ func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, e } return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...) } + +func (r *Repo) GetPublicKeys(role string) ([]*data.PublicKey, error) { + signers, err := r.local.GetSigners(role) + if err != nil { + return nil, err + } + pubkeys := []*data.PublicKey{} + for _, signer := range signers { + pubkeys = append(pubkeys, signer.PublicData()) + } + return pubkeys, nil +} diff --git a/repo_test.go b/repo_test.go index 476e0b47..a526e09d 100644 --- a/repo_test.go +++ b/repo_test.go @@ -165,8 +165,11 @@ func (rs *RepoSuite) TestInit(c *C) { c.Assert(root.ConsistentSnapshot, Equals, v) } - // Init() fails if targets have been added + // Add a target. + generateAndAddPrivateKey(c, r, "targets") c.Assert(r.AddTarget("foo.txt", nil), IsNil) + + // Init() fails if targets have been added c.Assert(r.Init(true), Equals, ErrInitNotAllowed) } @@ -1506,6 +1509,8 @@ func (rs *RepoSuite) TestCustomTargetMetadata(c *C) { r, err := NewRepo(local) c.Assert(err, IsNil) + generateAndAddPrivateKey(c, r, "targets") + custom := json.RawMessage(`{"foo":"bar"}`) assertCustomMeta := func(file string, custom *json.RawMessage) { t, err := r.topLevelTargets() @@ -1865,5 +1870,176 @@ func (rs *RepoSuite) TestSignDigest(c *C) { c.Assert(err, IsNil) c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Length, Equals, size) c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Hashes["sha256"], DeepEquals, hex_digest_bytes) +} + +func (rs *RepoSuite) TestDelegations(c *C) { + tmp := newTmpDir(c) + tmp.writeStagedTarget("foo.txt", "foo") + local := FileSystemStore(tmp.path, nil) + r, err := NewRepo(local) + c.Assert(err, IsNil) + + // Add one key to each role + genKey(c, r, "root") + genKey(c, r, "targets") + genKey(c, r, "snapshot") + genKey(c, r, "timestamp") + + // commit the metadata to the store. + c.Assert(r.AddTargets([]string{}, nil), IsNil) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + delegatedKey, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + + err = local.SaveSigner("role1", delegatedKey) + c.Assert(err, IsNil) + + paths := []string{} + paths = append(paths, "test/*") + + delegatee := data.DelegatedRole{ + Name: "role1", + KeyIDs: delegatedKey.PublicData().IDs(), + Paths: paths, + Threshold: 1, + } + publicKeys := []*data.PublicKey{} + publicKeys = append(publicKeys, delegatedKey.PublicData()) + + err = r.AddTargetsDelegation("targets", delegatee, publicKeys) + c.Assert(err, IsNil) + + //test duplicate delegation + err = r.AddTargetsDelegation("targets", delegatee, publicKeys) + c.Assert(err, NotNil) + + targets, err := r.topLevelTargets() + c.Assert(err, IsNil) + c.Assert(targets.Delegations.Roles, HasLen, 1) + c.Assert(targets.Delegations.Keys, HasLen, 1) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err := r.snapshot() + c.Assert(err, IsNil) + // Snapshots has targets, role1 + c.Assert(snapshot.Meta, HasLen, 2) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + + //add target to delegations + tmp.writeStagedTarget("test/bar.txt", "bar") + c.Assert(r.AddTarget("test/bar.txt", nil), IsNil) + targets, err = r.targets("role1") + c.Assert(err, IsNil) + c.Assert(targets.Targets, HasLen, 1) + // 3 characters in the file + c.Assert(targets.Targets["test/bar.txt"].Length, Equals, int64(3)) + + // add target to the top-level targets + tmp.writeStagedTarget("baz.txt", "baz") + c.Assert(r.AddTarget("baz.txt", nil), IsNil) + targets, err = r.targets("targets") + c.Assert(err, IsNil) + c.Assert(targets.Targets, HasLen, 2) + + // test AddTargetToPreferredRole + err = r.AddTargetToPreferredRole("test/bar.txt", nil, "targets") + c.Assert(err, IsNil) + targets, err = r.targets("targets") + c.Assert(err, IsNil) + c.Assert(targets.Targets, HasLen, 3) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + metas, err := local.GetMeta() + c.Assert(err, IsNil) + + for k, meta := range metas { + s := &data.Signed{} + err = json.Unmarshal(meta, s) + c.Assert(err, IsNil, Commentf("meta: %v", k)) + c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) + } +} + +func (rs *RepoSuite) TestHashedBinDelegations(c *C) { + tmp := newTmpDir(c) + local := FileSystemStore(tmp.path, nil) + r, err := NewRepo(local) + c.Assert(err, IsNil) + + // Add one key to each role + genKey(c, r, "root") + genKey(c, r, "targets") + genKey(c, r, "snapshot") + genKey(c, r, "timestamp") + + // keys for the hashed bins + binsKey, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + err = local.SaveSigner("bins", binsKey) + c.Assert(err, IsNil) + + leafKey, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + err = local.SaveSigner("bins_0-1", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_2-3", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_4-5", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_6-7", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_8-9", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_a-b", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_c-d", leafKey) + c.Assert(err, IsNil) + err = local.SaveSigner("bins_e-f", leafKey) + c.Assert(err, IsNil) + + err = r.AddTargetsDelegation("targets", data.DelegatedRole{ + Name: "bins", + KeyIDs: binsKey.PublicData().IDs(), + Paths: []string{"*.txt"}, + Threshold: 1, + }, []*data.PublicKey{ + binsKey.PublicData(), + }) + c.Assert(err, IsNil) + + err = r.AddTargetsDelegationsForPathHashBins("bins", "bins_", 3, []*data.PublicKey{leafKey.PublicData()}, 1) + c.Assert(err, IsNil) + targets, err := r.targets("bins") + c.Assert(err, IsNil) + c.Assert(targets.Delegations.Roles, HasLen, 8) + + tmp.writeStagedTarget("foo.txt", "foo") + err = r.AddTarget("foo.txt", nil) + c.Assert(err, IsNil) + targets, err = r.targets("bins_c-d") + c.Assert(err, IsNil) + c.Assert(targets.Targets, HasLen, 1) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + metas, err := local.GetMeta() + c.Assert(err, IsNil) + + for k, meta := range metas { + s := &data.Signed{} + err = json.Unmarshal(meta, s) + c.Assert(err, IsNil, Commentf("meta: %v", k)) + c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) + } } diff --git a/verify/db.go b/verify/db.go index a14a5149..657c2c55 100644 --- a/verify/db.go +++ b/verify/db.go @@ -40,7 +40,7 @@ func NewDBFromDelegations(d *data.Delegations) (*DB, error) { return nil, ErrInvalidDelegatedRole } role := &data.Role{Threshold: r.Threshold, KeyIDs: r.KeyIDs} - if err := db.addRole(r.Name, role); err != nil { + if err := db.AddRole(r.Name, role); err != nil { return nil, err } } @@ -65,13 +65,6 @@ func (db *DB) AddKey(id string, k *data.PublicKey) error { } func (db *DB) AddRole(name string, r *data.Role) error { - if !roles.IsTopLevelRole(name) { - return ErrInvalidRole - } - return db.addRole(name, r) -} - -func (db *DB) addRole(name string, r *data.Role) error { if r.Threshold < 1 { return ErrInvalidThreshold } From ab3d2f359b8fdb1e4eb35338d409074db5ddfaa1 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Wed, 9 Mar 2022 13:15:06 -0500 Subject: [PATCH 02/48] Add back return err in writeTopLevelTargetWithExpires --- repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.go b/repo.go index 0dc0feda..43de6bb7 100644 --- a/repo.go +++ b/repo.go @@ -1127,7 +1127,7 @@ func (r *Repo) writeTopLevelTargetWithExpires(t *data.Targets, expires time.Time } } - return nil + return err } func (r *Repo) RemoveTarget(path string) error { From b3ea3ab2d3e824470016da8600310809605dfa44 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Wed, 9 Mar 2022 13:17:04 -0500 Subject: [PATCH 03/48] Remove space --- repo.go | 1 - 1 file changed, 1 deletion(-) diff --git a/repo.go b/repo.go index 43de6bb7..6aadb09c 100644 --- a/repo.go +++ b/repo.go @@ -1126,7 +1126,6 @@ func (r *Repo) writeTopLevelTargetWithExpires(t *data.Targets, expires time.Time fmt.Println("*", k) } } - return err } From a235746411f8c8707863a7038293778ba243845c Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Fri, 25 Mar 2022 19:09:29 -0400 Subject: [PATCH 04/48] Move hash bin impl to pkg/targets --- repo.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/repo.go b/repo.go index 6aadb09c..20c9bc1b 100644 --- a/repo.go +++ b/repo.go @@ -14,7 +14,6 @@ import ( "github.com/theupdateframework/go-tuf/data" "github.com/theupdateframework/go-tuf/internal/roles" "github.com/theupdateframework/go-tuf/internal/signer" - itargets "github.com/theupdateframework/go-tuf/internal/targets" // FIXME "github.com/theupdateframework/go-tuf/pkg/keys" "github.com/theupdateframework/go-tuf/pkg/targets" "github.com/theupdateframework/go-tuf/sign" @@ -609,7 +608,7 @@ func (r *Repo) AddTargetsDelegationsForPathHashBins(delegator string, binRolePre // space into bins using the PathHashPrefixes delegation mechanism. New // metadata is written with the given expiration time. func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, binRolePrefix string, prefixBitLen int, keys []*data.PublicKey, threshold int, expires time.Time) error { - bins, err := itargets.NewHashBins(binRolePrefix, prefixBitLen) + bins, err := targets.NewHashBins(binRolePrefix, prefixBitLen) if err != nil { return err } From 0facfb0638deb59ef815a345856f7361339c413d Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sat, 26 Mar 2022 20:23:20 -0400 Subject: [PATCH 05/48] Fix build after rebase --- repo.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/repo.go b/repo.go index 20c9bc1b..a4719688 100644 --- a/repo.go +++ b/repo.go @@ -82,7 +82,7 @@ func (r *Repo) Init(consistentSnapshot bool) error { if err = r.setTopLevelMeta("root.json", root); err != nil { return err } - if err = r.writeTargetWithExpires(t, data.DefaultExpires("targets")); err != nil { + if err = r.writeTopLevelTargetWithExpires(t, data.DefaultExpires("targets")); err != nil { return err } fmt.Println("Repository initialized") @@ -369,7 +369,10 @@ func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicK // Not compatible with delegated roles. if !roles.IsTopLevelRole(keyRole) { - return ErrInvalidRole{keyRole} + return ErrInvalidRole{ + Role: keyRole, + Reason: "only top level targets roles are suppoerted", + } } if !validExpires(expires) { From 08b738b6b4a819188fb42e9a03c9461963c70ad8 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sat, 26 Mar 2022 21:53:14 -0400 Subject: [PATCH 06/48] Make AddTargetsWithDigest support delegated targets --- repo.go | 73 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/repo.go b/repo.go index a4719688..8517281e 100644 --- a/repo.go +++ b/repo.go @@ -82,9 +82,12 @@ func (r *Repo) Init(consistentSnapshot bool) error { if err = r.setTopLevelMeta("root.json", root); err != nil { return err } - if err = r.writeTopLevelTargetWithExpires(t, data.DefaultExpires("targets")); err != nil { + + t.Version = 1 + if err = r.setTopLevelMeta("targets.json", t); err != nil { return err } + fmt.Println("Repository initialized") return nil } @@ -938,11 +941,22 @@ func (r *Repo) AddTargetsToPreferredRole(paths []string, custom json.RawMessage, } func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int64, path string, custom json.RawMessage) error { + // TODO: Rename this to AddTargetWithDigest + // https://github.com/theupdateframework/go-tuf/issues/242 + expires := data.DefaultExpires("targets") + path = util.NormalizeTarget(path) - // TODO: support delegated targets - // FIXME in this PR - t, err := r.topLevelTargets() + targetsMeta, delegation, err := r.targetDelegationForPath(path, "") + if err != nil { + return err + } + // This is the targets role that needs to sign the target file. + targetsRoleName := delegation.Delegatee.Name + + // FIXME: get a complete set of signers, not just the set from the delegation + // traversal. + signers, err := r.getSignersInDB(targetsRoleName, delegation.DB) if err != nil { return err } @@ -957,13 +971,25 @@ func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int6 // metadata if len(custom) > 0 { meta.Custom = &custom - } else if t, ok := t.Targets[path]; ok { + } else if t, ok := targetsMeta.Targets[path]; ok { meta.Custom = t.Custom } - t.Targets[path] = data.TargetFileMeta{FileMeta: meta} + // What does G2 mean? Copying and pasting this comment along with the + // delete line. + // + // G2 -> we no longer desire any readers to ever observe non-prefix targets. + delete(targetsMeta.Targets, "/"+path) + targetsMeta.Targets[path] = data.TargetFileMeta{FileMeta: meta} + + targetsMeta.Expires = expires.Round(time.Second) - return r.writeTopLevelTargetWithExpires(t, expires) + manifestName := targetsRoleName + ".json" + if !r.local.FileIsStaged(manifestName) { + targetsMeta.Version++ + } + + return r.setMetaWithSigners(manifestName, targetsMeta, signers) } func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error { @@ -983,10 +1009,11 @@ type targetsMetaWithSigners struct { signers []keys.Signer } -// AddTargetsWithExpiresToPreferredRole signs the staged targets at `paths`. If -// preferredRole is not nil, the target is added to the given role's manifest -// if delegations allow it. If delegations do not allow the preferredRole to -// sign the given path, an error is returned. +// AddTargetsWithExpiresToPreferredRole signs the staged targets at `paths`. +// +// If preferredRole is not the empty string, the target is added to the given +// role's manifest if delegations allow it. If delegations do not allow the +// preferredRole to sign the given path, an error is returned. func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json.RawMessage, expires time.Time, preferredRole string) error { if !validExpires(expires) { return ErrInvalidExpires{expires} @@ -1014,12 +1041,15 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. return err } + // FIXME: get a complete set of all possible signers, not just the ones + // along the delegation path used to reach the preferredRole. + // The delegations determine the keys that need to be used to sign each // target file. If a particular target role is reachable in the delegation // graph through multiple paths, it may be the case that the targets - // manifest needs signatures from multiple sets of keys. Therefore, we + // manifest needs signatures from multiple sets of keys. Therefore, we // merge the keys specified by the current delegation with keys specified - // bypreviously seen delegations. + // by previously seen delegations. if prevMetaWithSigners, ok := targetsMetaToWrite[targetsRoleName]; ok { // Start with meta staged in targetsMetaToWrite instead of the committed meta. targetsMeta = prevMetaWithSigners.meta @@ -1048,7 +1078,6 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. if err != nil { return err } - path = util.NormalizeTarget(path) // if we have custom metadata, set it, otherwise maintain // existing metadata if present @@ -1115,22 +1144,6 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. return nil } -func (r *Repo) writeTopLevelTargetWithExpires(t *data.Targets, expires time.Time) error { - t.Expires = expires.Round(time.Second) - if !r.local.FileIsStaged("targets.json") { - t.Version++ - } - - err := r.setTopLevelMeta("targets.json", t) - if err == nil && len(t.Targets) > 0 { - fmt.Println("Added/staged targets:") - for k := range t.Targets { - fmt.Println("*", k) - } - } - return err -} - func (r *Repo) RemoveTarget(path string) error { return r.RemoveTargets([]string{path}) } From 99917104de4c840f57ffcd954d08db0ef60f57f6 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 14:11:25 -0400 Subject: [PATCH 07/48] Remove unused GetPublicKeys --- repo.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/repo.go b/repo.go index 8517281e..7848eb51 100644 --- a/repo.go +++ b/repo.go @@ -1516,15 +1516,3 @@ func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, e } return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...) } - -func (r *Repo) GetPublicKeys(role string) ([]*data.PublicKey, error) { - signers, err := r.local.GetSigners(role) - if err != nil { - return nil, err - } - pubkeys := []*data.PublicKey{} - for _, signer := range signers { - pubkeys = append(pubkeys, signer.PublicData()) - } - return pubkeys, nil -} From 619e3b2f142aa6d8d11495b00f711fa4d5d57344 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 14:25:44 -0400 Subject: [PATCH 08/48] Revert "Remove unused GetPublicKeys" This reverts commit 3daaeebce3318af575481c54e0359cb78d8363fd. --- repo.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/repo.go b/repo.go index 7848eb51..8517281e 100644 --- a/repo.go +++ b/repo.go @@ -1516,3 +1516,15 @@ func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, e } return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...) } + +func (r *Repo) GetPublicKeys(role string) ([]*data.PublicKey, error) { + signers, err := r.local.GetSigners(role) + if err != nil { + return nil, err + } + pubkeys := []*data.PublicKey{} + for _, signer := range signers { + pubkeys = append(pubkeys, signer.PublicData()) + } + return pubkeys, nil +} From 087db51cb0b7b65aa29f30490ce0de4f27ec696a Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 14:27:38 -0400 Subject: [PATCH 09/48] Fix typo --- repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.go b/repo.go index 8517281e..a5d0ff41 100644 --- a/repo.go +++ b/repo.go @@ -374,7 +374,7 @@ func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicK if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{ Role: keyRole, - Reason: "only top level targets roles are suppoerted", + Reason: "only top level targets roles are supported", } } From c6cabeb0cceb4c272caa77cd53d62e50faff69d4 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 14:59:13 -0400 Subject: [PATCH 10/48] Easier hash bin usage when setting up delegations --- repo.go | 11 +++-------- repo_test.go | 32 +++++++++++++++----------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/repo.go b/repo.go index a5d0ff41..9f6587ab 100644 --- a/repo.go +++ b/repo.go @@ -605,20 +605,15 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg // AddTargetsDelegationsForPathHashBins is equivalent to // AddTargetsDelegationsForPathHashBinsWithExpires, but with a default // expiration time. -func (r *Repo) AddTargetsDelegationsForPathHashBins(delegator string, binRolePrefix string, prefixBitLen int, keys []*data.PublicKey, threshold int) error { - return r.AddTargetsDelegationsForPathHashBinsWithExpires(delegator, binRolePrefix, prefixBitLen, keys, threshold, data.DefaultExpires("targets")) +func (r *Repo) AddTargetsDelegationsForPathHashBins(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int) error { + return r.AddTargetsDelegationsForPathHashBinsWithExpires(delegator, bins, keys, threshold, data.DefaultExpires("targets")) } // AddTargetsDelegationsForPathHashBinsWithExpires adds 2^(prefixBitLen) // delegations to the delegator role, which partition the target path hash // space into bins using the PathHashPrefixes delegation mechanism. New // metadata is written with the given expiration time. -func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, binRolePrefix string, prefixBitLen int, keys []*data.PublicKey, threshold int, expires time.Time) error { - bins, err := targets.NewHashBins(binRolePrefix, prefixBitLen) - if err != nil { - return err - } - +func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int, expires time.Time) error { keyIDs := []string{} for _, key := range keys { keyIDs = append(keyIDs, key.IDs()...) diff --git a/repo_test.go b/repo_test.go index a526e09d..ae12da85 100644 --- a/repo_test.go +++ b/repo_test.go @@ -21,6 +21,7 @@ import ( "github.com/theupdateframework/go-tuf/encrypted" "github.com/theupdateframework/go-tuf/internal/sets" "github.com/theupdateframework/go-tuf/pkg/keys" + "github.com/theupdateframework/go-tuf/pkg/targets" "github.com/theupdateframework/go-tuf/util" "github.com/theupdateframework/go-tuf/verify" "golang.org/x/crypto/ed25519" @@ -1989,22 +1990,19 @@ func (rs *RepoSuite) TestHashedBinDelegations(c *C) { leafKey, err := keys.GenerateEd25519Key() c.Assert(err, IsNil) - err = local.SaveSigner("bins_0-1", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_2-3", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_4-5", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_6-7", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_8-9", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_a-b", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_c-d", leafKey) - c.Assert(err, IsNil) - err = local.SaveSigner("bins_e-f", leafKey) - c.Assert(err, IsNil) + + hb, err := targets.NewHashBins("bins_", 3) + if err != nil { + c.Assert(err, IsNil) + } + + for i := uint64(0); i < hb.NumBins(); i++ { + b := hb.GetBin(i) + err = local.SaveSigner(b.RoleName(), leafKey) + if err != nil { + c.Assert(err, IsNil) + } + } err = r.AddTargetsDelegation("targets", data.DelegatedRole{ Name: "bins", @@ -2016,7 +2014,7 @@ func (rs *RepoSuite) TestHashedBinDelegations(c *C) { }) c.Assert(err, IsNil) - err = r.AddTargetsDelegationsForPathHashBins("bins", "bins_", 3, []*data.PublicKey{leafKey.PublicData()}, 1) + err = r.AddTargetsDelegationsForPathHashBins("bins", hb, []*data.PublicKey{leafKey.PublicData()}, 1) c.Assert(err, IsNil) targets, err := r.targets("bins") c.Assert(err, IsNil) From 50f25407ea63c7ba11104776feecb3740e45b138 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 15:08:53 -0400 Subject: [PATCH 11/48] Remove comment that ChangePassphrase doesn't support delegated roles --- repo.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/repo.go b/repo.go index 9f6587ab..2728522c 100644 --- a/repo.go +++ b/repo.go @@ -299,12 +299,6 @@ func (r *Repo) timestamp() (*data.Timestamp, error) { } func (r *Repo) ChangePassphrase(keyRole string) error { - // Not compatible with delegated roles. - - if !roles.IsTopLevelRole(keyRole) { - return ErrInvalidRole{keyRole, "only support passphrases for top-level roles"} - } - if p, ok := r.local.(PassphraseChanger); ok { return p.ChangePassphrase(keyRole) } From 0854893b4c5166eca5eb3b79790d40de0f8e694b Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 19:07:33 -0400 Subject: [PATCH 12/48] Update comments --- repo.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/repo.go b/repo.go index 2728522c..46e997cb 100644 --- a/repo.go +++ b/repo.go @@ -307,13 +307,15 @@ func (r *Repo) ChangePassphrase(keyRole string) error { } func (r *Repo) GenKey(role string) ([]string, error) { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). return r.GenKeyWithExpires(role, data.DefaultExpires(role)) } func (r *Repo) GenKeyWithExpires(keyRole string, expires time.Time) (keyids []string, err error) { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). signer, err := keys.GenerateEd25519Key() if err != nil { @@ -328,13 +330,16 @@ func (r *Repo) GenKeyWithExpires(keyRole string, expires time.Time) (keyids []st } func (r *Repo) AddPrivateKey(role string, signer keys.Signer) error { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). return r.AddPrivateKeyWithExpires(role, signer, data.DefaultExpires(role)) } func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expires time.Time) error { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). + if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{keyRole, "only support adding keys for top-level roles"} } @@ -357,13 +362,15 @@ func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expi } func (r *Repo) AddVerificationKey(keyRole string, pk *data.PublicKey) error { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). return r.AddVerificationKeyWithExpiration(keyRole, pk, data.DefaultExpires(keyRole)) } func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicKey, expires time.Time) error { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{ @@ -446,13 +453,15 @@ func (r *Repo) RootKeys() ([]*data.PublicKey, error) { } func (r *Repo) RevokeKey(role, id string) error { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). return r.RevokeKeyWithExpires(role, id, data.DefaultExpires("root")) } func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error { - // Not compatible with delegated roles. + // Not compatible with delegated targets roles, since delegated targets keys + // are associated with a delegation (edge), not a role (node). if !roles.IsTopLevelRole(keyRole) { return ErrInvalidRole{keyRole, "only revocations for top-level roles supported"} From 759410f552eddb7dfa1e6dc81c12257b061df354 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 19:42:11 -0400 Subject: [PATCH 13/48] Add API to reset delegations --- repo.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/repo.go b/repo.go index 46e997cb..ef6a92ce 100644 --- a/repo.go +++ b/repo.go @@ -640,6 +640,42 @@ func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, return nil } +// ResetTargetsDelegation is equivalent to ResetTargetsDelegationsWithExpires +// with a default expiry time. +func (r *Repo) ResetTargetsDelegations(delegator string) error { + return r.ResetTargetsDelegationsWithExpires(delegator, data.DefaultExpires("targets")) +} + +// ResetTargetsDelegationsWithExpires removes all targets delegations from the +// given delegator role, and updates the delegator role's expiration time. +func (r *Repo) ResetTargetsDelegationsWithExpires(delegator string, expires time.Time) error { + t, err := r.targets(delegator) + if err != nil { + return fmt.Errorf("error getting delegator (%q) metadata: %w", delegator, err) + } + + t.Delegations = &data.Delegations{} + t.Delegations.Keys = make(map[string]*data.PublicKey) + + t.Expires = expires.Round(time.Second) + + delegatorFile := delegator + ".json" + if !r.local.FileIsStaged(delegatorFile) { + t.Version++ + } + + delegatorSigners, err := r.local.GetSigners(delegator) + if err != nil { + return fmt.Errorf("error getting signers for delegator %v: %w", delegator, err) + } + err = r.setMetaWithSigners(delegatorFile, t, delegatorSigners) + if err != nil { + return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err) + } + + return nil +} + func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) { if r.prefix == "" && r.indent == "" { return json.Marshal(v) From 57656df547afcbb0881d881dfbb22d0917f9e054 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 19:53:22 -0400 Subject: [PATCH 14/48] clean up verifySignatures --- repo.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/repo.go b/repo.go index ef6a92ce..db7ed180 100644 --- a/repo.go +++ b/repo.go @@ -1518,18 +1518,21 @@ func (r *Repo) verifySignatures(metaFilename string) error { if err := db.Verify(s, role, 0); err != nil { return ErrInsufficientSignatures{metaFilename, err} } - } else { - dbs, err := r.delegatorDBs(role) - if err != nil { - return err - } + return nil + } - for delegator, db := range dbs { - if err := db.Verify(s, role, 0); err != nil { - return ErrInsufficientSignatures{delegator, err} + dbs, err := r.delegatorDBs(role) + if err != nil { + return err + } + + for delegator, db := range dbs { + if err := db.Verify(s, role, 0); err != nil { + return ErrInsufficientSignatures{ + Name: metaFilename, + Err: fmt.Errorf("delegator %q unable to verify metadata: %w", delegator, err), } } - } return nil From 5a0932d499c386588560f2dd9f6fcfd4a0cce759 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 19:56:50 -0400 Subject: [PATCH 15/48] Fix tests for ChangePassphrase --- repo_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/repo_test.go b/repo_test.go index ae12da85..95d5a5b3 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1390,16 +1390,20 @@ func (rs *RepoSuite) TestKeyPersistence(c *C) { c.Assert(insecureStore.SaveSigner("targets", signer), IsNil) assertKeys("targets", false, []*data.PrivateKey{privateKey}) + c.Assert(insecureStore.SaveSigner("foo", signer), IsNil) + assertKeys("foo", false, []*data.PrivateKey{privateKey}) + // Test changing the passphrase // 1. Create a secure store with a passphrase (create new object and temp folder so we discard any previous state) tmp = newTmpDir(c) store = FileSystemStore(tmp.path, testPassphraseFunc) - // 1.5. Changing passphrase only works for top-level roles. + // 1.5. Changing passphrase works for top-level and delegated roles. r, err := NewRepo(store) c.Assert(err, IsNil) - c.Assert(r.ChangePassphrase("foo"), DeepEquals, ErrInvalidRole{"foo", "only support passphrases for top-level roles"}) + c.Assert(r.ChangePassphrase("targets"), NotNil) + c.Assert(r.ChangePassphrase("foo"), NotNil) // 2. Test changing the passphrase when the keys file does not exist - should FAIL c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), NotNil) From 9e694fc33fa586530bce2bfcef6e86d42144a0b3 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 19:59:32 -0400 Subject: [PATCH 16/48] Fix comments --- repo.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.go b/repo.go index db7ed180..c1c08ad7 100644 --- a/repo.go +++ b/repo.go @@ -612,10 +612,9 @@ func (r *Repo) AddTargetsDelegationsForPathHashBins(delegator string, bins *targ return r.AddTargetsDelegationsForPathHashBinsWithExpires(delegator, bins, keys, threshold, data.DefaultExpires("targets")) } -// AddTargetsDelegationsForPathHashBinsWithExpires adds 2^(prefixBitLen) -// delegations to the delegator role, which partition the target path hash -// space into bins using the PathHashPrefixes delegation mechanism. New -// metadata is written with the given expiration time. +// AddTargetsDelegationsForPathHashBinsWithExpires adds delegations to the +// delegator role for the given hash bins configuration. New metadata is +// written with the given expiration time. func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int, expires time.Time) error { keyIDs := []string{} for _, key := range keys { @@ -647,7 +646,8 @@ func (r *Repo) ResetTargetsDelegations(delegator string) error { } // ResetTargetsDelegationsWithExpires removes all targets delegations from the -// given delegator role, and updates the delegator role's expiration time. +// given delegator role. New metadata is written with the given expiration +// time. func (r *Repo) ResetTargetsDelegationsWithExpires(delegator string, expires time.Time) error { t, err := r.targets(delegator) if err != nil { From 4c94b6ff23912c736c59b748a7483dad2acac9db Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 20:04:37 -0400 Subject: [PATCH 17/48] Deduplicate key IDs when setting up hash bins --- repo.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/repo.go b/repo.go index c1c08ad7..9e920cc5 100644 --- a/repo.go +++ b/repo.go @@ -13,6 +13,7 @@ import ( "github.com/theupdateframework/go-tuf/data" "github.com/theupdateframework/go-tuf/internal/roles" + "github.com/theupdateframework/go-tuf/internal/sets" "github.com/theupdateframework/go-tuf/internal/signer" "github.com/theupdateframework/go-tuf/pkg/keys" "github.com/theupdateframework/go-tuf/pkg/targets" @@ -627,7 +628,7 @@ func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, name := bin.RoleName() err := r.AddTargetsDelegationWithExpires(delegator, data.DelegatedRole{ Name: name, - KeyIDs: keyIDs, + KeyIDs: sets.DeduplicateStrings(keyIDs), PathHashPrefixes: bin.HashPrefixes(), Threshold: threshold, }, keys, expires) From f5f045dea3c19f18adf425e8f2c56e83026aff71 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 20:04:42 -0400 Subject: [PATCH 18/48] Revert "Revert "Remove unused GetPublicKeys"" This reverts commit dbe6a4d014089267239026a33d1f6950a99f83b8. --- repo.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/repo.go b/repo.go index 9e920cc5..4b48fd64 100644 --- a/repo.go +++ b/repo.go @@ -1554,15 +1554,3 @@ func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, e } return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...) } - -func (r *Repo) GetPublicKeys(role string) ([]*data.PublicKey, error) { - signers, err := r.local.GetSigners(role) - if err != nil { - return nil, err - } - pubkeys := []*data.PublicKey{} - for _, signer := range signers { - pubkeys = append(pubkeys, signer.PublicData()) - } - return pubkeys, nil -} From da8294885c7884c559e99a0be367ebefd1bc8faf Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 20:16:14 -0400 Subject: [PATCH 19/48] Add warning about GetSigners. TODO: fix usage --- local_store.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/local_store.go b/local_store.go index 425b1111..375a1efc 100644 --- a/local_store.go +++ b/local_store.go @@ -42,6 +42,8 @@ type LocalStore interface { Commit(bool, map[string]int64, map[string]data.Hashes) error // GetSigners return a list of signers for a role. + // This may include revoked keys, so the signers should not + // be used without filtering. GetSigners(role string) ([]keys.Signer, error) // SaveSigner adds a signer to a role. From 2b81fbe3716c006dd0ad685f160adb67eacd05b2 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 20:48:22 -0400 Subject: [PATCH 20/48] Clean up some usage of setMetawithSigners --- repo.go | 85 +++++++++++++++++++++++++++++----------------------- repo_test.go | 2 +- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/repo.go b/repo.go index 4b48fd64..2968af64 100644 --- a/repo.go +++ b/repo.go @@ -80,12 +80,12 @@ func (r *Repo) Init(consistentSnapshot bool) error { root.ConsistentSnapshot = consistentSnapshot // Set root version to 1 for a new root. root.Version = 1 - if err = r.setTopLevelMeta("root.json", root); err != nil { + if err = r.setMeta("root.json", root); err != nil { return err } t.Version = 1 - if err = r.setTopLevelMeta("targets.json", t); err != nil { + if err = r.setMeta("targets.json", t); err != nil { return err } @@ -200,7 +200,7 @@ func (r *Repo) SetThreshold(keyRole string, t int) error { if !r.local.FileIsStaged("root.json") { root.Version++ } - return r.setTopLevelMeta("root.json", root) + return r.setMeta("root.json", root) } func (r *Repo) Targets() (data.TargetFiles, error) { @@ -217,7 +217,7 @@ func (r *Repo) SetTargetsVersion(v int64) error { return err } t.Version = v - return r.setTopLevelMeta("targets.json", t) + return r.setMeta("targets.json", t) } func (r *Repo) TargetsVersion() (int64, error) { @@ -234,7 +234,7 @@ func (r *Repo) SetTimestampVersion(v int64) error { return err } ts.Version = v - return r.setTopLevelMeta("timestamp.json", ts) + return r.setMeta("timestamp.json", ts) } func (r *Repo) TimestampVersion() (int64, error) { @@ -252,7 +252,7 @@ func (r *Repo) SetSnapshotVersion(v int64) error { } s.Version = v - return r.setTopLevelMeta("snapshot.json", s) + return r.setMeta("snapshot.json", s) } func (r *Repo) SnapshotVersion() (int64, error) { @@ -412,7 +412,7 @@ func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicK root.Version++ } - return r.setTopLevelMeta("root.json", root) + return r.setMeta("root.json", root) } func validExpires(expires time.Time) bool { @@ -523,7 +523,7 @@ func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error root.Version++ } - err = r.setTopLevelMeta("root.json", root) + err = r.setMeta("root.json", root) if err == nil { fmt.Println("Revoked", keyRole, "key with ID", id, "in root metadata") } @@ -541,6 +541,8 @@ func (r *Repo) AddTargetsDelegation(delegator string, role data.DelegatedRole, k // should have corresponding Key entries in the keys argument. New metadata is // written with the given expiration time. func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error { + expires = expires.Round(time.Second) + t, err := r.targets(delegator) if err != nil { return fmt.Errorf("error getting delegator (%q) metadata: %w", delegator, err) @@ -567,18 +569,14 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg } } t.Delegations.Roles = append(t.Delegations.Roles, role) - t.Expires = expires.Round(time.Second) + t.Expires = expires delegatorFile := delegator + ".json" if !r.local.FileIsStaged(delegatorFile) { t.Version++ } - delegatorSigners, err := r.local.GetSigners(delegator) - if err != nil { - return fmt.Errorf("error getting signers for delegator %v: %w", delegator, err) - } - err = r.setMetaWithSigners(delegatorFile, t, delegatorSigners) + err = r.setMeta(delegatorFile, t) if err != nil { return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err) } @@ -588,17 +586,14 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg if err != nil { return fmt.Errorf("error getting delegatee (%q) metadata: %w", delegatee, err) } + dt.Expires = expires delegateeFile := delegatee + ".json" if !r.local.FileIsStaged(delegateeFile) { dt.Version++ } - delegateeSigners, err := r.local.GetSigners(delegatee) - if err != nil { - return fmt.Errorf("error getting signers for delegatee %v: %w", delegatee, err) - } - err = r.setMetaWithSigners(delegateeFile, dt, delegateeSigners) + err = r.setMeta(delegateeFile, dt) if err != nil { return fmt.Errorf("error setting metadata for %q: %w", delegateeFile, err) } @@ -665,11 +660,7 @@ func (r *Repo) ResetTargetsDelegationsWithExpires(delegator string, expires time t.Version++ } - delegatorSigners, err := r.local.GetSigners(delegator) - if err != nil { - return fmt.Errorf("error getting signers for delegator %v: %w", delegator, err) - } - err = r.setMetaWithSigners(delegatorFile, t, delegatorSigners) + err = r.setMeta(delegatorFile, t) if err != nil { return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err) } @@ -684,15 +675,37 @@ func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) { return json.MarshalIndent(v, r.prefix, r.indent) } -func (r *Repo) setTopLevelMeta(roleFilename string, meta interface{}) error { - db, err := r.topLevelKeysDB() - if err != nil { - return err +func (r *Repo) setMeta(roleFilename string, meta interface{}) error { + role := strings.TrimSuffix(roleFilename, ".json") + + dbs := []*verify.DB{} + if roles.IsTopLevelRole(role) { + db, err := r.topLevelKeysDB() + if err != nil { + return err + } + dbs = append(dbs, db) + } else { + dbm, err := r.delegatorDBs(role) + if err != nil { + return err + } + + for _, db := range dbm { + dbs = append(dbs, db) + } } - signers, err := r.getSignersInDB(strings.TrimSuffix(roleFilename, ".json"), db) - if err != nil { - return err + + signers := []keys.Signer{} + for _, db := range dbs { + ss, err := r.getSignersInDB(role, db) + if err != nil { + return err + } + + signers = append(signers, ss...) } + return r.setMetaWithSigners(roleFilename, meta, signers) } @@ -1010,9 +1023,7 @@ func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int6 meta.Custom = t.Custom } - // What does G2 mean? Copying and pasting this comment along with the - // delete line. - // + // What does G2 mean? Copying and pasting this comment from elsewhere in this file. // G2 -> we no longer desire any readers to ever observe non-prefix targets. delete(targetsMeta.Targets, "/"+path) targetsMeta.Targets[path] = data.TargetFileMeta{FileMeta: meta} @@ -1230,7 +1241,7 @@ func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error t.Version++ } - err = r.setTopLevelMeta("targets.json", t) + err = r.setMeta("targets.json", t) if err == nil { fmt.Println("Removed targets:") for _, v := range removed_targets { @@ -1289,7 +1300,7 @@ func (r *Repo) SnapshotWithExpires(expires time.Time) error { if !r.local.FileIsStaged("snapshot.json") { snapshot.Version++ } - err = r.setTopLevelMeta("snapshot.json", snapshot) + err = r.setMeta("snapshot.json", snapshot) if err == nil { fmt.Println("Staged snapshot.json metadata with expiration date:", snapshot.Expires) } @@ -1321,7 +1332,7 @@ func (r *Repo) TimestampWithExpires(expires time.Time) error { timestamp.Version++ } - err = r.setTopLevelMeta("timestamp.json", timestamp) + err = r.setMeta("timestamp.json", timestamp) if err == nil { fmt.Println("Staged timestamp.json metadata with expiration date:", timestamp.Expires) } diff --git a/repo_test.go b/repo_test.go index 95d5a5b3..c953b82c 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1563,7 +1563,7 @@ func (rs *RepoSuite) TestUnknownKeyIDs(c *C) { c.Assert(root.Version, Equals, int64(1)) root.Keys["unknown-key-id"] = signer.PublicData() - r.setTopLevelMeta("root.json", root) + r.setMeta("root.json", root) // commit the metadata to the store. c.Assert(r.AddTargets([]string{}, nil), IsNil) From 601a815b076966a89f1129c146c4064c0d63882b Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 20:52:03 -0400 Subject: [PATCH 21/48] Remove setMetaWithSigners since it's easy to misuse --- repo.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/repo.go b/repo.go index 2968af64..15d94fd3 100644 --- a/repo.go +++ b/repo.go @@ -706,10 +706,6 @@ func (r *Repo) setMeta(roleFilename string, meta interface{}) error { signers = append(signers, ss...) } - return r.setMetaWithSigners(roleFilename, meta, signers) -} - -func (r *Repo) setMetaWithSigners(roleFilename string, meta interface{}, signers []keys.Signer) error { s, err := sign.Marshal(meta, signers...) if err != nil { return err @@ -1002,13 +998,6 @@ func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int6 // This is the targets role that needs to sign the target file. targetsRoleName := delegation.Delegatee.Name - // FIXME: get a complete set of signers, not just the set from the delegation - // traversal. - signers, err := r.getSignersInDB(targetsRoleName, delegation.DB) - if err != nil { - return err - } - meta := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)} meta.Hashes[digestAlg], err = hex.DecodeString(digest) if err != nil { @@ -1035,7 +1024,12 @@ func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int6 targetsMeta.Version++ } - return r.setMetaWithSigners(manifestName, targetsMeta, signers) + err = r.setMeta(manifestName, targetsMeta) + if err != nil { + return fmt.Errorf("error setting metadata for %q: %w", manifestName, err) + } + + return nil } func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error { @@ -1172,7 +1166,6 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. exp := expires.Round(time.Second) for roleName, tm := range targetsMetaToWrite { meta := tm.meta - signers := tm.signers meta.Expires = exp @@ -1181,9 +1174,9 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. meta.Version++ } - err := r.setMetaWithSigners(manifestName, meta, signers) + err := r.setMeta(manifestName, meta) if err != nil { - return err + return fmt.Errorf("error setting metadata for %q: %w", manifestName, err) } } From 36b49f9fbddd02f030f3a03f6cb4f30d89271f86 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 21:31:07 -0400 Subject: [PATCH 22/48] cleanup --- repo.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/repo.go b/repo.go index 15d94fd3..42b3740a 100644 --- a/repo.go +++ b/repo.go @@ -161,9 +161,9 @@ func (r *Repo) RootVersion() (int64, error) { } func (r *Repo) GetThreshold(keyRole string) (int, error) { - if !roles.IsTopLevelRole(keyRole) { - // Delegations are not currently supported, so return an error if this is not a - // top-level metadata file. + if roles.IsDelegatedTargetsRole(keyRole) { + // The signature threshold for a delegated targets role + // depends on the incoming delegation edge. return -1, ErrInvalidRole{keyRole, "only thresholds for top-level roles supported"} } root, err := r.root() @@ -179,9 +179,9 @@ func (r *Repo) GetThreshold(keyRole string) (int, error) { } func (r *Repo) SetThreshold(keyRole string, t int) error { - if !roles.IsTopLevelRole(keyRole) { - // Delegations are not currently supported, so return an error if this is not a - // top-level metadata file. + if roles.IsDelegatedTargetsRole(keyRole) { + // The signature threshold for a delegated targets role + // depends on the incoming delegation edge. return ErrInvalidRole{keyRole, "only thresholds for top-level roles supported"} } root, err := r.root() @@ -341,7 +341,9 @@ func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expi // Not compatible with delegated targets roles, since delegated targets keys // are associated with a delegation (edge), not a role (node). - if !roles.IsTopLevelRole(keyRole) { + if roles.IsDelegatedTargetsRole(keyRole) { + // The signature threshold for a delegated targets role + // depends on the incoming delegation edge. return ErrInvalidRole{keyRole, "only support adding keys for top-level roles"} } @@ -373,7 +375,7 @@ func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicK // Not compatible with delegated targets roles, since delegated targets keys // are associated with a delegation (edge), not a role (node). - if !roles.IsTopLevelRole(keyRole) { + if roles.IsDelegatedTargetsRole(keyRole) { return ErrInvalidRole{ Role: keyRole, Reason: "only top level targets roles are supported", @@ -464,7 +466,7 @@ func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error // Not compatible with delegated targets roles, since delegated targets keys // are associated with a delegation (edge), not a role (node). - if !roles.IsTopLevelRole(keyRole) { + if roles.IsDelegatedTargetsRole(keyRole) { return ErrInvalidRole{keyRole, "only revocations for top-level roles supported"} } @@ -554,11 +556,10 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg } for _, keyID := range role.KeyIDs { - keyLoop: for _, key := range keys { if key.ContainsID(keyID) { t.Delegations.Keys[keyID] = key - break keyLoop + break } } } From a44b8e9d014cb84de5510e1e23f4e75ca99d2338 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 21:33:49 -0400 Subject: [PATCH 23/48] cleanup --- repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.go b/repo.go index 42b3740a..76d25eb1 100644 --- a/repo.go +++ b/repo.go @@ -837,7 +837,7 @@ func (r *Repo) getSignersInDB(roleName string, db *verify.DB) ([]keys.Signer, er return nil, nil } - signersInDB := make([]keys.Signer, 0, len(role.KeyIDs)) + signersInDB := []keys.Signer{} for _, s := range signers { for _, id := range s.PublicData().IDs() { if _, ok := role.KeyIDs[id]; ok { From b403fc07bcc74d4c7941a66aa8ee61512da923ac Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Sun, 27 Mar 2022 21:49:32 -0400 Subject: [PATCH 24/48] Cleanup usage of delegatorDBs --- repo.go | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/repo.go b/repo.go index 76d25eb1..2db15536 100644 --- a/repo.go +++ b/repo.go @@ -687,14 +687,12 @@ func (r *Repo) setMeta(roleFilename string, meta interface{}) error { } dbs = append(dbs, db) } else { - dbm, err := r.delegatorDBs(role) + ddbs, err := r.delegatorDBs(role) if err != nil { return err } - for _, db := range dbm { - dbs = append(dbs, db) - } + dbs = append(dbs, ddbs...) } signers := []keys.Signer{} @@ -864,8 +862,9 @@ func (r *Repo) SignedMeta(roleFilename string) (*data.Signed, error) { return s, nil } -func (r *Repo) delegatorDBs(delegateeRole string) (map[string]*verify.DB, error) { - delegatorDBs := map[string]*verify.DB{} +// delegatorDBs returns a list of key DBs for all incoming delegations. +func (r *Repo) delegatorDBs(delegateeRole string) ([]*verify.DB, error) { + delegatorDBs := []*verify.DB{} for metaName := range r.meta { if roles.IsTopLevelManifest(metaName) && metaName != "targets.json" { continue @@ -897,7 +896,7 @@ func (r *Repo) delegatorDBs(delegateeRole string) (map[string]*verify.DB, error) return nil, err } - delegatorDBs[roleName] = db + delegatorDBs = append(delegatorDBs, db) } return delegatorDBs, nil @@ -1516,28 +1515,23 @@ func (r *Repo) verifySignatures(metaFilename string) error { role := strings.TrimSuffix(metaFilename, ".json") + dbs := []*verify.DB{} if roles.IsTopLevelRole(role) { db, err := r.topLevelKeysDB() if err != nil { return err } - if err := db.Verify(s, role, 0); err != nil { - return ErrInsufficientSignatures{metaFilename, err} + dbs = append(dbs, db) + } else { + dbs, err = r.delegatorDBs(role) + if err != nil { + return err } - return nil - } - - dbs, err := r.delegatorDBs(role) - if err != nil { - return err } - for delegator, db := range dbs { + for _, db := range dbs { if err := db.Verify(s, role, 0); err != nil { - return ErrInsufficientSignatures{ - Name: metaFilename, - Err: fmt.Errorf("delegator %q unable to verify metadata: %w", delegator, err), - } + return ErrInsufficientSignatures{metaFilename, err} } } From 167f279503a232a3494100c032a5fc168cc9e23e Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 19:53:06 -0400 Subject: [PATCH 25/48] Simplify AddTargetsWithExpiresToPreferredRole --- local_store.go | 1 + repo.go | 89 ++++++++++++-------------------------------------- 2 files changed, 22 insertions(+), 68 deletions(-) diff --git a/local_store.go b/local_store.go index 375a1efc..1af1f560 100644 --- a/local_store.go +++ b/local_store.go @@ -107,6 +107,7 @@ func (m *memoryStore) FileIsStaged(name string) bool { func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error { if len(paths) == 0 { for path, data := range m.files { + fmt.Println("pd", path, data) if err := targetsFn(path, bytes.NewReader(data)); err != nil { return err } diff --git a/repo.go b/repo.go index 2db15536..5a5dbea6 100644 --- a/repo.go +++ b/repo.go @@ -1045,8 +1045,8 @@ func (r *Repo) AddTargetWithExpiresToPreferredRole(path string, custom json.RawM } type targetsMetaWithSigners struct { - meta *data.Targets - signers []keys.Signer + meta *data.Targets + // signers []keys.Signer } // AddTargetsWithExpiresToPreferredRole signs the staged targets at `paths`. @@ -1064,54 +1064,23 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. normalizedPaths[i] = util.NormalizeTarget(path) } - // As we iterate through staged files, we accumulate changes to their - // corresponding targets manifests. - targetsMetaToWrite := map[string]*targetsMetaWithSigners{} + // As we through staged targets files, we accumulate changes to their + // corresponding targets metadata. + updatedTargetsMeta := map[string]*data.Targets{} if err := r.local.WalkStagedTargets(normalizedPaths, func(path string, target io.Reader) (err error) { - targetsMeta, delegation, err := r.targetDelegationForPath(path, preferredRole) + originalMeta, delegation, err := r.targetDelegationForPath(path, preferredRole) if err != nil { return err } + // This is the targets role that needs to sign the target file. targetsRoleName := delegation.Delegatee.Name - signers, err := r.getSignersInDB(targetsRoleName, delegation.DB) - if err != nil { - return err - } - - // FIXME: get a complete set of all possible signers, not just the ones - // along the delegation path used to reach the preferredRole. - - // The delegations determine the keys that need to be used to sign each - // target file. If a particular target role is reachable in the delegation - // graph through multiple paths, it may be the case that the targets - // manifest needs signatures from multiple sets of keys. Therefore, we - // merge the keys specified by the current delegation with keys specified - // by previously seen delegations. - if prevMetaWithSigners, ok := targetsMetaToWrite[targetsRoleName]; ok { - // Start with meta staged in targetsMetaToWrite instead of the committed meta. - targetsMeta = prevMetaWithSigners.meta - - mergedKeyIDs := map[string]struct{}{} - for _, signer := range signers { - for _, keyID := range signer.PublicData().IDs() { - mergedKeyIDs[keyID] = struct{}{} - } - } - - // Merge prevMetaWithSigners.signers with signers. - for _, prevSigner := range prevMetaWithSigners.signers { - keyIDLoop: - for _, keyID := range prevSigner.PublicData().IDs() { - if _, seen := mergedKeyIDs[keyID]; !seen { - signers = append(signers, prevSigner) - mergedKeyIDs[keyID] = struct{}{} - break keyIDLoop - } - } - } + targetsMeta := originalMeta + if tm, ok := updatedTargetsMeta[targetsRoleName]; ok { + // Metadata in updatedTargetsMeta overrides staged/commited metadata. + targetsMeta = tm } fileMeta, err := util.GenerateTargetFileMeta(target, r.hashAlgorithms...) @@ -1119,7 +1088,7 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. return err } - // if we have custom metadata, set it, otherwise maintain + // If we have custom metadata, set it, otherwise maintain // existing metadata if present if len(custom) > 0 { fileMeta.Custom = &custom @@ -1131,50 +1100,34 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. delete(targetsMeta.Targets, "/"+path) targetsMeta.Targets[path] = fileMeta - targetsMetaToWrite[targetsRoleName] = &targetsMetaWithSigners{ - meta: targetsMeta, - signers: signers, - } + updatedTargetsMeta[targetsRoleName] = targetsMeta return nil }); err != nil { return err } - if len(targetsMetaToWrite) == 0 { + if len(updatedTargetsMeta) == 0 { + // This is potentially unexpected behavior kept for backwards compatibility. + // See https://github.com/theupdateframework/go-tuf/issues/243 t, err := r.topLevelTargets() if err != nil { return err } - db, err := r.topLevelKeysDB() - if err != nil { - return err - } - - signers, err := r.getSignersInDB("targets", db) - if err != nil { - return err - } - - targetsMetaToWrite["targets"] = &targetsMetaWithSigners{ - meta: t, - signers: signers, - } + updatedTargetsMeta["targets"] = t } exp := expires.Round(time.Second) - for roleName, tm := range targetsMetaToWrite { - meta := tm.meta - - meta.Expires = exp + for roleName, targetsMeta := range updatedTargetsMeta { + targetsMeta.Expires = exp manifestName := roleName + ".json" if !r.local.FileIsStaged(manifestName) { - meta.Version++ + targetsMeta.Version++ } - err := r.setMeta(manifestName, meta) + err := r.setMeta(manifestName, targetsMeta) if err != nil { return fmt.Errorf("error setting metadata for %q: %w", manifestName, err) } From 19cb8a403142ffab301aadf7bd4d046b54f69b59 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 19:54:02 -0400 Subject: [PATCH 26/48] Remove debugging --- local_store.go | 1 - 1 file changed, 1 deletion(-) diff --git a/local_store.go b/local_store.go index 1af1f560..375a1efc 100644 --- a/local_store.go +++ b/local_store.go @@ -107,7 +107,6 @@ func (m *memoryStore) FileIsStaged(name string) bool { func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error { if len(paths) == 0 { for path, data := range m.files { - fmt.Println("pd", path, data) if err := targetsFn(path, bytes.NewReader(data)); err != nil { return err } From b95b1e58a0b0d5a93756fb2fe77d3c926cc267b6 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 19:55:48 -0400 Subject: [PATCH 27/48] Remove targetsMetaWithSigners --- repo.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/repo.go b/repo.go index 5a5dbea6..d0681f33 100644 --- a/repo.go +++ b/repo.go @@ -1044,11 +1044,6 @@ func (r *Repo) AddTargetWithExpiresToPreferredRole(path string, custom json.RawM return r.AddTargetsWithExpiresToPreferredRole([]string{path}, custom, expires, preferredRole) } -type targetsMetaWithSigners struct { - meta *data.Targets - // signers []keys.Signer -} - // AddTargetsWithExpiresToPreferredRole signs the staged targets at `paths`. // // If preferredRole is not the empty string, the target is added to the given From 414a9c36a114f69c4f630280ab9d56934f206b5a Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 19:58:47 -0400 Subject: [PATCH 28/48] Fix comment --- repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.go b/repo.go index d0681f33..1bf4c976 100644 --- a/repo.go +++ b/repo.go @@ -1059,7 +1059,7 @@ func (r *Repo) AddTargetsWithExpiresToPreferredRole(paths []string, custom json. normalizedPaths[i] = util.NormalizeTarget(path) } - // As we through staged targets files, we accumulate changes to their + // As we iterate through staged targets files, we accumulate changes to their // corresponding targets metadata. updatedTargetsMeta := map[string]*data.Targets{} From 2c35b27e70a4852c5e901cd8a817a8d83af47914 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 22:51:06 -0400 Subject: [PATCH 29/48] Turn FIXME into an issue --- repo.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/repo.go b/repo.go index 1bf4c976..7001e698 100644 --- a/repo.go +++ b/repo.go @@ -1354,9 +1354,8 @@ func (r *Repo) fileHashes() (map[string]data.Hashes, error) { hashes[fileName] = m.Hashes } - // FIXME: Loading all targets into memory is not scalable if - // there are many targets. This is used to Commit, so we should - // only need new targets here. + // Scalability issue: Commit/fileHashes loads all targets metadata into memory + // https://github.com/theupdateframework/go-tuf/issues/245 if roleName != "root" { t, err := r.targets(roleName) if err != nil { From 1b228effdb96e65e2145ac19e425ed357729db5d Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 22:51:44 -0400 Subject: [PATCH 30/48] Fix comment position --- repo.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.go b/repo.go index 7001e698..2db0e9df 100644 --- a/repo.go +++ b/repo.go @@ -1354,9 +1354,9 @@ func (r *Repo) fileHashes() (map[string]data.Hashes, error) { hashes[fileName] = m.Hashes } - // Scalability issue: Commit/fileHashes loads all targets metadata into memory - // https://github.com/theupdateframework/go-tuf/issues/245 if roleName != "root" { + // Scalability issue: Commit/fileHashes loads all targets metadata into memory + // https://github.com/theupdateframework/go-tuf/issues/245 t, err := r.targets(roleName) if err != nil { return nil, err From eebf2240c46d64454618ec65066f833b5ded7ab0 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 22:55:39 -0400 Subject: [PATCH 31/48] Remove out-of-place comment --- repo.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/repo.go b/repo.go index 2db0e9df..607ca55d 100644 --- a/repo.go +++ b/repo.go @@ -342,8 +342,6 @@ func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expi // are associated with a delegation (edge), not a role (node). if roles.IsDelegatedTargetsRole(keyRole) { - // The signature threshold for a delegated targets role - // depends on the incoming delegation edge. return ErrInvalidRole{keyRole, "only support adding keys for top-level roles"} } From c2f8cc60900608c3ccd96c0c51e63e5e82d0185a Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 22:56:19 -0400 Subject: [PATCH 32/48] Add missing hyphen --- repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo.go b/repo.go index 607ca55d..9cacab6b 100644 --- a/repo.go +++ b/repo.go @@ -376,7 +376,7 @@ func (r *Repo) AddVerificationKeyWithExpiration(keyRole string, pk *data.PublicK if roles.IsDelegatedTargetsRole(keyRole) { return ErrInvalidRole{ Role: keyRole, - Reason: "only top level targets roles are supported", + Reason: "only top-level targets roles are supported", } } From 21f9d6c6dea99c2c094870022cc8e4803bda26ca Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Mon, 28 Mar 2022 23:46:04 -0400 Subject: [PATCH 33/48] Test delegations construction more rigorously --- repo_test.go | 171 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 116 insertions(+), 55 deletions(-) diff --git a/repo_test.go b/repo_test.go index c953b82c..a5e138ba 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1896,82 +1896,143 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(r.Timestamp(), IsNil) c.Assert(r.Commit(), IsNil) - delegatedKey, err := keys.GenerateEd25519Key() - c.Assert(err, IsNil) + saveNewKey := func(role string) keys.Signer { + key, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) - err = local.SaveSigner("role1", delegatedKey) - c.Assert(err, IsNil) + err = local.SaveSigner(role, key) + c.Assert(err, IsNil) - paths := []string{} - paths = append(paths, "test/*") + return key + } - delegatee := data.DelegatedRole{ + // Delegate from targets -> role1 for A/*, B/* with one key, threshold 1. + role1ABKey := saveNewKey("role1") + role1AB := data.DelegatedRole{ Name: "role1", - KeyIDs: delegatedKey.PublicData().IDs(), - Paths: paths, + KeyIDs: role1ABKey.PublicData().IDs(), + Paths: []string{"A/*", "B/*"}, Threshold: 1, } - publicKeys := []*data.PublicKey{} - publicKeys = append(publicKeys, delegatedKey.PublicData()) - - err = r.AddTargetsDelegation("targets", delegatee, publicKeys) + err = r.AddTargetsDelegation("targets", role1AB, []*data.PublicKey{ + role1ABKey.PublicData(), + }) c.Assert(err, IsNil) - //test duplicate delegation - err = r.AddTargetsDelegation("targets", delegatee, publicKeys) + // Adding duplicate delegation should return an error. + err = r.AddTargetsDelegation("targets", role1AB, []*data.PublicKey{ + role1ABKey.PublicData(), + }) c.Assert(err, NotNil) - targets, err := r.topLevelTargets() + // Delegate from targets -> role2 for C/*, D/* with three key, threshold 2. + role2CDKey1 := saveNewKey("role2") + role2CDKey2 := saveNewKey("role2") + role2CDKey3 := saveNewKey("role2") + role2CD := data.DelegatedRole{ + Name: "role2", + KeyIDs: append( + append(role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs()...), + role2CDKey3.PublicData().IDs()...), + Paths: []string{"C/*", "D/*"}, + Threshold: 2, + } + err = r.AddTargetsDelegation("targets", role2CD, []*data.PublicKey{ + role2CDKey1.PublicData(), + role2CDKey2.PublicData(), + role2CDKey3.PublicData(), + }) c.Assert(err, IsNil) - c.Assert(targets.Delegations.Roles, HasLen, 1) - c.Assert(targets.Delegations.Keys, HasLen, 1) - c.Assert(r.Snapshot(), IsNil) - c.Assert(r.Timestamp(), IsNil) - c.Assert(r.Commit(), IsNil) - - snapshot, err := r.snapshot() + // Delegate from role1 -> role2 for D/disco.txt with one key, threshold 1. + role1To2Key := saveNewKey("role2") + role1To2 := data.DelegatedRole{ + Name: "role2", + KeyIDs: role1To2Key.PublicData().IDs(), + Paths: []string{"D/disco.txt"}, + Threshold: 1, + Terminating: true, + } + err = r.AddTargetsDelegation("role1", role1To2, []*data.PublicKey{ + role1To2Key.PublicData(), + }) c.Assert(err, IsNil) - // Snapshots has targets, role1 - c.Assert(snapshot.Meta, HasLen, 2) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) - //add target to delegations - tmp.writeStagedTarget("test/bar.txt", "bar") - c.Assert(r.AddTarget("test/bar.txt", nil), IsNil) - targets, err = r.targets("role1") - c.Assert(err, IsNil) - c.Assert(targets.Targets, HasLen, 1) - // 3 characters in the file - c.Assert(targets.Targets["test/bar.txt"].Length, Equals, int64(3)) + checkDelegations := func(delegator string, delegatedRoles ...data.DelegatedRole) { + t, err := r.targets(delegator) + c.Assert(err, IsNil) - // add target to the top-level targets - tmp.writeStagedTarget("baz.txt", "baz") - c.Assert(r.AddTarget("baz.txt", nil), IsNil) - targets, err = r.targets("targets") - c.Assert(err, IsNil) - c.Assert(targets.Targets, HasLen, 2) + // Check that delegated roles are copied verbatim. + c.Assert(t.Delegations.Roles, DeepEquals, delegatedRoles) - // test AddTargetToPreferredRole - err = r.AddTargetToPreferredRole("test/bar.txt", nil, "targets") - c.Assert(err, IsNil) - targets, err = r.targets("targets") - c.Assert(err, IsNil) - c.Assert(targets.Targets, HasLen, 3) + // Check that public keys match key IDs in roles. + expectedKeyIDs := []string{} + for _, dr := range delegatedRoles { + expectedKeyIDs = append(expectedKeyIDs, dr.KeyIDs...) + } + expectedKeyIDs = sets.DeduplicateStrings(expectedKeyIDs) + sort.Strings(expectedKeyIDs) + + gotKeyIDs := []string{} + for _, k := range t.Delegations.Keys { + gotKeyIDs = append(gotKeyIDs, k.IDs()...) + } + gotKeyIDs = sets.DeduplicateStrings(gotKeyIDs) + sort.Strings(gotKeyIDs) + + c.Assert(gotKeyIDs, DeepEquals, expectedKeyIDs) + } + + checkDelegations("targets", role1AB, role2CD) + checkDelegations("role1", role1To2) c.Assert(r.Snapshot(), IsNil) c.Assert(r.Timestamp(), IsNil) c.Assert(r.Commit(), IsNil) - metas, err := local.GetMeta() - c.Assert(err, IsNil) - - for k, meta := range metas { - s := &data.Signed{} - err = json.Unmarshal(meta, s) - c.Assert(err, IsNil, Commentf("meta: %v", k)) - c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) - } + //snapshot, err := r.snapshot() + //c.Assert(err, IsNil) + //// Snapshots has targets, role1 + //c.Assert(snapshot.Meta, HasLen, 2) + //c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + + ////add target to delegations + //tmp.writeStagedTarget("test/bar.txt", "bar") + //c.Assert(r.AddTarget("test/bar.txt", nil), IsNil) + //targets, err = r.targets("role1") + //c.Assert(err, IsNil) + //c.Assert(targets.Targets, HasLen, 1) + //// 3 characters in the file + //c.Assert(targets.Targets["test/bar.txt"].Length, Equals, int64(3)) + + //// add target to the top-level targets + //tmp.writeStagedTarget("baz.txt", "baz") + //c.Assert(r.AddTarget("baz.txt", nil), IsNil) + //targets, err = r.targets("targets") + //c.Assert(err, IsNil) + //c.Assert(targets.Targets, HasLen, 2) + + //// test AddTargetToPreferredRole + //err = r.AddTargetToPreferredRole("test/bar.txt", nil, "targets") + //c.Assert(err, IsNil) + //targets, err = r.targets("targets") + //c.Assert(err, IsNil) + //c.Assert(targets.Targets, HasLen, 3) + + //c.Assert(r.Snapshot(), IsNil) + //c.Assert(r.Timestamp(), IsNil) + //c.Assert(r.Commit(), IsNil) + + //metas, err := local.GetMeta() + //c.Assert(err, IsNil) + + //for k, meta := range metas { + // s := &data.Signed{} + // err = json.Unmarshal(meta, s) + // c.Assert(err, IsNil, Commentf("meta: %v", k)) + // c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) + //} } func (rs *RepoSuite) TestHashedBinDelegations(c *C) { From 54b422499282c01539e32786ed20a2281151610d Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 29 Mar 2022 18:35:53 -0400 Subject: [PATCH 34/48] Support removing delegated targets files --- repo.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/repo.go b/repo.go index 9cacab6b..fdd728ef 100644 --- a/repo.go +++ b/repo.go @@ -1143,11 +1143,27 @@ func (r *Repo) RemoveTargetWithExpires(path string, expires time.Time) error { // If paths is empty, all targets will be removed. func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error { + for metaName := range r.meta { + if metaName != "targets.json" && !roles.IsDelegatedTargetsManifest(metaName) { + continue + } + + err := r.removeTargetsWithExpiresFromMeta(metaName, paths, expires) + if err != nil { + return fmt.Errorf("could not remove %v from %v: %w", paths, metaName, err) + } + } + + return nil +} + +func (r *Repo) removeTargetsWithExpiresFromMeta(metaName string, paths []string, expires time.Time) error { if !validExpires(expires) { return ErrInvalidExpires{expires} } - t, err := r.topLevelTargets() + roleName := strings.TrimSuffix(metaName, ".json") + t, err := r.targets(roleName) if err != nil { return err } @@ -1162,7 +1178,7 @@ func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error for _, path := range paths { path = util.NormalizeTarget(path) if _, ok := t.Targets[path]; !ok { - fmt.Println("The following target is not present:", path) + fmt.Printf("[%v] The following target is not present: %v\n", metaName, path) continue } removed = true @@ -1176,23 +1192,23 @@ func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error } } t.Expires = expires.Round(time.Second) - if !r.local.FileIsStaged("targets.json") { + if !r.local.FileIsStaged(metaName) { t.Version++ } - err = r.setMeta("targets.json", t) + err = r.setMeta(metaName, t) if err == nil { - fmt.Println("Removed targets:") + fmt.Printf("[%v] Removed targets:\n", metaName) for _, v := range removed_targets { fmt.Println("*", v) } if len(t.Targets) != 0 { - fmt.Println("Added/staged targets:") + fmt.Printf("[%v] Added/staged targets:\n", metaName) for k := range t.Targets { fmt.Println("*", k) } } else { - fmt.Println("There are no added/staged targets") + fmt.Printf("[%v] There are no added/staged targets\n", metaName) } } return err From b28aafe71755a8ca0d221dd4114a05bb9861b582 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 29 Mar 2022 19:23:38 -0400 Subject: [PATCH 35/48] Fix tests --- repo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/repo.go b/repo.go index fdd728ef..3212da74 100644 --- a/repo.go +++ b/repo.go @@ -1143,6 +1143,10 @@ func (r *Repo) RemoveTargetWithExpires(path string, expires time.Time) error { // If paths is empty, all targets will be removed. func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error { + if !validExpires(expires) { + return ErrInvalidExpires{expires} + } + for metaName := range r.meta { if metaName != "targets.json" && !roles.IsDelegatedTargetsManifest(metaName) { continue @@ -1158,10 +1162,6 @@ func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error } func (r *Repo) removeTargetsWithExpiresFromMeta(metaName string, paths []string, expires time.Time) error { - if !validExpires(expires) { - return ErrInvalidExpires{expires} - } - roleName := strings.TrimSuffix(metaName, ".json") t, err := r.targets(roleName) if err != nil { From b512eec203e990a58f830dca2598ccab70c2f9dd Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 29 Mar 2022 19:26:06 -0400 Subject: [PATCH 36/48] Add more tests for delegation --- repo_test.go | 348 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 279 insertions(+), 69 deletions(-) diff --git a/repo_test.go b/repo_test.go index a5e138ba..52ba8c58 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1877,16 +1877,45 @@ func (rs *RepoSuite) TestSignDigest(c *C) { c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Hashes["sha256"], DeepEquals, hex_digest_bytes) } +func concat(ss ...[]string) []string { + ret := []string{} + for _, s := range ss { + ret = append(ret, s...) + } + return ret +} + +func checkSigKeyIDs(c *C, local LocalStore, fileToKeyIDs map[string][]string) { + metas, err := local.GetMeta() + c.Assert(err, IsNil) + + for f, keyIDs := range fileToKeyIDs { + meta := metas[f] + + s := &data.Signed{} + err = json.Unmarshal(meta, s) + + gotKeyIDs := []string{} + for _, sig := range s.Signatures { + gotKeyIDs = append(gotKeyIDs, sig.KeyID) + } + gotKeyIDs = sets.DeduplicateStrings(gotKeyIDs) + sort.Strings(gotKeyIDs) + + sort.Strings(keyIDs) + c.Assert(gotKeyIDs, DeepEquals, keyIDs) + } +} + func (rs *RepoSuite) TestDelegations(c *C) { tmp := newTmpDir(c) - tmp.writeStagedTarget("foo.txt", "foo") local := FileSystemStore(tmp.path, nil) r, err := NewRepo(local) c.Assert(err, IsNil) // Add one key to each role genKey(c, r, "root") - genKey(c, r, "targets") + targetsKeyIDs := genKey(c, r, "targets") genKey(c, r, "snapshot") genKey(c, r, "timestamp") @@ -1896,6 +1925,15 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(r.Timestamp(), IsNil) c.Assert(r.Commit(), IsNil) + snapshot, err := r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) + + checkSigKeyIDs(c, local, map[string][]string{ + "1.targets.json": targetsKeyIDs, + }) + saveNewKey := func(role string) keys.Signer { key, err := keys.GenerateEd25519Key() c.Assert(err, IsNil) @@ -1931,10 +1969,11 @@ func (rs *RepoSuite) TestDelegations(c *C) { role2CDKey3 := saveNewKey("role2") role2CD := data.DelegatedRole{ Name: "role2", - KeyIDs: append( - append(role2CDKey1.PublicData().IDs(), - role2CDKey2.PublicData().IDs()...), - role2CDKey3.PublicData().IDs()...), + KeyIDs: concat( + role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs(), + role2CDKey3.PublicData().IDs(), + ), Paths: []string{"C/*", "D/*"}, Threshold: 2, } @@ -1945,12 +1984,12 @@ func (rs *RepoSuite) TestDelegations(c *C) { }) c.Assert(err, IsNil) - // Delegate from role1 -> role2 for D/disco.txt with one key, threshold 1. + // Delegate from role1 -> role2 for A/allium.txt with one key, threshold 1. role1To2Key := saveNewKey("role2") role1To2 := data.DelegatedRole{ Name: "role2", KeyIDs: role1To2Key.PublicData().IDs(), - Paths: []string{"D/disco.txt"}, + Paths: []string{"A/allium.txt"}, Threshold: 1, Terminating: true, } @@ -1991,51 +2030,198 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(r.Timestamp(), IsNil) c.Assert(r.Commit(), IsNil) - //snapshot, err := r.snapshot() - //c.Assert(err, IsNil) - //// Snapshots has targets, role1 - //c.Assert(snapshot.Meta, HasLen, 2) - //c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) - - ////add target to delegations - //tmp.writeStagedTarget("test/bar.txt", "bar") - //c.Assert(r.AddTarget("test/bar.txt", nil), IsNil) - //targets, err = r.targets("role1") - //c.Assert(err, IsNil) - //c.Assert(targets.Targets, HasLen, 1) - //// 3 characters in the file - //c.Assert(targets.Targets["test/bar.txt"].Length, Equals, int64(3)) - - //// add target to the top-level targets - //tmp.writeStagedTarget("baz.txt", "baz") - //c.Assert(r.AddTarget("baz.txt", nil), IsNil) - //targets, err = r.targets("targets") - //c.Assert(err, IsNil) - //c.Assert(targets.Targets, HasLen, 2) - - //// test AddTargetToPreferredRole - //err = r.AddTargetToPreferredRole("test/bar.txt", nil, "targets") - //c.Assert(err, IsNil) - //targets, err = r.targets("targets") - //c.Assert(err, IsNil) - //c.Assert(targets.Targets, HasLen, 3) - - //c.Assert(r.Snapshot(), IsNil) - //c.Assert(r.Timestamp(), IsNil) - //c.Assert(r.Commit(), IsNil) - - //metas, err := local.GetMeta() - //c.Assert(err, IsNil) - - //for k, meta := range metas { - // s := &data.Signed{} - // err = json.Unmarshal(meta, s) - // c.Assert(err, IsNil, Commentf("meta: %v", k)) - // c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) - //} + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 3) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 2) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, 1) + + checkSigKeyIDs(c, local, map[string][]string{ + "2.targets.json": targetsKeyIDs, + "1.role1.json": role1ABKey.PublicData().IDs(), + "1.role2.json": concat( + role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs(), + role2CDKey3.PublicData().IDs(), + role1To2Key.PublicData().IDs(), + ), + }) + + // Add a variety of targets. + files := map[string]string{ + // targets.json + "potato.txt": "potatoes can be starchy or waxy", + // role1.json + "A/apple.txt": "apples are sometimes red", + "B/banana.txt": "bananas are yellow and sometimes brown", + // role2.json + "C/clementine.txt": "clementines are a citrus fruit", + "D/durian.txt": "durians are spiky", + "A/allium.txt": "alliums include garlic and leeks", + } + for name, content := range files { + tmp.writeStagedTarget(name, content) + c.Assert(r.AddTarget(name, nil), IsNil) + } + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 3) + // All roles should have new targets. + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 2) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, 2) + + checkSigKeyIDs(c, local, map[string][]string{ + "3.targets.json": targetsKeyIDs, + "2.role1.json": role1ABKey.PublicData().IDs(), + "2.role2.json": concat( + role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs(), + role2CDKey3.PublicData().IDs(), + role1To2Key.PublicData().IDs(), + ), + }) + + // Check that the given targets role has signed for the given filenames, with + // the correct file metadata. + checkTargets := func(role string, filenames ...string) { + t, err := r.targets(role) + c.Assert(err, IsNil) + c.Assert(t.Targets, HasLen, len(filenames)) + + for _, fn := range filenames { + content := files[fn] + + fm, err := util.GenerateTargetFileMeta(strings.NewReader(content)) + c.Assert(err, IsNil) + + c.Assert(util.TargetFileMetaEqual(t.Targets[fn], fm), IsNil) + } + } + + checkTargets("targets", "potato.txt") + checkTargets("role1", "A/apple.txt", "B/banana.txt") + checkTargets("role2", "C/clementine.txt", "D/durian.txt", "A/allium.txt") + + // Test AddTargetToPreferredRole. + // role2 is the default signer for A/allium.txt, but role1 is also eligible + // for A/*.txt according to the delegation from the top-level targets role. + c.Assert(r.RemoveTarget("A/allium.txt"), IsNil) + tmp.writeStagedTarget("A/allium.txt", files["A/allium.txt"]) + c.Assert(r.AddTargetToPreferredRole("A/allium.txt", nil, "role1"), IsNil) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 3) + // Only role1 and role2 should have bumped versions. + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, 3) + + checkSigKeyIDs(c, local, map[string][]string{ + "3.targets.json": targetsKeyIDs, + "3.role1.json": role1ABKey.PublicData().IDs(), + "3.role2.json": concat( + role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs(), + role2CDKey3.PublicData().IDs(), + role1To2Key.PublicData().IDs(), + ), + }) + + // role1 now signs A/allium.txt. + checkTargets("targets", "potato.txt") + checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") + checkTargets("role2", "C/clementine.txt", "D/durian.txt") + + // Remove all delegation from role1 to role2. + c.Assert(r.ResetTargetsDelegations("role1"), IsNil) + checkDelegations("targets", role1AB, role2CD) + checkDelegations("role1") + + // Try to sign A/allium.txt with role2. + // It should fail since we removed the role1 -> role2 delegation. + c.Assert(r.RemoveTarget("A/allium.txt"), IsNil) + tmp.writeStagedTarget("A/allium.txt", files["A/allium.txt"]) + c.Assert(r.AddTargetToPreferredRole("A/allium.txt", nil, "role2"), Equals, ErrNoDelegatedTarget{Path: "A/allium.txt"}) + + // Try to sign A/allium.txt with the default role (role1). + c.Assert(r.AddTarget("A/allium.txt", nil), IsNil) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 3) + // Only role1 should have a bumped version. + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 4) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, 3) + + checkSigKeyIDs(c, local, map[string][]string{ + "3.targets.json": targetsKeyIDs, + "4.role1.json": role1ABKey.PublicData().IDs(), + "3.role2.json": concat( + // Metadata (and therefore signers) for role2.json shouldn't have + // changed, even though we revoked role1To2Key. Clients verify the + // signature using keys specified by 4.role1.json, so role1To2Key + // shouldn't contribute to the threshold. + role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs(), + role2CDKey3.PublicData().IDs(), + role1To2Key.PublicData().IDs(), + ), + }) + + // Re-sign target signed by role2 to test that role1To2Key is not used going + // forward. + c.Assert(r.RemoveTarget("C/clementine.txt"), IsNil) + tmp.writeStagedTarget("C/clementine.txt", files["C/clementine.txt"]) + c.Assert(r.AddTarget("C/clementine.txt", nil), IsNil) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 3) + // Only role2 should have a bumped version. + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 4) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, 4) + + checkSigKeyIDs(c, local, map[string][]string{ + "3.targets.json": targetsKeyIDs, + "4.role1.json": role1ABKey.PublicData().IDs(), + "4.role2.json": concat( + role2CDKey1.PublicData().IDs(), + role2CDKey2.PublicData().IDs(), + role2CDKey3.PublicData().IDs(), + // Note that role1To2Key no longer signs since the role1 -> role2 + // delegation was removed. + ), + }) + + // Targets should still be signed by the same roles. + checkTargets("targets", "potato.txt") + checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") + checkTargets("role2", "C/clementine.txt", "D/durian.txt") } -func (rs *RepoSuite) TestHashedBinDelegations(c *C) { +func (rs *RepoSuite) TestHashBinDelegations(c *C) { tmp := newTmpDir(c) local := FileSystemStore(tmp.path, nil) r, err := NewRepo(local) @@ -2043,24 +2229,24 @@ func (rs *RepoSuite) TestHashedBinDelegations(c *C) { // Add one key to each role genKey(c, r, "root") - genKey(c, r, "targets") + targetsKeyIDs := genKey(c, r, "targets") genKey(c, r, "snapshot") genKey(c, r, "timestamp") - // keys for the hashed bins + hb, err := targets.NewHashBins("bins_", 3) + if err != nil { + c.Assert(err, IsNil) + } + + // Generate key for the intermediate bins role. binsKey, err := keys.GenerateEd25519Key() c.Assert(err, IsNil) err = local.SaveSigner("bins", binsKey) c.Assert(err, IsNil) + // Generate key for the leaf bins role. leafKey, err := keys.GenerateEd25519Key() c.Assert(err, IsNil) - - hb, err := targets.NewHashBins("bins_", 3) - if err != nil { - c.Assert(err, IsNil) - } - for i := uint64(0); i < hb.NumBins(); i++ { b := hb.GetBin(i) err = local.SaveSigner(b.RoleName(), leafKey) @@ -2085,24 +2271,48 @@ func (rs *RepoSuite) TestHashedBinDelegations(c *C) { c.Assert(err, IsNil) c.Assert(targets.Delegations.Roles, HasLen, 8) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + tmp.writeStagedTarget("foo.txt", "foo") err = r.AddTarget("foo.txt", nil) c.Assert(err, IsNil) - targets, err = r.targets("bins_c-d") - c.Assert(err, IsNil) - c.Assert(targets.Targets, HasLen, 1) c.Assert(r.Snapshot(), IsNil) c.Assert(r.Timestamp(), IsNil) c.Assert(r.Commit(), IsNil) - metas, err := local.GetMeta() + snapshot, err := r.snapshot() + fmt.Println(len(snapshot.Meta)) + // 1 targets.json, 1 bins.json, 8 bins_*.json. + c.Assert(snapshot.Meta, HasLen, 10) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_0-1.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_2-3.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_4-5.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_6-7.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_8-9.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_a-b.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["bins_c-d.json"].Version, Equals, 2) + c.Assert(snapshot.Meta["bins_e-f.json"].Version, Equals, 1) + + targets, err = r.targets("bins_c-d") c.Assert(err, IsNil) + c.Assert(targets.Targets, HasLen, 1) - for k, meta := range metas { - s := &data.Signed{} - err = json.Unmarshal(meta, s) - c.Assert(err, IsNil, Commentf("meta: %v", k)) - c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) - } + checkSigKeyIDs(c, local, map[string][]string{ + "targets.json": targetsKeyIDs, + "1.bins.json": binsKey.PublicData().IDs(), + "1.bins_0-1.json": leafKey.PublicData().IDs(), + "1.bins_2-3.json": leafKey.PublicData().IDs(), + "1.bins_4-5.json": leafKey.PublicData().IDs(), + "1.bins_6-7.json": leafKey.PublicData().IDs(), + "1.bins_8-9.json": leafKey.PublicData().IDs(), + "1.bins_a-b.json": leafKey.PublicData().IDs(), + "1.bins_c-d.json": leafKey.PublicData().IDs(), + "2.bins_c-d.json": leafKey.PublicData().IDs(), + "1.bins_e-f.json": leafKey.PublicData().IDs(), + }) } From bd0f4864ff2c198fcd28a4cdd1ddeb50389aa45b Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 29 Mar 2022 19:29:13 -0400 Subject: [PATCH 37/48] Fix unchecked error --- repo_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/repo_test.go b/repo_test.go index 52ba8c58..4a1034d4 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1894,6 +1894,7 @@ func checkSigKeyIDs(c *C, local LocalStore, fileToKeyIDs map[string][]string) { s := &data.Signed{} err = json.Unmarshal(meta, s) + c.Assert(err, IsNil) gotKeyIDs := []string{} for _, sig := range s.Signatures { @@ -2284,7 +2285,7 @@ func (rs *RepoSuite) TestHashBinDelegations(c *C) { c.Assert(r.Commit(), IsNil) snapshot, err := r.snapshot() - fmt.Println(len(snapshot.Meta)) + c.Assert(err, IsNil) // 1 targets.json, 1 bins.json, 8 bins_*.json. c.Assert(snapshot.Meta, HasLen, 10) c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) From 733c1426ddedde8c653531f85038f68910a7f2e7 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 29 Mar 2022 19:40:04 -0400 Subject: [PATCH 38/48] Clarify comment --- repo_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repo_test.go b/repo_test.go index 4a1034d4..aa355429 100644 --- a/repo_test.go +++ b/repo_test.go @@ -2145,7 +2145,7 @@ func (rs *RepoSuite) TestDelegations(c *C) { checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") checkTargets("role2", "C/clementine.txt", "D/durian.txt") - // Remove all delegation from role1 to role2. + // Remove the delegation from role1 to role2. c.Assert(r.ResetTargetsDelegations("role1"), IsNil) checkDelegations("targets", role1AB, role2CD) checkDelegations("role1") From 56093d4d9e31bbbf3a7be0a5cacc86455c992f45 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 29 Mar 2022 19:43:21 -0400 Subject: [PATCH 39/48] Better naming --- repo.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/repo.go b/repo.go index 3212da74..7c4194b8 100644 --- a/repo.go +++ b/repo.go @@ -532,15 +532,15 @@ func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error // AddTargetsDelegation is equivalent to AddTargetsDelegationWithExpires, but // with a default expiration time. -func (r *Repo) AddTargetsDelegation(delegator string, role data.DelegatedRole, keys []*data.PublicKey) error { - return r.AddTargetsDelegationWithExpires(delegator, role, keys, data.DefaultExpires("targets")) +func (r *Repo) AddTargetsDelegation(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey) error { + return r.AddTargetsDelegationWithExpires(delegator, delegatedRole, keys, data.DefaultExpires("targets")) } // AddTargetsDelegationWithExpires adds a delegation from the delegator to the // role specified in the role argument. Key IDs referenced in role.KeyIDs // should have corresponding Key entries in the keys argument. New metadata is // written with the given expiration time. -func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error { +func (r *Repo) AddTargetsDelegationWithExpires(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error { expires = expires.Round(time.Second) t, err := r.targets(delegator) @@ -553,7 +553,7 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg t.Delegations.Keys = make(map[string]*data.PublicKey) } - for _, keyID := range role.KeyIDs { + for _, keyID := range delegatedRole.KeyIDs { for _, key := range keys { if key.ContainsID(keyID) { t.Delegations.Keys[keyID] = key @@ -563,11 +563,11 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg } for _, r := range t.Delegations.Roles { - if r.Name == role.Name { - return fmt.Errorf("role: %s is already delegated to by %s", role.Name, r.Name) + if r.Name == delegatedRole.Name { + return fmt.Errorf("role: %s is already delegated to by %s", delegatedRole.Name, r.Name) } } - t.Delegations.Roles = append(t.Delegations.Roles, role) + t.Delegations.Roles = append(t.Delegations.Roles, delegatedRole) t.Expires = expires delegatorFile := delegator + ".json" @@ -580,7 +580,7 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, role data.Deleg return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err) } - delegatee := role.Name + delegatee := delegatedRole.Name dt, err := r.targets(delegatee) if err != nil { return fmt.Errorf("error getting delegatee (%q) metadata: %w", delegatee, err) From b63ab014ca79c67e4ea487685662f4a2258c9a4c Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 5 Apr 2022 17:31:02 -0400 Subject: [PATCH 40/48] Support delegated targets in Sign and AddOrUpdateSignature --- repo.go | 87 ++++++++++++++++++++++++++++++---------------------- repo_test.go | 12 +++++--- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/repo.go b/repo.go index 7c4194b8..f409a05d 100644 --- a/repo.go +++ b/repo.go @@ -674,35 +674,54 @@ func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) { return json.MarshalIndent(v, r.prefix, r.indent) } -func (r *Repo) setMeta(roleFilename string, meta interface{}) error { - role := strings.TrimSuffix(roleFilename, ".json") - +func (r *Repo) dbsForRole(role string) ([]*verify.DB, error) { dbs := []*verify.DB{} + if roles.IsTopLevelRole(role) { db, err := r.topLevelKeysDB() if err != nil { - return err + return nil, err } dbs = append(dbs, db) } else { ddbs, err := r.delegatorDBs(role) if err != nil { - return err + return nil, err } dbs = append(dbs, ddbs...) } + return dbs, nil +} + +func (r *Repo) signersForRole(role string) ([]keys.Signer, error) { + dbs, err := r.dbsForRole(role) + if err != nil { + return nil, err + } + signers := []keys.Signer{} for _, db := range dbs { ss, err := r.getSignersInDB(role, db) if err != nil { - return err + return nil, err } signers = append(signers, ss...) } + return signers, nil +} + +func (r *Repo) setMeta(roleFilename string, meta interface{}) error { + role := strings.TrimSuffix(roleFilename, ".json") + + signers, err := r.signersForRole(role) + if err != nil { + return err + } + s, err := sign.Marshal(meta, signers...) if err != nil { return err @@ -717,20 +736,13 @@ func (r *Repo) setMeta(roleFilename string, meta interface{}) error { func (r *Repo) Sign(roleFilename string) error { role := strings.TrimSuffix(roleFilename, ".json") - if !roles.IsTopLevelRole(role) { - return ErrInvalidRole{role, "only signing top-level metadata supported"} - } s, err := r.SignedMeta(roleFilename) if err != nil { return err } - db, err := r.topLevelKeysDB() - if err != nil { - return err - } - keys, err := r.getSignersInDB(role, db) + keys, err := r.signersForRole(role) if err != nil { return err } @@ -757,20 +769,28 @@ func (r *Repo) Sign(roleFilename string) error { // The name must be a valid metadata file name, like root.json. func (r *Repo) AddOrUpdateSignature(roleFilename string, signature data.Signature) error { role := strings.TrimSuffix(roleFilename, ".json") - if !roles.IsTopLevelRole(role) { - return ErrInvalidRole{role, "only signing top-level metadata supported"} - } // Check key ID is in valid for the role. - db, err := r.topLevelKeysDB() + dbs, err := r.dbsForRole(role) if err != nil { return err } - roleData := db.GetRole(role) - if roleData == nil { - return ErrInvalidRole{role, "role missing from top-level keys"} + + if len(dbs) == 0 { + return ErrInvalidRole{role, "no trusted keys for role"} } - if !roleData.ValidKey(signature.KeyID) { + + keyInDB := false + for _, db := range dbs { + roleData := db.GetRole(role) + if roleData == nil { + return ErrInvalidRole{role, "role is not in verifier DB"} + } + if roleData.ValidKey(signature.KeyID) { + keyInDB = true + } + } + if !keyInDB { return verify.ErrInvalidKey } @@ -791,9 +811,11 @@ func (r *Repo) AddOrUpdateSignature(roleFilename string, signature data.Signatur // Check signature on signed meta. Ignore threshold errors as this may not be fully // signed. - if err := db.VerifySignatures(s, role); err != nil { - if _, ok := err.(verify.ErrRoleThreshold); !ok { - return err + for _, db := range dbs { + if err := db.VerifySignatures(s, role); err != nil { + if _, ok := err.(verify.ErrRoleThreshold); !ok { + return err + } } } @@ -1476,18 +1498,9 @@ func (r *Repo) verifySignatures(metaFilename string) error { role := strings.TrimSuffix(metaFilename, ".json") - dbs := []*verify.DB{} - if roles.IsTopLevelRole(role) { - db, err := r.topLevelKeysDB() - if err != nil { - return err - } - dbs = append(dbs, db) - } else { - dbs, err = r.delegatorDBs(role) - if err != nil { - return err - } + dbs, err := r.dbsForRole(role) + if err != nil { + return err } for _, db := range dbs { diff --git a/repo_test.go b/repo_test.go index aa355429..a41a8334 100644 --- a/repo_test.go +++ b/repo_test.go @@ -636,7 +636,7 @@ func (rs *RepoSuite) TestSign(c *C) { r, err := NewRepo(local) c.Assert(err, IsNil) - c.Assert(r.Sign("foo.json"), Equals, ErrInvalidRole{"foo", "only signing top-level metadata supported"}) + c.Assert(r.Sign("foo.json"), Equals, ErrMissingMetadata{"foo.json"}) // signing with no keys returns ErrInsufficientKeys c.Assert(r.Sign("root.json"), Equals, ErrInsufficientKeys{"root.json"}) @@ -1768,7 +1768,7 @@ func (rs *RepoSuite) TestBadAddOrUpdateSignatures(c *C) { c.Assert(r.AddOrUpdateSignature("targets.json", data.Signature{ KeyID: "foo", - Signature: nil}), Equals, ErrInvalidRole{"targets", "role missing from top-level keys"}) + Signature: nil}), Equals, ErrInvalidRole{"targets", "role is not in verifier DB"}) // generate root key offline and add as a verification key rootKey, err := keys.GenerateEd25519Key() @@ -1794,7 +1794,7 @@ func (rs *RepoSuite) TestBadAddOrUpdateSignatures(c *C) { for _, id := range rootKey.PublicData().IDs() { c.Assert(r.AddOrUpdateSignature("invalid_root.json", data.Signature{ KeyID: id, - Signature: rootSig}), Equals, ErrInvalidRole{"invalid_root", "only signing top-level metadata supported"}) + Signature: rootSig}), Equals, ErrInvalidRole{"invalid_root", "no trusted keys for role"}) } // add a root signature with an key ID that is for the targets role @@ -1890,7 +1890,8 @@ func checkSigKeyIDs(c *C, local LocalStore, fileToKeyIDs map[string][]string) { c.Assert(err, IsNil) for f, keyIDs := range fileToKeyIDs { - meta := metas[f] + meta, ok := metas[f] + c.Assert(ok, Equals, true, Commentf("meta file: %v", f)) s := &data.Signed{} err = json.Unmarshal(meta, s) @@ -2220,6 +2221,9 @@ func (rs *RepoSuite) TestDelegations(c *C) { checkTargets("targets", "potato.txt") checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") checkTargets("role2", "C/clementine.txt", "D/durian.txt") + + // Check compatibility with Sign and j + // r.Sign("4.) } func (rs *RepoSuite) TestHashBinDelegations(c *C) { From e14e3e781d9e6ac194f2a151bdb0325f67086f4b Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Tue, 5 Apr 2022 17:36:43 -0400 Subject: [PATCH 41/48] Add test for ResetTargetsDelegationsWithExpires --- repo_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/repo_test.go b/repo_test.go index a41a8334..5d84a563 100644 --- a/repo_test.go +++ b/repo_test.go @@ -2321,3 +2321,84 @@ func (rs *RepoSuite) TestHashBinDelegations(c *C) { "1.bins_e-f.json": leafKey.PublicData().IDs(), }) } + +func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) { + tmp := newTmpDir(c) + local := FileSystemStore(tmp.path, nil) + r, err := NewRepo(local) + c.Assert(err, IsNil) + + // Add one key to each role + genKey(c, r, "root") + targetsKeyIDs := genKey(c, r, "targets") + genKey(c, r, "snapshot") + genKey(c, r, "timestamp") + + // commit the metadata to the store. + c.Assert(r.AddTargets([]string{}, nil), IsNil) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err := r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) + + checkSigKeyIDs(c, local, map[string][]string{ + "1.targets.json": targetsKeyIDs, + }) + + role1Key, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + + err = local.SaveSigner("role1", role1Key) + c.Assert(err, IsNil) + + // Delegate from targets -> role1 for A/*, B/* with one key, threshold 1. + role1 := data.DelegatedRole{ + Name: "role1", + KeyIDs: role1Key.PublicData().IDs(), + Paths: []string{"A/*", "B/*"}, + Threshold: 1, + } + err = r.AddTargetsDelegation("targets", role1, []*data.PublicKey{ + role1Key.PublicData(), + }) + c.Assert(err, IsNil) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 2) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 2) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + + checkSigKeyIDs(c, local, map[string][]string{ + "1.targets.json": targetsKeyIDs, + "targets.json": targetsKeyIDs, + "1.role1.json": role1Key.PublicData().IDs(), + "role1.json": role1Key.PublicData().IDs(), + }) + + c.Assert(r.ResetTargetsDelegations("targets"), IsNil) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 2) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + + checkSigKeyIDs(c, local, map[string][]string{ + "2.targets.json": targetsKeyIDs, + "targets.json": targetsKeyIDs, + "1.role1.json": role1Key.PublicData().IDs(), + "role1.json": role1Key.PublicData().IDs(), + }) +} From ecd63d1bdba72b98849646fe9e251e705c62bb6f Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Fri, 8 Apr 2022 17:58:14 -0400 Subject: [PATCH 42/48] Improve naming of delegation methods --- repo.go | 24 ++++++++++++------------ repo_test.go | 14 +++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/repo.go b/repo.go index f409a05d..0553fd61 100644 --- a/repo.go +++ b/repo.go @@ -530,17 +530,17 @@ func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error return err } -// AddTargetsDelegation is equivalent to AddTargetsDelegationWithExpires, but +// AddDelegatedRole is equivalent to AddDelegatedRoleWithExpires, but // with a default expiration time. -func (r *Repo) AddTargetsDelegation(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey) error { - return r.AddTargetsDelegationWithExpires(delegator, delegatedRole, keys, data.DefaultExpires("targets")) +func (r *Repo) AddDelegatedRole(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey) error { + return r.AddDelegatedRoleWithExpires(delegator, delegatedRole, keys, data.DefaultExpires("targets")) } -// AddTargetsDelegationWithExpires adds a delegation from the delegator to the +// AddDelegatedRoleWithExpires adds a delegation from the delegator to the // role specified in the role argument. Key IDs referenced in role.KeyIDs // should have corresponding Key entries in the keys argument. New metadata is // written with the given expiration time. -func (r *Repo) AddTargetsDelegationWithExpires(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error { +func (r *Repo) AddDelegatedRoleWithExpires(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey, expires time.Time) error { expires = expires.Round(time.Second) t, err := r.targets(delegator) @@ -600,17 +600,17 @@ func (r *Repo) AddTargetsDelegationWithExpires(delegator string, delegatedRole d return nil } -// AddTargetsDelegationsForPathHashBins is equivalent to -// AddTargetsDelegationsForPathHashBinsWithExpires, but with a default +// AddDelegatedRolesForPathHashBins is equivalent to +// AddDelegatedRolesForPathHashBinsWithExpires, but with a default // expiration time. -func (r *Repo) AddTargetsDelegationsForPathHashBins(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int) error { - return r.AddTargetsDelegationsForPathHashBinsWithExpires(delegator, bins, keys, threshold, data.DefaultExpires("targets")) +func (r *Repo) AddDelegatedRolesForPathHashBins(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int) error { + return r.AddDelegatedRolesForPathHashBinsWithExpires(delegator, bins, keys, threshold, data.DefaultExpires("targets")) } -// AddTargetsDelegationsForPathHashBinsWithExpires adds delegations to the +// AddDelegatedRolesForPathHashBinsWithExpires adds delegations to the // delegator role for the given hash bins configuration. New metadata is // written with the given expiration time. -func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int, expires time.Time) error { +func (r *Repo) AddDelegatedRolesForPathHashBinsWithExpires(delegator string, bins *targets.HashBins, keys []*data.PublicKey, threshold int, expires time.Time) error { keyIDs := []string{} for _, key := range keys { keyIDs = append(keyIDs, key.IDs()...) @@ -620,7 +620,7 @@ func (r *Repo) AddTargetsDelegationsForPathHashBinsWithExpires(delegator string, for i := uint64(0); i < n; i += 1 { bin := bins.GetBin(i) name := bin.RoleName() - err := r.AddTargetsDelegationWithExpires(delegator, data.DelegatedRole{ + err := r.AddDelegatedRoleWithExpires(delegator, data.DelegatedRole{ Name: name, KeyIDs: sets.DeduplicateStrings(keyIDs), PathHashPrefixes: bin.HashPrefixes(), diff --git a/repo_test.go b/repo_test.go index 5d84a563..e3f10b56 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1954,13 +1954,13 @@ func (rs *RepoSuite) TestDelegations(c *C) { Paths: []string{"A/*", "B/*"}, Threshold: 1, } - err = r.AddTargetsDelegation("targets", role1AB, []*data.PublicKey{ + err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{ role1ABKey.PublicData(), }) c.Assert(err, IsNil) // Adding duplicate delegation should return an error. - err = r.AddTargetsDelegation("targets", role1AB, []*data.PublicKey{ + err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{ role1ABKey.PublicData(), }) c.Assert(err, NotNil) @@ -1979,7 +1979,7 @@ func (rs *RepoSuite) TestDelegations(c *C) { Paths: []string{"C/*", "D/*"}, Threshold: 2, } - err = r.AddTargetsDelegation("targets", role2CD, []*data.PublicKey{ + err = r.AddDelegatedRole("targets", role2CD, []*data.PublicKey{ role2CDKey1.PublicData(), role2CDKey2.PublicData(), role2CDKey3.PublicData(), @@ -1995,7 +1995,7 @@ func (rs *RepoSuite) TestDelegations(c *C) { Threshold: 1, Terminating: true, } - err = r.AddTargetsDelegation("role1", role1To2, []*data.PublicKey{ + err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{ role1To2Key.PublicData(), }) c.Assert(err, IsNil) @@ -2260,7 +2260,7 @@ func (rs *RepoSuite) TestHashBinDelegations(c *C) { } } - err = r.AddTargetsDelegation("targets", data.DelegatedRole{ + err = r.AddDelegatedRole("targets", data.DelegatedRole{ Name: "bins", KeyIDs: binsKey.PublicData().IDs(), Paths: []string{"*.txt"}, @@ -2270,7 +2270,7 @@ func (rs *RepoSuite) TestHashBinDelegations(c *C) { }) c.Assert(err, IsNil) - err = r.AddTargetsDelegationsForPathHashBins("bins", hb, []*data.PublicKey{leafKey.PublicData()}, 1) + err = r.AddDelegatedRolesForPathHashBins("bins", hb, []*data.PublicKey{leafKey.PublicData()}, 1) c.Assert(err, IsNil) targets, err := r.targets("bins") c.Assert(err, IsNil) @@ -2362,7 +2362,7 @@ func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) { Paths: []string{"A/*", "B/*"}, Threshold: 1, } - err = r.AddTargetsDelegation("targets", role1, []*data.PublicKey{ + err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{ role1Key.PublicData(), }) c.Assert(err, IsNil) From 17c73183f36e19b3fdd7aff415ba532709db7398 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Wed, 13 Apr 2022 15:59:36 -0400 Subject: [PATCH 43/48] Update tests for int64 version numbers --- repo_test.go | 62 ++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/repo_test.go b/repo_test.go index e3f10b56..0fd43763 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1930,7 +1930,7 @@ func (rs *RepoSuite) TestDelegations(c *C) { snapshot, err := r.snapshot() c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 1) - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1)) checkSigKeyIDs(c, local, map[string][]string{ "1.targets.json": targetsKeyIDs, @@ -2035,9 +2035,9 @@ func (rs *RepoSuite) TestDelegations(c *C) { snapshot, err = r.snapshot() c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 3) - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 2) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["role2.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(2)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(1)) checkSigKeyIDs(c, local, map[string][]string{ "2.targets.json": targetsKeyIDs, @@ -2075,9 +2075,9 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 3) // All roles should have new targets. - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 2) - c.Assert(snapshot.Meta["role2.json"].Version, Equals, 2) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(2)) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(2)) checkSigKeyIDs(c, local, map[string][]string{ "3.targets.json": targetsKeyIDs, @@ -2126,9 +2126,9 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 3) // Only role1 and role2 should have bumped versions. - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 3) - c.Assert(snapshot.Meta["role2.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(3)) checkSigKeyIDs(c, local, map[string][]string{ "3.targets.json": targetsKeyIDs, @@ -2168,9 +2168,9 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 3) // Only role1 should have a bumped version. - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 4) - c.Assert(snapshot.Meta["role2.json"].Version, Equals, 3) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(4)) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(3)) checkSigKeyIDs(c, local, map[string][]string{ "3.targets.json": targetsKeyIDs, @@ -2201,9 +2201,9 @@ func (rs *RepoSuite) TestDelegations(c *C) { c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 3) // Only role2 should have a bumped version. - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 4) - c.Assert(snapshot.Meta["role2.json"].Version, Equals, 4) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(4)) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(4)) checkSigKeyIDs(c, local, map[string][]string{ "3.targets.json": targetsKeyIDs, @@ -2292,16 +2292,16 @@ func (rs *RepoSuite) TestHashBinDelegations(c *C) { c.Assert(err, IsNil) // 1 targets.json, 1 bins.json, 8 bins_*.json. c.Assert(snapshot.Meta, HasLen, 10) - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_0-1.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_2-3.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_4-5.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_6-7.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_8-9.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_a-b.json"].Version, Equals, 1) - c.Assert(snapshot.Meta["bins_c-d.json"].Version, Equals, 2) - c.Assert(snapshot.Meta["bins_e-f.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_0-1.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_2-3.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_4-5.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_6-7.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_8-9.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_a-b.json"].Version, Equals, int64(1)) + c.Assert(snapshot.Meta["bins_c-d.json"].Version, Equals, int64(2)) + c.Assert(snapshot.Meta["bins_e-f.json"].Version, Equals, int64(1)) targets, err = r.targets("bins_c-d") c.Assert(err, IsNil) @@ -2343,7 +2343,7 @@ func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) { snapshot, err := r.snapshot() c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 1) - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1)) checkSigKeyIDs(c, local, map[string][]string{ "1.targets.json": targetsKeyIDs, @@ -2374,8 +2374,8 @@ func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) { snapshot, err = r.snapshot() c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 2) - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 2) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(2)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1)) checkSigKeyIDs(c, local, map[string][]string{ "1.targets.json": targetsKeyIDs, @@ -2392,8 +2392,8 @@ func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) { snapshot, err = r.snapshot() c.Assert(err, IsNil) c.Assert(snapshot.Meta, HasLen, 2) - c.Assert(snapshot.Meta["targets.json"].Version, Equals, 3) - c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1)) checkSigKeyIDs(c, local, map[string][]string{ "2.targets.json": targetsKeyIDs, From 501b1f5005bdae5553c9e1187df29807f934b1ba Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Wed, 13 Apr 2022 16:52:40 -0400 Subject: [PATCH 44/48] Add tests for Sign and AddOrUpdateSignature with delegations --- repo_test.go | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/repo_test.go b/repo_test.go index 0fd43763..58aa273a 100644 --- a/repo_test.go +++ b/repo_test.go @@ -2402,3 +2402,122 @@ func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) { "role1.json": role1Key.PublicData().IDs(), }) } + +func (rs *RepoSuite) TestSignWithDelegations(c *C) { + tmp := newTmpDir(c) + local := FileSystemStore(tmp.path, nil) + r, err := NewRepo(local) + c.Assert(err, IsNil) + + // Add one key to each role + genKey(c, r, "root") + genKey(c, r, "targets") + genKey(c, r, "snapshot") + genKey(c, r, "timestamp") + + role1Key, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + + role1 := data.DelegatedRole{ + Name: "role1", + KeyIDs: role1Key.PublicData().IDs(), + Paths: []string{"A/*", "B/*"}, + Threshold: 1, + } + err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{ + role1Key.PublicData(), + }) + c.Assert(err, IsNil) + + // targets.json should be signed, but role1.json is not signed because there + // is no key in the local store. + m, err := local.GetMeta() + c.Assert(err, IsNil) + targetsMeta := &data.Signed{} + c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil) + c.Assert(len(targetsMeta.Signatures), Equals, 1) + role1Meta := &data.Signed{} + c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil) + c.Assert(len(role1Meta.Signatures), Equals, 0) + + c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures}) + + // Sign role1.json. + c.Assert(local.SaveSigner("role1", role1Key), IsNil) + c.Assert(r.Sign("role1.json"), IsNil) + + m, err = local.GetMeta() + c.Assert(err, IsNil) + targetsMeta = &data.Signed{} + c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil) + c.Assert(len(targetsMeta.Signatures), Equals, 1) + role1Meta = &data.Signed{} + c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil) + c.Assert(len(role1Meta.Signatures), Equals, 1) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) +} + +func (rs *RepoSuite) TestAddOrUpdateSignatureWithDelegations(c *C) { + tmp := newTmpDir(c) + local := FileSystemStore(tmp.path, nil) + r, err := NewRepo(local) + c.Assert(err, IsNil) + + // Add one key to each role + genKey(c, r, "root") + genKey(c, r, "targets") + genKey(c, r, "snapshot") + genKey(c, r, "timestamp") + + role1Key, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + + role1 := data.DelegatedRole{ + Name: "role1", + KeyIDs: role1Key.PublicData().IDs(), + Paths: []string{"A/*", "B/*"}, + Threshold: 1, + } + err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{ + role1Key.PublicData(), + }) + c.Assert(err, IsNil) + + // targets.json should be signed, but role1.json is not signed because there + // is no key in the local store. + m, err := local.GetMeta() + c.Assert(err, IsNil) + targetsMeta := &data.Signed{} + c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil) + c.Assert(len(targetsMeta.Signatures), Equals, 1) + role1Meta := &data.Signed{} + c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil) + c.Assert(len(role1Meta.Signatures), Equals, 0) + + c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures}) + + // Sign role1.json. + sig, err := role1Key.SignMessage(role1Meta.Signed) + c.Assert(err, IsNil) + err = r.AddOrUpdateSignature("role1.json", data.Signature{ + KeyID: role1Key.PublicData().IDs()[0], + Signature: sig, + }) + c.Assert(err, IsNil) + + m, err = local.GetMeta() + c.Assert(err, IsNil) + targetsMeta = &data.Signed{} + c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil) + c.Assert(len(targetsMeta.Signatures), Equals, 1) + role1Meta = &data.Signed{} + c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil) + c.Assert(len(role1Meta.Signatures), Equals, 1) + + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) +} From 805310c6f8b16f105b730b40841837385b1b054c Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Wed, 13 Apr 2022 17:08:33 -0400 Subject: [PATCH 45/48] Remove unused functions in errors.go --- client/errors.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/client/errors.go b/client/errors.go index 40f52d1d..0edd9578 100644 --- a/client/errors.go +++ b/client/errors.go @@ -3,8 +3,6 @@ package client import ( "errors" "fmt" - - "github.com/theupdateframework/go-tuf/verify" ) var ( @@ -49,21 +47,6 @@ func (e ErrMaxDelegations) Error() string { return fmt.Sprintf("tuf: max delegation of %d reached searching for %s with snapshot version %d", e.MaxDelegations, e.Target, e.SnapshotVersion) } -//lint:ignore U1000 unused -func isDecodeFailedWithErrRoleThreshold(err error) bool { - e, ok := err.(ErrDecodeFailed) - if !ok { - return false - } - return isErrRoleThreshold(e.Err) -} - -//lint:ignore U1000 unused -func isErrRoleThreshold(err error) bool { - _, ok := err.(verify.ErrRoleThreshold) - return ok -} - type ErrNotFound struct { File string } From 6ebf1e8d9b012ec193905ad580832d840dd95d7b Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Thu, 14 Apr 2022 16:32:26 -0400 Subject: [PATCH 46/48] Remove commented out test code --- repo_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/repo_test.go b/repo_test.go index 58aa273a..c09f05d6 100644 --- a/repo_test.go +++ b/repo_test.go @@ -2221,9 +2221,6 @@ func (rs *RepoSuite) TestDelegations(c *C) { checkTargets("targets", "potato.txt") checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") checkTargets("role2", "C/clementine.txt", "D/durian.txt") - - // Check compatibility with Sign and j - // r.Sign("4.) } func (rs *RepoSuite) TestHashBinDelegations(c *C) { From 722aa3e4208a9d85f9db0c19ba00fb47f54f52dd Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Thu, 14 Apr 2022 17:17:43 -0400 Subject: [PATCH 47/48] Add test to verify that adding a delegation to an existing role doesn't change signed targets --- repo_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/repo_test.go b/repo_test.go index c09f05d6..76f2d094 100644 --- a/repo_test.go +++ b/repo_test.go @@ -2221,6 +2221,30 @@ func (rs *RepoSuite) TestDelegations(c *C) { checkTargets("targets", "potato.txt") checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") checkTargets("role2", "C/clementine.txt", "D/durian.txt") + + // Add back the role1 -> role2 delegation, and verify that it doesn't change + // existing targets in role2.json. + err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{ + role1To2Key.PublicData(), + }) + c.Assert(err, IsNil) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + snapshot, err = r.snapshot() + c.Assert(err, IsNil) + c.Assert(snapshot.Meta, HasLen, 3) + // Both role1 and role2 should have a bumped version. + // role1 is bumped because the delegations changed. + // role2 is only bumped because its expiration is bumped. + c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3)) + c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(5)) + c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(5)) + + checkTargets("targets", "potato.txt") + checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt") + checkTargets("role2", "C/clementine.txt", "D/durian.txt") } func (rs *RepoSuite) TestHashBinDelegations(c *C) { From e481cd3f645673b838844c0b76f970a5ba48ef1d Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Thu, 14 Apr 2022 17:27:35 -0400 Subject: [PATCH 48/48] Fix SignMessage usage in test with new canonicalization pattern --- repo_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/repo_test.go b/repo_test.go index 76f2d094..c71bf5f0 100644 --- a/repo_test.go +++ b/repo_test.go @@ -2521,7 +2521,9 @@ func (rs *RepoSuite) TestAddOrUpdateSignatureWithDelegations(c *C) { c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures}) // Sign role1.json. - sig, err := role1Key.SignMessage(role1Meta.Signed) + canonical, err := cjson.EncodeCanonical(role1Meta.Signed) + c.Assert(err, IsNil) + sig, err := role1Key.SignMessage(canonical) c.Assert(err, IsNil) err = r.AddOrUpdateSignature("role1.json", data.Signature{ KeyID: role1Key.PublicData().IDs()[0],