diff --git a/client/delegations.go b/client/delegations.go index f66a302a1..6b17df669 100644 --- a/client/delegations.go +++ b/client/delegations.go @@ -20,7 +20,7 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) { // - filter delegations with paths or path_hash_prefixes matching searched target // - 5.6.7.1 cycles protection // - 5.6.7.2 terminations - delegations := targets.NewDelegationsIterator(target) + delegations := targets.NewDelegationsIterator(target, c.db) for i := 0; i < c.MaxDelegations; i++ { d, ok := delegations.Next() if !ok { @@ -28,7 +28,7 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) { } // covers 5.6.{1,2,3,4,5,6} - targets, err := c.loadDelegatedTargets(snapshot, d.Delegatee.Name, d.Verifier) + targets, err := c.loadDelegatedTargets(snapshot, d.Delegatee.Name, d.DB) if err != nil { return data.TargetFileMeta{}, err } @@ -39,11 +39,11 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) { } if targets.Delegations != nil { - delegationsVerifier, err := verify.NewDelegationsVerifier(targets.Delegations) + delegationsDB, err := verify.NewDBFromDelegations(targets.Delegations) if err != nil { return data.TargetFileMeta{}, err } - err = delegations.Add(targets.Delegations.Roles, d.Delegatee.Name, delegationsVerifier) + err = delegations.Add(targets.Delegations.Roles, d.Delegatee.Name, delegationsDB) if err != nil { return data.TargetFileMeta{}, err } @@ -75,7 +75,7 @@ func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) { } // loadDelegatedTargets downloads, decodes, verifies and stores targets -func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, verifier verify.DelegationsVerifier) (*data.Targets, error) { +func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, db *verify.DB) (*data.Targets, error) { var err error fileName := role + ".json" fileMeta, ok := snapshot.Meta[fileName] @@ -98,11 +98,7 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, veri // 5.6.3 verify signature with parent public keys // 5.6.5 verify that the targets is not expired // role "targets" is a top role verified by root keys loaded in the client db - if role == "targets" { - err = c.db.Unmarshal(raw, targets, role, fileMeta.Version) - } else { - err = verifier.Unmarshal(raw, targets, role, fileMeta.Version) - } + err = db.Unmarshal(raw, targets, role, fileMeta.Version) if err != nil { return nil, ErrDecodeFailed{fileName, err} } diff --git a/internal/targets/delegation.go b/internal/targets/delegation.go index 8e09c05c2..3823df4ea 100644 --- a/internal/targets/delegation.go +++ b/internal/targets/delegation.go @@ -2,13 +2,14 @@ package targets import ( "github.com/theupdateframework/go-tuf/data" + "github.com/theupdateframework/go-tuf/internal/sets" "github.com/theupdateframework/go-tuf/verify" ) type Delegation struct { Delegator string - Verifier verify.DelegationsVerifier Delegatee data.DelegatedRole + DB *verify.DB } type delegationsIterator struct { @@ -18,13 +19,23 @@ type delegationsIterator struct { } // NewDelegationsIterator initialises an iterator with a first step -// on top level targets -func NewDelegationsIterator(target string) *delegationsIterator { +// on top level targets. +func NewDelegationsIterator(target string, topLevelKeysDB *verify.DB) *delegationsIterator { + role := topLevelKeysDB.GetRole("targets") + keyIDs := []string{} + if role != nil { + keyIDs = sets.StringSetToSlice(role.KeyIDs) + } + i := &delegationsIterator{ target: target, stack: []Delegation{ { - Delegatee: data.DelegatedRole{Name: "targets"}, + Delegatee: data.DelegatedRole{ + Name: "targets", + KeyIDs: keyIDs, + }, + DB: topLevelKeysDB, }, }, visitedRoles: make(map[string]struct{}), @@ -57,7 +68,7 @@ func (d *delegationsIterator) Next() (value Delegation, ok bool) { return delegation, true } -func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string, verifier verify.DelegationsVerifier) error { +func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string, db *verify.DB) error { for i := len(roles) - 1; i >= 0; i-- { // Push the roles onto the stack in reverse so we get an preorder traversal // of the delegations graph. @@ -70,7 +81,7 @@ func (d *delegationsIterator) Add(roles []data.DelegatedRole, delegator string, delegation := Delegation{ Delegator: delegator, Delegatee: r, - Verifier: verifier, + DB: db, } d.stack = append(d.stack, delegation) } diff --git a/internal/targets/delegation_test.go b/internal/targets/delegation_test.go index bb460b180..f157c077c 100644 --- a/internal/targets/delegation_test.go +++ b/internal/targets/delegation_test.go @@ -14,6 +14,7 @@ var ( ) func TestDelegationsIterator(t *testing.T) { + defaultKeyIDs := []string{"26b878ad73362774b8b69dd4fdeb2cc6a2688e4133ed5ace9e18a06e9d998a6d"} var iteratorTests = []struct { testName string roles map[string][]data.DelegatedRole @@ -25,23 +26,23 @@ func TestDelegationsIterator(t *testing.T) { testName: "no termination", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", Paths: defaultPathPatterns}, - {Name: "e", Paths: defaultPathPatterns}, + {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": { - {Name: "c", Paths: defaultPathPatterns}, + {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "c": { - {Name: "d", Paths: defaultPathPatterns}, + {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "e": { - {Name: "f", Paths: defaultPathPatterns}, - {Name: "g", Paths: defaultPathPatterns}, + {Name: "f", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "g", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "g": { - {Name: "h", Paths: defaultPathPatterns}, - {Name: "i", Paths: defaultPathPatterns}, - {Name: "j", Paths: defaultPathPatterns}, + {Name: "h", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "i", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "j", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", @@ -51,12 +52,12 @@ func TestDelegationsIterator(t *testing.T) { testName: "terminated in b", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", Paths: defaultPathPatterns, Terminating: true}, - {Name: "e", Paths: defaultPathPatterns}, + {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs, Terminating: true}, + {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": { - {Name: "c", Paths: defaultPathPatterns}, - {Name: "d", Paths: defaultPathPatterns}, + {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", @@ -66,12 +67,12 @@ func TestDelegationsIterator(t *testing.T) { testName: "path does not match b", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", Paths: noMatchPathPatterns}, - {Name: "e", Paths: defaultPathPatterns}, + {Name: "b", Paths: noMatchPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": { - {Name: "c", Paths: defaultPathPatterns}, - {Name: "d", Paths: defaultPathPatterns}, + {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", @@ -81,12 +82,13 @@ func TestDelegationsIterator(t *testing.T) { testName: "path does not match b - path prefixes", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", PathHashPrefixes: []string{"33472a4909"}}, - {Name: "c", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633"}}, + {Name: "b", PathHashPrefixes: []string{"33472a4909"}, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "c", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633"}, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "c": { - {Name: "d", PathHashPrefixes: []string{"8baf"}}, - {Name: "e", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633472a49096ed87f8f764bd597831eac371f40ac39"}}, + + {Name: "d", PathHashPrefixes: []string{"8baf"}, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "e", PathHashPrefixes: []string{"34c85d1ee84f61f10d7dc633472a49096ed87f8f764bd597831eac371f40ac39"}, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "/e/f/g.txt", @@ -96,7 +98,7 @@ func TestDelegationsIterator(t *testing.T) { testName: "err paths and pathHashPrefixes are set", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", Paths: defaultPathPatterns, PathHashPrefixes: defaultPathPatterns}, + {Name: "b", Paths: defaultPathPatterns, PathHashPrefixes: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": {}, }, @@ -108,48 +110,54 @@ func TestDelegationsIterator(t *testing.T) { testName: "cycle avoided 1", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", Paths: defaultPathPatterns}, - {Name: "e", Paths: defaultPathPatterns}, + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + }, + "a": { + {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "e", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": { - {Name: "targets", Paths: defaultPathPatterns}, - {Name: "d", Paths: defaultPathPatterns}, + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", - resultOrder: []string{"targets", "b", "d", "e"}, + resultOrder: []string{"targets", "a", "b", "d", "e"}, }, { testName: "cycle avoided 2", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "targets", Paths: defaultPathPatterns}, - {Name: "b", Paths: defaultPathPatterns}, + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + }, + "a": { + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": { - {Name: "targets", Paths: defaultPathPatterns}, - {Name: "b", Paths: defaultPathPatterns}, - {Name: "c", Paths: defaultPathPatterns}, + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "c": { - {Name: "c", Paths: defaultPathPatterns}, + {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", - resultOrder: []string{"targets", "b", "c"}, + resultOrder: []string{"targets", "a", "b", "c"}, }, { testName: "diamond delegation", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "b", Paths: defaultPathPatterns}, - {Name: "c", Paths: defaultPathPatterns}, + {Name: "b", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, + {Name: "c", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "b": { - {Name: "d", Paths: defaultPathPatterns}, + {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "c": { - {Name: "d", Paths: defaultPathPatterns}, + {Name: "d", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", @@ -159,10 +167,10 @@ func TestDelegationsIterator(t *testing.T) { testName: "simple cycle", roles: map[string][]data.DelegatedRole{ "targets": { - {Name: "a", Paths: defaultPathPatterns}, + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, "a": { - {Name: "a", Paths: defaultPathPatterns}, + {Name: "a", Paths: defaultPathPatterns, Threshold: 1, KeyIDs: defaultKeyIDs}, }, }, file: "", @@ -172,7 +180,17 @@ func TestDelegationsIterator(t *testing.T) { for _, tt := range iteratorTests { t.Run(tt.testName, func(t *testing.T) { - d := NewDelegationsIterator(tt.file) + flattened := []data.DelegatedRole{} + for _, roles := range tt.roles { + flattened = append(flattened, roles...) + } + db, err := verify.NewDBFromDelegations(&data.Delegations{ + Roles: flattened, + }) + + assert.NoError(t, err) + d := NewDelegationsIterator(tt.file, db) + var iterationOrder []string for { r, ok := d.Next() @@ -184,7 +202,13 @@ func TestDelegationsIterator(t *testing.T) { if !ok { continue } - err := d.Add(delegations, r.Delegatee.Name, verify.DelegationsVerifier{}) + + db, err := verify.NewDBFromDelegations(&data.Delegations{ + Roles: delegations, + }) + assert.NoError(t, err) + + err = d.Add(delegations, r.Delegatee.Name, db) assert.Equal(t, tt.err, err) } assert.Equal(t, tt.resultOrder, iterationOrder) diff --git a/verify/db.go b/verify/db.go index f80949042..a14a51493 100644 --- a/verify/db.go +++ b/verify/db.go @@ -28,37 +28,28 @@ func NewDB() *DB { } } -type DelegationsVerifier struct { - DB *DB -} - -func (d *DelegationsVerifier) Unmarshal(b []byte, v interface{}, role string, minVersion int) error { - return d.DB.Unmarshal(b, v, role, minVersion) -} - -// NewDelegationsVerifier returns a DelegationsVerifier that verifies delegations -// of a given Targets. It reuses the DB struct to leverage verified keys, roles -// unmarshals. -func NewDelegationsVerifier(d *data.Delegations) (DelegationsVerifier, error) { +// NewDBFromDelegations returns a DB that verifies delegations +// of a given Targets. +func NewDBFromDelegations(d *data.Delegations) (*DB, error) { db := &DB{ roles: make(map[string]*Role, len(d.Roles)), verifiers: make(map[string]keys.Verifier, len(d.Keys)), } for _, r := range d.Roles { if _, ok := roles.TopLevelRoles[r.Name]; ok { - return DelegationsVerifier{}, ErrInvalidDelegatedRole + return nil, ErrInvalidDelegatedRole } role := &data.Role{Threshold: r.Threshold, KeyIDs: r.KeyIDs} if err := db.addRole(r.Name, role); err != nil { - return DelegationsVerifier{}, err + return nil, err } } for id, k := range d.Keys { if err := db.AddKey(id, k); err != nil { - return DelegationsVerifier{}, err + return nil, err } } - return DelegationsVerifier{db}, nil + return db, nil } func (db *DB) AddKey(id string, k *data.PublicKey) error { diff --git a/verify/db_test.go b/verify/db_test.go index cb3289813..e4bab0a33 100644 --- a/verify/db_test.go +++ b/verify/db_test.go @@ -7,8 +7,8 @@ import ( "github.com/theupdateframework/go-tuf/data" ) -func TestDelegationsVerifier(t *testing.T) { - var verifierTests = []struct { +func TestDelegationsDB(t *testing.T) { + var dbTests = []struct { testName string delegations *data.Delegations initErr error @@ -42,14 +42,14 @@ func TestDelegationsVerifier(t *testing.T) { }, } - for _, tt := range verifierTests { + for _, tt := range dbTests { t.Run(tt.testName, func(t *testing.T) { - verifier, err := NewDelegationsVerifier(tt.delegations) - assert.NotNil(t, verifier) + db, err := NewDBFromDelegations(tt.delegations) assert.Equal(t, tt.initErr, err) if err == nil { + assert.NotNil(t, db) var targets data.Targets - err = verifier.Unmarshal([]byte(`{"a":"b"}`), targets, "tree", 0) + err = db.Unmarshal([]byte(`{"a":"b"}`), targets, "tree", 0) assert.Equal(t, tt.unmarshalErr, err) } })