Repository: theupdateframework/go-tuf
Pull request #271 (2022-04-19 15:39:18)

feat: propose adding Zach Newman to list of maintainers
Please fill in the fields below to submit a pull request.  The more information that is provided, the better.

Fixes #<Issue>
Release Notes: <!-- What comments/remarks should we include in the release notes for this change? -->

**Types of changes**:
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [  ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [  ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

**Description of the changes being introduced by the pull request**:

**Please verify and check that the pull request fulfills the following requirements**:

- [N/A] Tests have been added for the bug fix or new feature
- [N/A] Docs have been added for the bug fix or new feature



Patch:
diff --git a/MAINTAINERS b/MAINTAINERS
index d3443b1..4d0cfe7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3,4 +3,6 @@ Trishank Karthik Kuppusamy <trishank.kuppusamy@datadoghq.com> (github: trishanka
 Joshua Lock <jlock@vmware.com> (github: joshuagl)
 Ethan Lownman <ethan.lowman@datadoghq.com> (github: ethan-lowman-dd)
 Marina Moore <mm9693@nyu.edu> (github: mnm678)
+Zack Newman <zjn@chainguard.dev> (github: znewman01)
 Hossein Siadati <hossein.siadati@datadoghq.com> (github: hosseinsia)
+Radoslav Dimitrov <dimitrovr@vmware.com> (github: rdimitrov)
\ No newline at end of file
diff --git a/client/errors.go b/client/errors.go
index 44a36d2..0edd957 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,20 +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)
-}
-
-func isErrRoleThreshold(err error) bool {
-	_, ok := err.(verify.ErrRoleThreshold)
-	return ok
-}
-
 type ErrNotFound struct {
 	File string
 }
diff --git a/errors.go b/errors.go
index 1a8ab9f..09df039 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 9456016..375a1ef 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"
@@ -43,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.
@@ -222,8 +223,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 10d13af..0553fd6 100644
--- a/repo.go
+++ b/repo.go
@@ -13,13 +13,20 @@ 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"
 	"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",
@@ -73,12 +80,15 @@ 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
 	}
-	if err = r.writeTargetWithExpires(t, data.DefaultExpires("targets")); err != nil {
+
+	t.Version = 1
+	if err = r.setMeta("targets.json", t); err != nil {
 		return err
 	}
+
 	fmt.Println("Repository initialized")
 	return nil
 }
@@ -151,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()
@@ -169,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()
@@ -190,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) {
@@ -207,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) {
@@ -224,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) {
@@ -242,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) {
@@ -254,17 +264,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,10 +300,6 @@ func (r *Repo) timestamp() (*data.Timestamp, error) {
 }
 
 func (r *Repo) ChangePassphrase(keyRole string) error {
-	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)
 	}
