Skip to content

Commit

Permalink
upgrade(localMeta): Verify delegated targets
Browse files Browse the repository at this point in the history
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 <baptiste.foy@datadoghq.com>
  • Loading branch information
BaptisteFoy committed Dec 22, 2022
1 parent 3831342 commit c478700
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 11 deletions.
53 changes: 51 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"

"github.com/theupdateframework/go-tuf/data"
Expand Down Expand Up @@ -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
Expand All @@ -425,19 +426,67 @@ 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
}
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.
Expand Down
53 changes: 44 additions & 9 deletions client/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,64 +8,99 @@ 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
// - 5.6.7.1 cycles protection
// - 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
Expand Down
7 changes: 7 additions & 0 deletions pkg/targets/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
}
Expand Down Expand Up @@ -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]
}

0 comments on commit c478700

Please sign in to comment.