summaryrefslogtreecommitdiffstats
path: root/repo.go
diff options
context:
space:
mode:
Diffstat (limited to 'repo.go')
-rw-r--r--repo.go1695
1 files changed, 1695 insertions, 0 deletions
diff --git a/repo.go b/repo.go
new file mode 100644
index 0000000..db2ac66
--- /dev/null
+++ b/repo.go
@@ -0,0 +1,1695 @@
+package tuf
+
+import (
+ "bytes"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "path"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/secure-systems-lab/go-securesystemslib/cjson"
+ "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",
+ "targets.json",
+ "snapshot.json",
+ "timestamp.json",
+}
+
+// TargetsWalkFunc is a function of a target path name and a target payload used to
+// execute some function on each staged target file. For example, it may normalize path
+// names and generate target file metadata with additional custom metadata.
+type TargetsWalkFunc func(path string, target io.Reader) error
+
+type Repo struct {
+ local LocalStore
+ hashAlgorithms []string
+ meta map[string]json.RawMessage
+ prefix string
+ indent string
+ logger *log.Logger
+}
+
+type RepoOpts func(r *Repo)
+
+func WithLogger(logger *log.Logger) RepoOpts {
+ return func(r *Repo) {
+ r.logger = logger
+ }
+}
+
+func WithHashAlgorithms(hashAlgorithms ...string) RepoOpts {
+ return func(r *Repo) {
+ r.hashAlgorithms = hashAlgorithms
+ }
+}
+
+func WithPrefix(prefix string) RepoOpts {
+ return func(r *Repo) {
+ r.prefix = prefix
+ }
+}
+
+func WithIndex(indent string) RepoOpts {
+ return func(r *Repo) {
+ r.indent = indent
+ }
+}
+
+func NewRepo(local LocalStore, hashAlgorithms ...string) (*Repo, error) {
+ return NewRepoIndent(local, "", "", hashAlgorithms...)
+}
+
+func NewRepoIndent(local LocalStore, prefix string, indent string,
+ hashAlgorithms ...string) (*Repo, error) {
+ r := &Repo{
+ local: local,
+ hashAlgorithms: hashAlgorithms,
+ prefix: prefix,
+ indent: indent,
+ logger: log.New(io.Discard, "", 0),
+ }
+
+ var err error
+ r.meta, err = local.GetMeta()
+ if err != nil {
+ return nil, err
+ }
+ return r, nil
+}
+
+func NewRepoWithOpts(local LocalStore, opts ...RepoOpts) (*Repo, error) {
+ r, err := NewRepo(local)
+ if err != nil {
+ return nil, err
+ }
+ for _, opt := range opts {
+ opt(r)
+ }
+ return r, nil
+}
+
+func (r *Repo) Init(consistentSnapshot bool) error {
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+ if len(t.Targets) > 0 {
+ return ErrInitNotAllowed
+ }
+ root := data.NewRoot()
+ root.ConsistentSnapshot = consistentSnapshot
+ // Set root version to 1 for a new root.
+ root.Version = 1
+ if err = r.setMeta("root.json", root); err != nil {
+ return err
+ }
+
+ t.Version = 1
+ if err = r.setMeta("targets.json", t); err != nil {
+ return err
+ }
+
+ r.logger.Println("Repository initialized")
+ return nil
+}
+
+func (r *Repo) topLevelKeysDB() (*verify.DB, error) {
+ db := verify.NewDB()
+ root, err := r.root()
+ if err != nil {
+ return nil, err
+ }
+ for id, k := range root.Keys {
+ if err := db.AddKey(id, k); err != nil {
+ return nil, err
+ }
+ }
+ for name, role := range root.Roles {
+ if err := db.AddRole(name, role); err != nil {
+ return nil, err
+ }
+ }
+ return db, nil
+}
+
+func (r *Repo) root() (*data.Root, error) {
+ rootJSON, ok := r.meta["root.json"]
+ if !ok {
+ return data.NewRoot(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(rootJSON, s); err != nil {
+ return nil, err
+ }
+ root := &data.Root{}
+ if err := json.Unmarshal(s.Signed, root); err != nil {
+ return nil, err
+ }
+ return root, nil
+}
+
+func (r *Repo) snapshot() (*data.Snapshot, error) {
+ snapshotJSON, ok := r.meta["snapshot.json"]
+ if !ok {
+ return data.NewSnapshot(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(snapshotJSON, s); err != nil {
+ return nil, err
+ }
+ snapshot := &data.Snapshot{}
+ if err := json.Unmarshal(s.Signed, snapshot); err != nil {
+ return nil, err
+ }
+ return snapshot, nil
+}
+
+func (r *Repo) RootVersion() (int64, error) {
+ root, err := r.root()
+ if err != nil {
+ return -1, err
+ }
+ return root.Version, nil
+}
+
+func (r *Repo) GetThreshold(keyRole string) (int, error) {
+ 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()
+ if err != nil {
+ return -1, err
+ }
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ return -1, ErrInvalidRole{keyRole, "role missing from root metadata"}
+ }
+
+ return role.Threshold, nil
+}
+
+func (r *Repo) SetThreshold(keyRole string, t int) error {
+ 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()
+ if err != nil {
+ return err
+ }
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ return ErrInvalidRole{keyRole, "role missing from root metadata"}
+ }
+ if role.Threshold == t {
+ // Change was a no-op.
+ return nil
+ }
+ role.Threshold = t
+ if !r.local.FileIsStaged("root.json") {
+ root.Version++
+ }
+ return r.setMeta("root.json", root)
+}
+
+func (r *Repo) Targets() (data.TargetFiles, error) {
+ targets, err := r.topLevelTargets()
+ if err != nil {
+ return nil, err
+ }
+ return targets.Targets, nil
+}
+
+func (r *Repo) SetTargetsVersion(v int64) error {
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+ t.Version = v
+ return r.setMeta("targets.json", t)
+}
+
+func (r *Repo) TargetsVersion() (int64, error) {
+ t, err := r.topLevelTargets()
+ if err != nil {
+ return -1, err
+ }
+ return t.Version, nil
+}
+
+func (r *Repo) SetTimestampVersion(v int64) error {
+ ts, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ ts.Version = v
+ return r.setMeta("timestamp.json", ts)
+}
+
+func (r *Repo) TimestampVersion() (int64, error) {
+ ts, err := r.timestamp()
+ if err != nil {
+ return -1, err
+ }
+ return ts.Version, nil
+}
+
+func (r *Repo) SetSnapshotVersion(v int64) error {
+ s, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+
+ s.Version = v
+ return r.setMeta("snapshot.json", s)
+}
+
+func (r *Repo) SnapshotVersion() (int64, error) {
+ s, err := r.snapshot()
+ if err != nil {
+ return -1, err
+ }
+ return s.Version, nil
+}
+
+func (r *Repo) topLevelTargets() (*data.Targets, error) {
+ 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, fmt.Errorf("error unmarshalling for targets %q: %w", metaName, err)
+ }
+ targets := &data.Targets{}
+ if err := json.Unmarshal(s.Signed, targets); err != nil {
+ return nil, fmt.Errorf("error unmarshalling signed data for targets %q: %w", metaName, err)
+ }
+ return targets, nil
+}
+
+func (r *Repo) timestamp() (*data.Timestamp, error) {
+ timestampJSON, ok := r.meta["timestamp.json"]
+ if !ok {
+ return data.NewTimestamp(), nil
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(timestampJSON, s); err != nil {
+ return nil, err
+ }
+ timestamp := &data.Timestamp{}
+ if err := json.Unmarshal(s.Signed, timestamp); err != nil {
+ return nil, err
+ }
+ return timestamp, nil
+}
+
+func (r *Repo) ChangePassphrase(keyRole string) error {
+ if p, ok := r.local.(PassphraseChanger); ok {
+ return p.ChangePassphrase(keyRole)
+ }
+
+ return ErrChangePassphraseNotSupported
+}
+
+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) {
+ return r.GenKeyWithSchemeAndExpires(keyRole, expires, data.KeySchemeEd25519)
+}
+
+func (r *Repo) GenKeyWithSchemeAndExpires(role string, expires time.Time, keyScheme data.KeyScheme) ([]string, error) {
+ var signer keys.Signer
+ var err error
+ switch keyScheme {
+ case data.KeySchemeEd25519:
+ signer, err = keys.GenerateEd25519Key()
+ case data.KeySchemeECDSA_SHA2_P256:
+ signer, err = keys.GenerateEcdsaKey()
+ case data.KeySchemeRSASSA_PSS_SHA256:
+ signer, err = keys.GenerateRsaKey()
+ default:
+ return nil, errors.New("unknown key type")
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ // Not compatible with delegated targets roles, since delegated targets keys
+ // are associated with a delegation (edge), not a role (node).
+
+ if err = r.AddPrivateKeyWithExpires(role, signer, expires); err != nil {
+ return nil, err
+ }
+ return signer.PublicData().IDs(), nil
+}
+
+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 {
+ // 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"}
+ }
+
+ if !validExpires(expires) {
+ 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
+ }
+
+ if err := r.AddVerificationKeyWithExpiration(keyRole, signer.PublicData(), expires); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+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
+ }
+
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ role = &data.Role{KeyIDs: []string{}, Threshold: 1}
+ root.Roles[keyRole] = role
+ }
+ changed := false
+ if role.AddKeyIDs(pk.IDs()) {
+ changed = true
+ }
+
+ if root.AddKey(pk) {
+ changed = true
+ }
+
+ if !changed {
+ return nil
+ }
+
+ root.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("root.json") {
+ root.Version++
+ }
+
+ return r.setMeta("root.json", root)
+}
+
+func validExpires(expires time.Time) bool {
+ return time.Until(expires) > 0
+}
+
+func (r *Repo) RootKeys() ([]*data.PublicKey, error) {
+ root, err := r.root()
+ if err != nil {
+ return nil, err
+ }
+ role, ok := root.Roles["root"]
+ if !ok {
+ return nil, nil
+ }
+
+ // We might have multiple key ids that correspond to the same key, so
+ // make sure we only return unique keys.
+ seen := make(map[string]struct{})
+ rootKeys := []*data.PublicKey{}
+ for _, id := range role.KeyIDs {
+ key, ok := root.Keys[id]
+ if !ok {
+ return nil, fmt.Errorf("tuf: invalid root metadata")
+ }
+ found := false
+ if _, ok := seen[id]; ok {
+ found = true
+ break
+ }
+ if !found {
+ for _, id := range key.IDs() {
+ seen[id] = struct{}{}
+ }
+ rootKeys = append(rootKeys, key)
+ }
+ }
+ return rootKeys, nil
+}
+
+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 {
+ // 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"}
+ }
+
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+
+ key, ok := root.Keys[id]
+ if !ok {
+ return ErrKeyNotFound{keyRole, id}
+ }
+
+ role, ok := root.Roles[keyRole]
+ if !ok {
+ return ErrKeyNotFound{keyRole, id}
+ }
+
+ // Create a list of filtered key IDs that do not contain the revoked key IDs.
+ filteredKeyIDs := make([]string, 0, len(role.KeyIDs))
+
+ // There may be multiple keyids that correspond to this key, so
+ // filter all of them out.
+ for _, keyID := range role.KeyIDs {
+ if !key.ContainsID(keyID) {
+ filteredKeyIDs = append(filteredKeyIDs, keyID)
+ }
+ }
+ if len(filteredKeyIDs) == len(role.KeyIDs) {
+ return ErrKeyNotFound{keyRole, id}
+ }
+ role.KeyIDs = filteredKeyIDs
+ root.Roles[keyRole] = role
+
+ // Only delete the key from root.Keys if the key is no longer in use by
+ // any other role.
+ key_in_use := false
+ for _, role := range root.Roles {
+ for _, keyID := range role.KeyIDs {
+ if key.ContainsID(keyID) {
+ key_in_use = true
+ }
+ }
+ }
+ if !key_in_use {
+ for _, keyID := range key.IDs() {
+ delete(root.Keys, keyID)
+ }
+ }
+ root.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("root.json") {
+ root.Version++
+ }
+
+ err = r.setMeta("root.json", root)
+ if err == nil {
+ r.logger.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 = nil
+
+ 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)
+ }
+ return json.MarshalIndent(v, r.prefix, r.indent)
+}
+
+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, signers...)
+ if err != nil {
+ return err
+ }
+ b, err := r.jsonMarshal(s)
+ if err != nil {
+ return err
+ }
+ r.meta[roleFilename] = b
+ return r.local.SetMeta(roleFilename, b)
+}
+
+// CanonicalizeAndSign canonicalizes the signed portion of signed, then signs it using the key(s) associated with role.
+//
+// It appends the signature to signed.
+//
+// It returns the total number of keys used for signing, 0 (along with
+// ErrNoKeys) if no keys were found, or -1 (along with an error) in error cases.
+func (r *Repo) CanonicalizeAndSign(role string, signed *data.Signed) (int, error) {
+ keys, err := r.signersForRole(role)
+ if err != nil {
+ return -1, err
+ }
+ if len(keys) == 0 {
+ return 0, ErrNoKeys{role}
+ }
+ for _, k := range keys {
+ if err = sign.Sign(signed, k); err != nil {
+ return -1, err
+ }
+ }
+ return len(keys), nil
+}
+
+// SignPayload canonicalizes the signed portion of payload, then signs it using the key(s) associated with role.
+//
+// It returns the total number of keys used for signing, 0 (along with
+// ErrNoKeys) if no keys were found, or -1 (along with an error) in error cases.
+//
+// DEPRECATED: please use CanonicalizeAndSign instead.
+func (r *Repo) SignPayload(role string, payload *data.Signed) (int, error) {
+ return r.CanonicalizeAndSign(role, payload)
+}
+
+// SignRaw signs the given (pre-canonicalized) payload using the key(s) associated with role.
+//
+// It returns the new data.Signatures.
+func (r *Repo) SignRaw(role string, payload []byte) ([]data.Signature, error) {
+ keys, err := r.signersForRole(role)
+ if err != nil {
+ return nil, err
+ }
+ if len(keys) == 0 {
+ return nil, ErrNoKeys{role}
+ }
+
+ allSigs := make([]data.Signature, 0, len(keys))
+ for _, k := range keys {
+ sigs, err := sign.MakeSignatures(payload, k)
+ if err != nil {
+ return nil, err
+ }
+ allSigs = append(allSigs, sigs...)
+ }
+ return allSigs, nil
+}
+
+func (r *Repo) Sign(roleFilename string) error {
+ signed, err := r.SignedMeta(roleFilename)
+ if err != nil {
+ return err
+ }
+
+ role := strings.TrimSuffix(roleFilename, ".json")
+ numKeys, err := r.SignPayload(role, signed)
+ if errors.Is(err, ErrNoKeys{role}) {
+ return ErrNoKeys{roleFilename}
+ } else if err != nil {
+ return err
+ }
+
+ b, err := r.jsonMarshal(signed)
+ if err != nil {
+ return err
+ }
+ r.meta[roleFilename] = b
+ err = r.local.SetMeta(roleFilename, b)
+ if err == nil {
+ r.logger.Println("Signed", roleFilename, "with", numKeys, "key(s)")
+ }
+ return err
+}
+
+// AddOrUpdateSignature allows users to add or update a signature generated with an external tool.
+// 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")
+
+ // Check key ID is in valid for the role.
+ dbs, err := r.dbsForRole(role)
+ if err != nil {
+ return err
+ }
+
+ if len(dbs) == 0 {
+ return ErrInvalidRole{role, "no trusted keys for role"}
+ }
+
+ s, err := r.SignedMeta(roleFilename)
+ if err != nil {
+ return err
+ }
+
+ keyInDB := false
+ validSig := 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
+
+ verifier, err := db.GetVerifier(signature.KeyID)
+ if err != nil {
+ continue
+ }
+
+ // Now check if this validly signed the metadata.
+ if err := verify.VerifySignature(s.Signed, signature.Signature,
+ verifier); err == nil {
+ validSig = true
+ break
+ }
+ }
+ }
+ if !keyInDB {
+ // This key was not delegated for the role in any delegatee.
+ return verify.ErrInvalidKey
+ }
+ if !validSig {
+ // The signature was invalid.
+ return verify.ErrInvalid
+ }
+
+ // Add or update signature.
+ signatures := make([]data.Signature, 0, len(s.Signatures)+1)
+ for _, sig := range s.Signatures {
+ if sig.KeyID != signature.KeyID {
+ signatures = append(signatures, sig)
+ }
+ }
+ signatures = append(signatures, signature)
+ s.Signatures = signatures
+
+ b, err := r.jsonMarshal(s)
+ if err != nil {
+ return err
+ }
+ r.meta[roleFilename] = b
+
+ return r.local.SetMeta(roleFilename, b)
+}
+
+// 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) getSignersInDB(roleName string, db *verify.DB) ([]keys.Signer, error) {
+ signers, err := r.local.GetSigners(roleName)
+ if err != nil {
+ return nil, err
+ }
+
+ if roleName == "root" {
+ sorted := make([]keys.Signer, len(signers))
+ copy(sorted, signers)
+ sort.Sort(signer.ByIDs(sorted))
+ return sorted, nil
+ }
+
+ role := db.GetRole(roleName)
+ if role == nil {
+ return nil, nil
+ }
+ if len(role.KeyIDs) == 0 {
+ return nil, nil
+ }
+
+ signersInDB := []keys.Signer{}
+ for _, s := range signers {
+ for _, id := range s.PublicData().IDs() {
+ if _, ok := role.KeyIDs[id]; ok {
+ signersInDB = append(signersInDB, s)
+ }
+ }
+ }
+
+ sort.Sort(signer.ByIDs(signersInDB))
+
+ return signersInDB, nil
+}
+
+// Used to retrieve the signable portion of the metadata when using an external signing tool.
+func (r *Repo) SignedMeta(roleFilename string) (*data.Signed, error) {
+ b, ok := r.meta[roleFilename]
+ if !ok {
+ return nil, ErrMissingMetadata{roleFilename}
+ }
+ s := &data.Signed{}
+ if err := json.Unmarshal(b, s); err != nil {
+ return nil, err
+ }
+ 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.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)
+
+ 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.TargetFileMeta{FileMeta: data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)}}
+ meta.Hashes[digestAlg], err = hex.DecodeString(digest)
+ if err != nil {
+ return err
+ }
+
+ // If custom is provided, set custom, otherwise maintain existing custom
+ // metadata
+ if len(custom) > 0 {
+ meta.Custom = &custom
+ } else if t, ok := targetsMeta.Targets[path]; ok {
+ meta.Custom = t.Custom
+ }
+
+ // 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] = 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 nil
+}
+
+func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error {
+ return r.AddTargetsWithExpires([]string{path}, custom, 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}
+ }
+
+ 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) {
+ 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
+ }
+
+ // If we have custom metadata, set it, otherwise maintain
+ // existing metadata if present
+ if len(custom) > 0 {
+ 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(targetsMeta.Targets, "/"+path)
+ targetsMeta.Targets[path] = fileMeta
+
+ updatedTargetsMeta[targetsRoleName] = targetsMeta
+
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ 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
+ }
+
+ 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 nil
+}
+
+func (r *Repo) RemoveTarget(path string) error {
+ return r.RemoveTargets([]string{path})
+}
+
+func (r *Repo) RemoveTargets(paths []string) error {
+ return r.RemoveTargetsWithExpires(paths, data.DefaultExpires("targets"))
+}
+
+func (r *Repo) RemoveTargetWithExpires(path string, expires time.Time) error {
+ return r.RemoveTargetsWithExpires([]string{path}, expires)
+}
+
+// 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
+ }
+
+ 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
+ }
+ removed_targets := []string{}
+ if len(paths) == 0 {
+ for rt := range t.Targets {
+ removed_targets = append(removed_targets, rt)
+ }
+ t.Targets = make(data.TargetFiles)
+ } else {
+ removed := false
+ for _, path := range paths {
+ path = util.NormalizeTarget(path)
+ if _, ok := t.Targets[path]; !ok {
+ r.logger.Printf("[%v] The following target is not present: %v\n", metaName, path)
+ continue
+ }
+ removed = true
+ // G2 -> we no longer desire any readers to ever observe non-prefix targets.
+ delete(t.Targets, "/"+path)
+ delete(t.Targets, path)
+ removed_targets = append(removed_targets, path)
+ }
+ if !removed {
+ return nil
+ }
+ }
+ t.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged(metaName) {
+ t.Version++
+ }
+
+ err = r.setMeta(metaName, t)
+ if err == nil {
+ r.logger.Printf("[%v] Removed targets:\n", metaName)
+ for _, v := range removed_targets {
+ r.logger.Println("*", v)
+ }
+ if len(t.Targets) != 0 {
+ r.logger.Printf("[%v] Added/staged targets:\n", metaName)
+ for k := range t.Targets {
+ r.logger.Println("*", k)
+ }
+ } else {
+ r.logger.Printf("[%v] There are no added/staged targets\n", metaName)
+ }
+ }
+ return err
+}
+
+func (r *Repo) Snapshot() error {
+ return r.SnapshotWithExpires(data.DefaultExpires("snapshot"))
+}
+
+func (r *Repo) snapshotMetadata() []string {
+ 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 {
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+
+ // Verify root metadata before verifying signatures on role metadata.
+ if err := r.verifySignatures("root.json"); err != nil {
+ return err
+ }
+
+ for _, metaName := range r.snapshotMetadata() {
+ if err := r.verifySignatures(metaName); err != nil {
+ return err
+ }
+ var err error
+ snapshot.Meta[metaName], err = r.snapshotFileMeta(metaName)
+ if err != nil {
+ return err
+ }
+ }
+ snapshot.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("snapshot.json") {
+ snapshot.Version++
+ }
+ err = r.setMeta("snapshot.json", snapshot)
+ if err == nil {
+ r.logger.Println("Staged snapshot.json metadata with expiration date:", snapshot.Expires)
+ }
+ return err
+}
+
+func (r *Repo) Timestamp() error {
+ return r.TimestampWithExpires(data.DefaultExpires("timestamp"))
+}
+
+func (r *Repo) TimestampWithExpires(expires time.Time) error {
+ if !validExpires(expires) {
+ return ErrInvalidExpires{expires}
+ }
+
+ if err := r.verifySignatures("snapshot.json"); err != nil {
+ return err
+ }
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ timestamp.Meta["snapshot.json"], err = r.timestampFileMeta("snapshot.json")
+ if err != nil {
+ return err
+ }
+ timestamp.Expires = expires.Round(time.Second)
+ if !r.local.FileIsStaged("timestamp.json") {
+ timestamp.Version++
+ }
+
+ err = r.setMeta("timestamp.json", timestamp)
+ if err == nil {
+ r.logger.Println("Staged timestamp.json metadata with expiration date:", timestamp.Expires)
+ }
+ return err
+}
+
+func (r *Repo) fileVersions() (map[string]int64, error) {
+ versions := make(map[string]int64)
+
+ 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)
+
+ 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
+}
+
+func (r *Repo) Commit() error {
+ // check we have all the metadata
+ for _, name := range topLevelMetadata {
+ if _, ok := r.meta[name]; !ok {
+ return ErrMissingMetadata{name}
+ }
+ }
+
+ // check roles are valid
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+ for name, role := range root.Roles {
+ if len(role.KeyIDs) < role.Threshold {
+ return ErrNotEnoughKeys{name, len(role.KeyIDs), role.Threshold}
+ }
+ }
+
+ // verify hashes in snapshot.json are up to date
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+ for _, name := range r.snapshotMetadata() {
+ expected, ok := snapshot.Meta[name]
+ if !ok {
+ return fmt.Errorf("tuf: snapshot.json missing hash for %s", name)
+ }
+ actual, err := r.snapshotFileMeta(name)
+ if err != nil {
+ return err
+ }
+ if err := util.SnapshotFileMetaEqual(actual, expected); err != nil {
+ return fmt.Errorf("tuf: invalid %s in snapshot.json: %s", name, err)
+ }
+ }
+
+ // verify hashes in timestamp.json are up to date
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ snapshotMeta, err := r.timestampFileMeta("snapshot.json")
+ if err != nil {
+ return err
+ }
+ if err := util.TimestampFileMetaEqual(snapshotMeta, timestamp.Meta["snapshot.json"]); err != nil {
+ return fmt.Errorf("tuf: invalid snapshot.json in timestamp.json: %s", err)
+ }
+
+ for _, name := range topLevelMetadata {
+ if err := r.verifySignatures(name); err != nil {
+ return err
+ }
+ }
+
+ versions, err := r.fileVersions()
+ if err != nil {
+ return err
+ }
+ hashes, err := r.fileHashes()
+ if err != nil {
+ return err
+ }
+
+ err = r.local.Commit(root.ConsistentSnapshot, versions, hashes)
+ if err == nil {
+ r.logger.Println("Committed successfully")
+ }
+ return err
+}
+
+func (r *Repo) Clean() error {
+ err := r.local.Clean()
+ if err == nil {
+ r.logger.Println("Removed all staged metadata and target files")
+ }
+ return err
+}
+
+func (r *Repo) verifySignatures(metaFilename string) error {
+ s, err := r.SignedMeta(metaFilename)
+ if err != nil {
+ return 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
+}
+
+func (r *Repo) snapshotFileMeta(roleFilename string) (data.SnapshotFileMeta, error) {
+ b, ok := r.meta[roleFilename]
+ if !ok {
+ return data.SnapshotFileMeta{}, ErrMissingMetadata{roleFilename}
+ }
+ return util.GenerateSnapshotFileMeta(bytes.NewReader(b), r.hashAlgorithms...)
+}
+
+func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, error) {
+ b, ok := r.meta[roleFilename]
+ if !ok {
+ return data.TimestampFileMeta{}, ErrMissingMetadata{roleFilename}
+ }
+ return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...)
+}
+
+func (r *Repo) Payload(roleFilename string) ([]byte, error) {
+ s, err := r.SignedMeta(roleFilename)
+ if err != nil {
+ return nil, err
+ }
+
+ p, err := cjson.EncodeCanonical(s.Signed)
+ if err != nil {
+ return nil, err
+ }
+
+ return p, nil
+}
+
+func (r *Repo) CheckRoleUnexpired(role string, validAt time.Time) error {
+ var expires time.Time
+ switch role {
+ case "root":
+ root, err := r.root()
+ if err != nil {
+ return err
+ }
+ expires = root.Expires
+ case "snapshot":
+ snapshot, err := r.snapshot()
+ if err != nil {
+ return err
+ }
+ expires = snapshot.Expires
+ case "timestamp":
+ timestamp, err := r.timestamp()
+ if err != nil {
+ return err
+ }
+ expires = timestamp.Expires
+ case "targets":
+ targets, err := r.topLevelTargets()
+ if err != nil {
+ return err
+ }
+ expires = targets.Expires
+ default:
+ return fmt.Errorf("invalid role: %s", role)
+ }
+ if expires.Before(validAt) || expires.Equal(validAt) {
+ return fmt.Errorf("role expired on: %s", expires)
+ }
+ return nil
+}
+
+// GetMeta returns the underlying meta file map from the store.
+func (r *Repo) GetMeta() (map[string]json.RawMessage, error) {
+ return r.local.GetMeta()
+}