From c478700eeb00437817671352de8f3154e49dfc3a Mon Sep 17 00:00:00 2001 From: Baptiste Foy Date: Thu, 22 Dec 2022 09:45:46 +0100 Subject: [PATCH] upgrade(localMeta): Verify delegated targets Adds verification of delegated targets when they go from the local store to the localMeta structure. The verification is done by selecting one target file per delegated targets that verify at least one, then build the delegation path from the top targets to that specific delegated file. Building the path is already verifying every targets in the path, so the whole path is safe to add to the localMeta. Doing so for every delegated targets lets us verify every delegated targets that is used at least once. Signed-off-by: Baptiste Foy --- client/client.go | 53 +++++++++++++++++++++++++++++++++++++-- client/delegations.go | 53 ++++++++++++++++++++++++++++++++------- pkg/targets/delegation.go | 7 ++++++ 3 files changed, 102 insertions(+), 11 deletions(-) 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] +}