| 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() |
| } |