diff --git a/client/client.go b/client/client.go index 87c6d6bc..e2701327 100644 --- a/client/client.go +++ b/client/client.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "encoding/json" + "fmt" "io" "github.com/theupdateframework/go-tuf/data" @@ -400,8 +401,8 @@ func (c *Client) getLocalMeta() error { } } + snapshot := &data.Snapshot{} if snapshotJSON, ok := meta["snapshot.json"]; ok { - snapshot := &data.Snapshot{} if err := c.db.UnmarshalTrusted(snapshotJSON, snapshot, "snapshot"); err != nil { loadFailed = true retErr = err @@ -425,12 +426,33 @@ func (c *Client) getLocalMeta() error { } } + if loadFailed { + // If any of the metadata failed to be verified, return the reason for that failure + // and fail fast before delegated targets + return retErr + } + + verifiedDelegatedTargets := make(map[string]bool) for fileName := range meta { if roles.IsDelegatedTargetsManifest(fileName) { - c.localMeta[fileName] = meta[fileName] + if delegations, err := c.getDelegationPathFromRaw(snapshot, meta[fileName]); err != nil { + loadFailed = true + retErr = err + } else { + for _, key := range delegations { + fileName := fmt.Sprintf("%s.json", key) + if !verifiedDelegatedTargets[fileName] { + verifiedDelegatedTargets[fileName] = true + } + } + } } } + for fileName := range verifiedDelegatedTargets { + c.localMeta[fileName] = meta[fileName] + } + if loadFailed { // If any of the metadata failed to be verified, return the reason for that failure return retErr @@ -438,6 +460,33 @@ func (c *Client) getLocalMeta() error { return nil } +// getDelegationPathFromRaw verifies a delegated targets against +// a given snapshot and returns an error if it's invalid +// +// Delegation must have targets to get a path, else an empty list +// will be returned: this is because the delegation iterator is leveraged. +func (c *Client) getDelegationPathFromRaw(snapshot *data.Snapshot, delegatedTargetsJSON json.RawMessage) ([]string, error) { + // unmarshal the delegated targets first without verifying as + // we need at least one targets file name to leverage the + // getTargetFileMetaDelegationPath method + s := &data.Signed{} + if err := json.Unmarshal(delegatedTargetsJSON, s); err != nil { + return nil, err + } + targets := &data.Targets{} + if err := json.Unmarshal(s.Signed, targets); err != nil { + return nil, err + } + for targetPath := range targets.Targets { + _, resp, err := c.getTargetFileMetaDelegationPath(targetPath, snapshot) + // We only need to test one targets file: + // - If it is valid, it means the delegated targets has been validated + // - If it is not, the delegated targets isn't valid + return resp, err + } + return nil, nil +} + // loadAndVerifyLocalRootMeta decodes and verifies root metadata from // local storage and loads the top-level keys. This method first clears // the DB for top-level keys and then loads the new keys. diff --git a/client/delegations.go b/client/delegations.go index de3e6647..cc0fc482 100644 --- a/client/delegations.go +++ b/client/delegations.go @@ -8,13 +8,23 @@ import ( // getTargetFileMeta searches for a verified TargetFileMeta matching a target // Requires a local snapshot to be loaded and is locked to the snapshot versions. -// Searches through delegated targets following TUF spec 1.0.19 section 5.6. func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) { snapshot, err := c.loadLocalSnapshot() if err != nil { return data.TargetFileMeta{}, err } + targetFileMeta, _, err := c.getTargetFileMetaDelegationPath(target, snapshot) + if err != nil { + return data.TargetFileMeta{}, err + } + return targetFileMeta, nil +} + +// getTargetFileMetaDelegationPath searches for a verified TargetFileMeta matching a target +// Requires snapshot to be passed and is locked to that specific snapshot versions. +// Searches through delegated targets following TUF spec 1.0.19 section 5.6. +func (c *Client) getTargetFileMetaDelegationPath(target string, snapshot *data.Snapshot) (data.TargetFileMeta, []string, error) { // delegationsIterator covers 5.6.7 // - pre-order depth-first search starting with the top targets // - filter delegations with paths or path_hash_prefixes matching searched target @@ -22,50 +32,75 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) { // - 5.6.7.2 terminations delegations, err := targets.NewDelegationsIterator(target, c.db) if err != nil { - return data.TargetFileMeta{}, err + return data.TargetFileMeta{}, nil, err } + targetFileMeta := data.TargetFileMeta{} + delegationRole := "" + for i := 0; i < c.MaxDelegations; i++ { d, ok := delegations.Next() if !ok { - return data.TargetFileMeta{}, ErrUnknownTarget{target, snapshot.Version} + return data.TargetFileMeta{}, nil, ErrUnknownTarget{target, snapshot.Version} } // covers 5.6.{1,2,3,4,5,6} targets, err := c.loadDelegatedTargets(snapshot, d.Delegatee.Name, d.DB) if err != nil { - return data.TargetFileMeta{}, err + return data.TargetFileMeta{}, nil, err } // stop when the searched TargetFileMeta is found if m, ok := targets.Targets[target]; ok { - return m, nil + delegationRole = d.Delegatee.Name + targetFileMeta = m + break } if targets.Delegations != nil { delegationsDB, err := verify.NewDBFromDelegations(targets.Delegations) if err != nil { - return data.TargetFileMeta{}, err + return data.TargetFileMeta{}, nil, err } err = delegations.Add(targets.Delegations.Roles, d.Delegatee.Name, delegationsDB) if err != nil { - return data.TargetFileMeta{}, err + return data.TargetFileMeta{}, nil, err } } } - return data.TargetFileMeta{}, ErrMaxDelegations{ + if len(delegationRole) > 0 { + return targetFileMeta, buildPath(delegations.Parent, delegationRole, ""), nil + } + + return data.TargetFileMeta{}, nil, ErrMaxDelegations{ Target: target, MaxDelegations: c.MaxDelegations, SnapshotVersion: snapshot.Version, } } +func buildPath(parent func(string) string, start string, end string) []string { + if start == end { + return nil + } + + path := []string{start} + current := start + for { + current = parent(current) + if current == end { + break + } + path = append(path, current) + } + return path +} + func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) { if err := c.getLocalMeta(); err != nil { return nil, err } - rawS, ok := c.localMeta["snapshot.json"] if !ok { return nil, ErrNoLocalSnapshot diff --git a/pkg/targets/delegation.go b/pkg/targets/delegation.go index ccd52bae..dce61710 100644 --- a/pkg/targets/delegation.go +++ b/pkg/targets/delegation.go @@ -18,6 +18,7 @@ type delegationsIterator struct { stack []Delegation target string visitedRoles map[string]struct{} + parents map[string]string } var ErrTopLevelTargetsRoleMissing = errors.New("tuf: top level targets role missing from top level keys DB") @@ -43,6 +44,7 @@ func NewDelegationsIterator(target string, topLevelKeysDB *verify.DB) (*delegati }, }, visitedRoles: make(map[string]struct{}), + parents: make(map[string]string), } return i, nil } @@ -88,8 +90,13 @@ func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string, DB: db, } d.stack = append(d.stack, delegation) + d.parents[r.Name] = delegator } } return nil } + +func (d *delegationsIterator) Parent(role string) string { + return d.parents[role] +}