@@ -298,10 +308,16 @@ func (r *Repo) ChangePassphrase(keyRole string) error {
 }
 
 func (r *Repo) GenKey(role string) ([]string, error) {
+	// 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 targets roles, since delegated targets keys
+	// are associated with a delegation (edge), not a role (node).
+
 	signer, err := keys.GenerateEd25519Key()
 	if err != nil {
 		return []string{}, err
@@ -315,11 +331,17 @@ 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 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 {
-	if !roles.IsTopLevelRole(keyRole) {
+	// Not compatible with delegated targets roles, since delegated targets keys
+	// are associated with a delegation (edge), not a role (node).
+
+	if roles.IsDelegatedTargetsRole(keyRole) {
 		return ErrInvalidRole{keyRole, "only support adding keys for top-level roles"}
 	}
 
@@ -327,6 +349,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 +363,27 @@ func (r *Repo) AddPrivateKeyWithExpires(keyRole string, signer keys.Signer, expi
 }
 
 func (r *Repo) AddVerificationKey(keyRole string, pk *data.PublicKey) error {
+	// 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 targets roles, since delegated targets keys
+	// are associated with a delegation (edge), not a role (node).
+
+	if roles.IsDelegatedTargetsRole(keyRole) {
+		return ErrInvalidRole{
+			Role:   keyRole,
+			Reason: "only top-level targets roles are supported",
+		}
+	}
+
+	if !validExpires(expires) {
+		return ErrInvalidExpires{expires}
+	}
+
 	root, err := r.root()
 	if err != nil {
 		return err
@@ -371,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 {
@@ -413,11 +454,17 @@ func (r *Repo) RootKeys() ([]*data.PublicKey, error) {
 }
 
 func (r *Repo) RevokeKey(role, id string) error {
+	// 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 {
-	if !roles.IsTopLevelRole(keyRole) {
+	// Not compatible with delegated targets roles, since delegated targets keys
+	// are associated with a delegation (edge), not a role (node).
+
+	if roles.IsDelegatedTargetsRole(keyRole) {
 		return ErrInvalidRole{keyRole, "only revocations for top-level roles supported"}
 	}
 
@@ -476,13 +523,150 @@ 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")
 	}
 	return err
 }
 
+// AddDelegatedRole is equivalent to AddDelegatedRoleWithExpires, but
+// with a default expiration time.
+func (r *Repo) AddDelegatedRole(delegator string, delegatedRole data.DelegatedRole, keys []*data.PublicKey) error {
+	return r.AddDelegatedRoleWithExpires(delegator, delegatedRole, keys, data.DefaultExpires("targets"))
+}
+
+// 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) AddDelegatedRoleWithExpires(delegator string, delegatedRole 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)
+	}
+
+	if t.Delegations == nil {
+		t.Delegations = &data.Delegations{}
+		t.Delegations.Keys = make(map[string]*data.PublicKey)
+	}
+
+	for _, keyID := range delegatedRole.KeyIDs {
+		for _, key := range keys {
+			if key.ContainsID(keyID) {
+				t.Delegations.Keys[keyID] = key
+				break
+			}
+		}
+	}
+
+	for _, r := range t.Delegations.Roles {
+		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, delegatedRole)
+	t.Expires = expires
+
+	delegatorFile := delegator + ".json"
+	if !r.local.FileIsStaged(delegatorFile) {
+		t.Version++
+	}
+
+	err = r.setMeta(delegatorFile, t)
+	if err != nil {
+		return fmt.Errorf("error setting metadata for %q: %w", delegatorFile, err)
+	}
+
+	delegatee := delegatedRole.Name
+	dt, err := r.targets(delegatee)
+	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++
+	}
+
+	err = r.setMeta(delegateeFile, dt)
+	if err != nil {
+		return fmt.Errorf("error setting metadata for %q: %w", delegateeFile, err)
+	}
+
+	return nil
+}
+
+// AddDelegatedRolesForPathHashBins is equivalent to
+// AddDelegatedRolesForPathHashBinsWithExpires, but with a default
+// expiration time.
+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"))
+}
+
+// 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) 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()...)
+	}
+
+	n := bins.NumBins()
+	for i := uint64(0); i < n; i += 1 {
+		bin := bins.GetBin(i)
+		name := bin.RoleName()
+		err := r.AddDelegatedRoleWithExpires(delegator, data.DelegatedRole{
+			Name:             name,
+			KeyIDs:           sets.DeduplicateStrings(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
+}
+
+// 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. 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 {
+		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++
+	}
+
+	err = r.setMeta(delegatorFile, t)
+	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)
@@ -490,12 +674,55 @@ 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 {
-	keys, err := r.getSortedSigningKeys(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 nil, err
+		}
+		dbs = append(dbs, db)
+	} else {
+		ddbs, err := r.delegatorDBs(role)
+		if err != nil {
+			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 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, keys...)
+
+	s, err := sign.Marshal(meta, signers...)
 	if err != nil {
 		return err
 	}
@@ -509,16 +736,13 @@ func (r *Repo) setTopLevelMeta(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
 	}
 
-	keys, err := r.getSortedSigningKeys(role)
+	keys, err := r.signersForRole(role)
 	if err != nil {
 		return err
 	}
@@ -545,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"}
+	}
+
+	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 !roleData.ValidKey(signature.KeyID) {
+	if !keyInDB {
 		return verify.ErrInvalidKey
 	}
 
@@ -579,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
+			}
 		}
 	}
 
@@ -594,46 +828,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 := []keys.Signer{}
+	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,22 +882,141 @@ func (r *Repo) SignedMeta(roleFilename string) (*data.Signed, error) {
 	return s, nil
 }
 
+// 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
+		}
+		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 = append(delegatorDBs, 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 {
+	// 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
-	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
 
 	meta := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)}
 	meta.Hashes[digestAlg], err = hex.DecodeString(digest)
