diff --git a/client/delegations.go b/client/delegations.go index 38a011231..464f83c28 100644 --- a/client/delegations.go +++ b/client/delegations.go @@ -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 { @@ -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" @@ -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{ @@ -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 @@ -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 } diff --git a/client/delegations_test.go b/client/delegations_test.go index 33c4c32ac..2bbeb8c58 100644 --- a/client/delegations_test.go +++ b/client/delegations_test.go @@ -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}, }, @@ -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}, }, @@ -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}, }, @@ -84,35 +81,33 @@ 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}, }, @@ -120,14 +115,13 @@ func TestDelegationsIterator(t *testing.T) { {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}, }, @@ -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() @@ -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") diff --git a/data/types.go b/data/types.go index 4ccc17029..12d0ca25c 100644 --- a/data/types.go +++ b/data/types.go @@ -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. diff --git a/verify/db.go b/verify/db.go index f8562c95e..1565f99b6 100644 --- a/verify/db.go +++ b/verify/db.go @@ -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} diff --git a/verify/verify.go b/verify/verify.go index 3e9c10fd3..b6d4162d5 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -2,6 +2,7 @@ package verify import ( "encoding/json" + "strings" "time" cjson "github.com/tent/canonical-json-go" @@ -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 } }