Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 22 additions & 33 deletions client/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ func (c *Client) getTargetFileMeta(file string) (data.TargetFileMeta, error) {
return data.TargetFileMeta{}, err
}

verifiers := map[string]verify.DelegationsVerifier{
"root": {DB: c.db},
}
// verifiers is map of parent targets name to an associated DelegationsVerifier
// that can verify all child targets pointed by delegatedRoles in the parent targets
verifiers := make(map[string]verify.DelegationsVerifier)

// 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 file
// - 5.6.7.1 cycles protection
// - 5.6.7.2 terminations
delegations := newDelegationsIterator(c.rootTargetDelegation(), "root", file)
delegations := newDelegationsIterator(file)
for i := 0; i < c.MaxDelegations; i++ {
d, ok := delegations.next()
if !ok {
Expand Down Expand Up @@ -80,7 +80,7 @@ func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) {
return snapshot, nil
}

// loadDelegatedTargets downloads, decodes, verifies and stores delegated targets
// loadDelegatedTargets downloads, decodes, verifies and stores targets
func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, verifier verify.DelegationsVerifier) (*data.Targets, error) {
var err error
fileName := role + ".json"
Expand All @@ -102,9 +102,16 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, veri
target := &data.Targets{}
// 5.6.3 verify signature with parent public keys
// 5.6.5 verify that the targets is not expired
if err := verifier.Unmarshal(raw, target, role, fileMeta.Version); err != nil {
// role "targets" is the topTargets verified by root roles loaded in the client db
if role == "targets" {
err = c.db.Unmarshal(raw, target, role, fileMeta.Version)
} else {
err = verifier.Unmarshal(raw, target, role, fileMeta.Version)
}
if err != nil {
return nil, ErrDecodeFailed{fileName, err}
}

// 5.6.4 check against snapshot version
if target.Version != fileMeta.Version {
return nil, ErrTargetsSnapshotVersionMismatch{
Expand All @@ -123,27 +130,6 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, veri
return target, nil
}

func (c *Client) rootTargetDelegation() data.DelegatedRole {
role := "targets"
r := c.db.GetRole(role)
if r == nil {
return data.DelegatedRole{}
}

keyIDs := make([]string, 0, len(r.KeyIDs))
for id, _ := range r.KeyIDs {
keyIDs = append(keyIDs, id)
}

// root delegates the signing of all files to the top level targets
return data.DelegatedRole{
Name: role,
KeyIDs: keyIDs,
Threshold: r.Threshold,
PathHashPrefixes: []string{""},
}
}

type delegation struct {
parent string
child data.DelegatedRole
Expand All @@ -160,15 +146,18 @@ type delegationsIterator struct {
visited map[delegationID]struct{}
}

func newDelegationsIterator(role data.DelegatedRole, parent string, file string) *delegationsIterator {
// newDelegationsIterator initialises an iterator with a first step
// on top level targets
func newDelegationsIterator(file string) *delegationsIterator {
i := &delegationsIterator{
file: file,
stack: make([]delegation, 0, 1),
file: file,
stack: []delegation{
{
child: data.DelegatedRole{Name: "targets"},
},
},
visited: make(map[delegationID]struct{}),
}

i.add([]data.DelegatedRole{role}, parent)

return i
}

Expand Down
73 changes: 26 additions & 47 deletions client/delegations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,15 @@ var (

func TestDelegationsIterator(t *testing.T) {
var iteratorTests = []struct {
testName string
roles map[string][]data.DelegatedRole
rootDelegation data.DelegatedRole
file string
resultOrder []string
testName string
roles map[string][]data.DelegatedRole
file string
resultOrder []string
}{
{
testName: "no termination",
roles: map[string][]data.DelegatedRole{
"a": {
"targets": {
{Name: "b", Paths: defaultPathPatterns},
{Name: "e", Paths: defaultPathPatterns},
},
Expand All @@ -52,14 +51,13 @@ func TestDelegationsIterator(t *testing.T) {
{Name: "j", Paths: defaultPathPatterns},
},
},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
file: "",
resultOrder: []string{"targets", "b", "c", "d", "e", "f", "g", "h", "i", "j"},
},
{
testName: "terminated in b",
roles: map[string][]data.DelegatedRole{
"a": {
"targets": {
{Name: "b", Paths: defaultPathPatterns, Terminating: true},
{Name: "e", Paths: defaultPathPatterns},
},
Expand All @@ -68,14 +66,13 @@ func TestDelegationsIterator(t *testing.T) {
{Name: "d", Paths: defaultPathPatterns},
},
},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "c", "d"},
file: "",
resultOrder: []string{"targets", "b", "c", "d"},
},
{
testName: "path does not match b",
roles: map[string][]data.DelegatedRole{
"a": {
"targets": {
{Name: "b", Paths: noMatchPathPatterns},
{Name: "e", Paths: defaultPathPatterns},
},
Expand All @@ -84,50 +81,47 @@ func TestDelegationsIterator(t *testing.T) {
{Name: "d", Paths: defaultPathPatterns},
},
},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "e"},
file: "",
resultOrder: []string{"targets", "e"},
},
{
testName: "cycle avoided 1",
roles: map[string][]data.DelegatedRole{
"a": {
"targets": {
{Name: "b", Paths: defaultPathPatterns},
{Name: "e", Paths: defaultPathPatterns},
},
"b": {
{Name: "a", Paths: defaultPathPatterns},
{Name: "targets", Paths: defaultPathPatterns},
{Name: "d", Paths: defaultPathPatterns},
},
},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "a", "e", "d"},
file: "",
resultOrder: []string{"targets", "b", "targets", "e", "d"},
},
{
testName: "cycle avoided 2",
roles: map[string][]data.DelegatedRole{
"a": {
{Name: "a", Paths: defaultPathPatterns},
"targets": {
{Name: "targets", Paths: defaultPathPatterns},
{Name: "b", Paths: defaultPathPatterns},
},
"b": {
{Name: "a", Paths: defaultPathPatterns},
{Name: "targets", Paths: defaultPathPatterns},
{Name: "b", Paths: defaultPathPatterns},
{Name: "c", Paths: defaultPathPatterns},
},
"c": {
{Name: "c", Paths: defaultPathPatterns},
},
},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "a", "b", "a", "b", "c", "c"},
file: "",
resultOrder: []string{"targets", "targets", "b", "targets", "b", "c", "c"},
},
{
testName: "diamond delegation",
roles: map[string][]data.DelegatedRole{
"a": {
"targets": {
{Name: "b", Paths: defaultPathPatterns},
{Name: "c", Paths: defaultPathPatterns},
},
Expand All @@ -138,15 +132,14 @@ func TestDelegationsIterator(t *testing.T) {
{Name: "d", Paths: defaultPathPatterns},
},
},
rootDelegation: data.DelegatedRole{Name: "a", Paths: defaultPathPatterns},
file: "",
resultOrder: []string{"a", "b", "d", "c", "d"},
file: "",
resultOrder: []string{"targets", "b", "d", "c", "d"},
},
}

for _, tt := range iteratorTests {
t.Run(tt.testName, func(t *testing.T) {
d := newDelegationsIterator(tt.rootDelegation, "root", tt.file)
d := newDelegationsIterator(tt.file)
var iterationOrder []string
for {
r, ok := d.next()
Expand Down Expand Up @@ -234,20 +227,6 @@ func TestTargetsNotFound(t *testing.T) {
assert.Equal(t, ErrMissingRemoteMetadata{Name: "c.json"}, err)
}

func TestRootDelegationMatchesAll(t *testing.T) {
c := &Client{db: verify.NewDB()}
c.db.AddRole("targets", &data.Role{Threshold: 1})
d := c.rootTargetDelegation()

matchesPath, err := d.MatchesPath("a.txt")
assert.NoError(t, err)
assert.True(t, matchesPath)

matchesPath, err = d.MatchesPath("var/b//g")
assert.NoError(t, err)
assert.True(t, matchesPath)
}

func TestUnverifiedTargets(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
Expand Down
3 changes: 2 additions & 1 deletion data/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ func (d *DelegatedRole) MatchesPath(file string) (bool, error) {
return false, nil
}

// validateFields enforces the spec 1.0.19 section 4.5:
// validateFields enforces the spec
// https://theupdateframework.github.io/specification/v1.0.19/index.html#file-formats-targets
// 'role MUST specify only one of the "path_hash_prefixes" or "paths"'
// Marshalling and unmarshalling JSON will fail and return
// ErrPathsAndPathHashesSet if both fields are set and not empty.
Expand Down
2 changes: 1 addition & 1 deletion verify/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (d *DelegationsVerifier) Unmarshal(b []byte, v interface{}, role string, mi
func NewDelegationsVerifier(d *data.Delegations) (DelegationsVerifier, error) {
db := &DB{
roles: make(map[string]*Role, len(d.Roles)),
keys: make(map[string]*data.Key),
keys: make(map[string]*data.Key, len(d.Keys)),
}
for _, r := range d.Roles {
role := &data.Role{Threshold: r.Threshold, KeyIDs: r.KeyIDs}
Expand Down
5 changes: 3 additions & 2 deletions verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package verify

import (
"encoding/json"
"strings"
"time"

cjson "github.com/tent/canonical-json-go"
Expand All @@ -27,12 +28,12 @@ func (db *DB) Verify(s *data.Signed, role string, minVersion int) error {
if isTopLevelRole(role) {
// Top-level roles can only sign metadata of the same type (e.g. snapshot
// metadata must be signed by the snapshot role).
if sm.Type != role {
if strings.ToLower(sm.Type) != strings.ToLower(role) {
return ErrWrongMetaType
}
} else {
// Delegated (non-top-level) roles may only sign targets metadata.
if sm.Type != "targets" {
if strings.ToLower(sm.Type) != "targets" {
return ErrWrongMetaType
}
}
Expand Down