@@ -676,13 +1028,28 @@ 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 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}
+
+	targetsMeta.Expires = expires.Round(time.Second)
+
+	manifestName := targetsRoleName + ".json"
+	if !r.local.FileIsStaged(manifestName) {
+		targetsMeta.Version++
+	}
+
+	err = r.setMeta(manifestName, targetsMeta)
+	if err != nil {
+		return fmt.Errorf("error setting metadata for %q: %w", manifestName, err)
+	}
 
-	return r.writeTargetWithExpires(t, expires)
+	return nil
 }
 
 func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error {
@@ -690,57 +1057,98 @@ 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)
+}
+
+// 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}
 	}
 
-	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 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) {
-		meta, err := util.GenerateTargetFileMeta(target, r.hashAlgorithms...)
+		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
+
+		targetsMeta := originalMeta
+		if tm, ok := updatedTargetsMeta[targetsRoleName]; ok {
+			// Metadata in updatedTargetsMeta overrides staged/commited metadata.
+			targetsMeta = tm
+		}
+
+		fileMeta, err := util.GenerateTargetFileMeta(target, r.hashAlgorithms...)
 		if err != nil {
 			return err
 		}
-		path = util.NormalizeTarget(path)
 
-		// 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 {
-			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
+
+		updatedTargetsMeta[targetsRoleName] = targetsMeta
+
 		return nil
 	}); err != nil {
 		return err
 	}
-	return r.writeTargetWithExpires(t, expires)
-}
 
-func (r *Repo) writeTargetWithExpires(t *data.Targets, expires time.Time) error {
-	t.Expires = expires.Round(time.Second)
-	if !r.local.FileIsStaged("targets.json") {
-		t.Version++
+	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
+		}
+
+		updatedTargetsMeta["targets"] = t
 	}
 
-	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)
+	exp := expires.Round(time.Second)
+	for roleName, targetsMeta := range updatedTargetsMeta {
+		targetsMeta.Expires = exp
+
+		manifestName := roleName + ".json"
+		if !r.local.FileIsStaged(manifestName) {
+			targetsMeta.Version++
+		}
+
+		err := r.setMeta(manifestName, targetsMeta)
+		if err != nil {
+			return fmt.Errorf("error setting metadata for %q: %w", manifestName, err)
 		}
 	}
-	return err
+
+	return nil
 }
 
 func (r *Repo) RemoveTarget(path string) error {
@@ -761,7 +1169,23 @@ func (r *Repo) RemoveTargetsWithExpires(paths []string, expires time.Time) error
 		return ErrInvalidExpires{expires}
 	}
 
-	t, err := r.topLevelTargets()
+	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 {
+	roleName := strings.TrimSuffix(metaName, ".json")
+	t, err := r.targets(roleName)
 	if err != nil {
 		return err
 	}
@@ -776,7 +1200,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
@@ -790,23 +1214,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.setTopLevelMeta("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
@@ -817,7 +1241,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 +1262,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
@@ -848,7 +1277,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)
 	}
@@ -864,11 +1293,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()
@@ -884,7 +1309,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)
 	}
@@ -892,48 +1317,95 @@ 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
+			}
+
+			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
+				}
+				for name, m := range t.Targets {
+					hashes[path.Join("targets", name)] = m.Hashes
+				}
+			}
+
+		}
+
 	}
+
 	return hashes, nil
 }
 
@@ -988,13 +1460,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 +1490,25 @@ 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")
+
+	dbs, err := r.dbsForRole(role)
+	if err != nil {
+		return err
 	}
+
+	for _, db := range dbs {
+		if err := db.Verify(s, role, 0); err != nil {
+			return ErrInsufficientSignatures{metaFilename, err}
+		}
+	}
+
 	return nil
 }
 
diff --git a/verify/db.go b/verify/db.go
index a14a514..657c2c5 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
 	}


Test patch:
diff --git a/repo_test.go b/repo_test.go
index 476e0b4..c71bf5f 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"
@@ -165,8 +166,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)
 }
 
@@ -632,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"})
@@ -1386,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)
@@ -1506,6 +1514,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()
@@ -1553,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)
@@ -1758,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()
@@ -1784,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
@@ -1865,5 +1875,672 @@ 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 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, ok := metas[f]
+		c.Assert(ok, Equals, true, Commentf("meta file: %v", f))
+
+		s := &data.Signed{}
+		err = json.Unmarshal(meta, s)
+		c.Assert(err, IsNil)
+
+		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)
+	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, int64(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)
+
+		err = local.SaveSigner(role, key)
+		c.Assert(err, IsNil)
+
+		return key
+	}
+
+	// Delegate from targets -> role1 for A/*, B/* with one key, threshold 1.
+	role1ABKey := saveNewKey("role1")
+	role1AB := data.DelegatedRole{
+		Name:      "role1",
+		KeyIDs:    role1ABKey.PublicData().IDs(),
+		Paths:     []string{"A/*", "B/*"},
+		Threshold: 1,
+	}
+	err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{
+		role1ABKey.PublicData(),
+	})
+	c.Assert(err, IsNil)
+
+	// Adding duplicate delegation should return an error.
+	err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{
+		role1ABKey.PublicData(),
+	})
+	c.Assert(err, NotNil)
+
+	// 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: concat(
+			role2CDKey1.PublicData().IDs(),
+			role2CDKey2.PublicData().IDs(),
+			role2CDKey3.PublicData().IDs(),
+		),
+		Paths:     []string{"C/*", "D/*"},
+		Threshold: 2,
+	}
+	err = r.AddDelegatedRole("targets", role2CD, []*data.PublicKey{
+		role2CDKey1.PublicData(),
+		role2CDKey2.PublicData(),
+		role2CDKey3.PublicData(),
+	})
+	c.Assert(err, IsNil)
+
+	// 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{"A/allium.txt"},
+		Threshold:   1,
+		Terminating: true,
+	}
+	err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{
+		role1To2Key.PublicData(),
+	})
+	c.Assert(err, IsNil)
+
+	checkDelegations := func(delegator string, delegatedRoles ...data.DelegatedRole) {
+		t, err := r.targets(delegator)
+		c.Assert(err, IsNil)
+
+		// Check that delegated roles are copied verbatim.
+		c.Assert(t.Delegations.Roles, DeepEquals, delegatedRoles)
+
+		// 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)
+
+	snapshot, err = r.snapshot()
+	c.Assert(err, IsNil)
+	c.Assert(snapshot.Meta, HasLen, 3)
+	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,
+		"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, 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,
+		"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, 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,
+		"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 the 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, 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,
+		"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, 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,
+		"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")
+
+	// 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) {
+	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")
+
+	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)
+	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.AddDelegatedRole("targets", data.DelegatedRole{
+		Name:      "bins",
+		KeyIDs:    binsKey.PublicData().IDs(),
+		Paths:     []string{"*.txt"},
+		Threshold: 1,
+	}, []*data.PublicKey{
+		binsKey.PublicData(),
+	})
+	c.Assert(err, IsNil)
+
+	err = r.AddDelegatedRolesForPathHashBins("bins", hb, []*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)
+
+	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)
+
+	c.Assert(r.Snapshot(), IsNil)
+	c.Assert(r.Timestamp(), IsNil)
+	c.Assert(r.Commit(), IsNil)
+
+	snapshot, err := r.snapshot()
+	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, 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)
+	c.Assert(targets.Targets, HasLen, 1)
+
+	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(),
+	})
+}
+
+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, int64(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.AddDelegatedRole("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, int64(2))
+	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(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, int64(3))
+	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(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(),
+	})
+}
+
+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.
+	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],
+		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)
 }