Skip to content

Commit d85e0a2

Browse files
[Delegations prereq 7] Make signers addressible by key ID in LocalStore (#197)
* [Delegations prereq] Use a verify.DB for delegation in client Splitting up #175 * stash * Add tests to make sure the top level targets 'delegation' edge has associated keys. Make NewDelegationsIterator return an error if the passed DB is missing the top level targets role * [Delegations prereq] Make signers addressible by key ID in LocalStore Splitting up #175 * Clarify naming * Add local_store_test.go * Another test case
1 parent 314eed4 commit d85e0a2

File tree

4 files changed

+240
-73
lines changed

4 files changed

+240
-73
lines changed

client/delegations.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (c *Client) getTargetFileMeta(target string) (data.TargetFileMeta, error) {
3232
}
3333

3434
// covers 5.6.{1,2,3,4,5,6}
35-
targets, err := c.loadDelegatedTargets(snapshot, d)
35+
targets, err := c.loadDelegatedTargets(snapshot, d.Delegatee.Name, d.DB)
3636
if err != nil {
3737
return data.TargetFileMeta{}, err
3838
}
@@ -79,9 +79,7 @@ func (c *Client) loadLocalSnapshot() (*data.Snapshot, error) {
7979
}
8080

8181
// loadDelegatedTargets downloads, decodes, verifies and stores targets
82-
func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, delegation targets.Delegation) (*data.Targets, error) {
83-
role := delegation.Delegatee.Name
84-
82+
func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, role string, db *verify.DB) (*data.Targets, error) {
8583
var err error
8684
fileName := role + ".json"
8785
fileMeta, ok := snapshot.Meta[fileName]
@@ -104,7 +102,7 @@ func (c *Client) loadDelegatedTargets(snapshot *data.Snapshot, delegation target
104102
// 5.6.3 verify signature with parent public keys
105103
// 5.6.5 verify that the targets is not expired
106104
// role "targets" is a top role verified by root keys loaded in the client db
107-
err = delegation.DB.Unmarshal(raw, targets, role, fileMeta.Version)
105+
err = db.Unmarshal(raw, targets, role, fileMeta.Version)
108106
if err != nil {
109107
return nil, ErrDecodeFailed{fileName, err}
110108
}

local_store.go

Lines changed: 167 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,71 @@ import (
1212

1313
"github.com/theupdateframework/go-tuf/data"
1414
"github.com/theupdateframework/go-tuf/encrypted"
15+
"github.com/theupdateframework/go-tuf/internal/sets"
1516
"github.com/theupdateframework/go-tuf/pkg/keys"
1617
"github.com/theupdateframework/go-tuf/util"
1718
)
1819

19-
func signers(privateKeys []*data.PrivateKey) []keys.Signer {
20-
res := make([]keys.Signer, 0, len(privateKeys))
21-
for _, k := range privateKeys {
22-
signer, err := keys.GetSigner(k)
23-
if err != nil {
24-
continue
25-
}
26-
res = append(res, signer)
27-
}
28-
return res
20+
type LocalStore interface {
21+
// GetMeta returns a map from metadata file names (e.g. root.json) to their raw JSON payload or an error.
22+
GetMeta() (map[string]json.RawMessage, error)
23+
24+
// SetMeta is used to update a metadata file name with a JSON payload.
25+
SetMeta(name string, meta json.RawMessage) error
26+
27+
// WalkStagedTargets calls targetsFn for each staged target file in paths.
28+
// If paths is empty, all staged target files will be walked.
29+
WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error
30+
31+
// FileIsStaged determines if a metadata file is currently staged, to avoid incrementing
32+
// version numbers repeatedly while staged.
33+
FileIsStaged(filename string) bool
34+
35+
// Commit is used to publish staged files to the repository
36+
//
37+
// This will also reset the staged meta to signal incrementing version numbers.
38+
// TUF 1.0 requires that the root metadata version numbers in the repository does not
39+
// gaps. To avoid this, we will only increment the number once until we commit.
40+
Commit(bool, map[string]int, map[string]data.Hashes) error
41+
42+
// GetSigners return a list of signers for a role.
43+
GetSigners(role string) ([]keys.Signer, error)
44+
45+
// SaveSigner adds a signer to a role.
46+
SaveSigner(role string, signer keys.Signer) error
47+
48+
// SignersForRole return a list of signing keys for a role.
49+
SignersForKeyIDs(keyIDs []string) []keys.Signer
50+
51+
// Clean is used to remove all staged manifests.
52+
Clean() error
53+
}
54+
55+
type PassphraseChanger interface {
56+
// ChangePassphrase changes the passphrase for a role keys file.
57+
ChangePassphrase(string) error
2958
}
3059

3160
func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) LocalStore {
3261
if meta == nil {
3362
meta = make(map[string]json.RawMessage)
3463
}
3564
return &memoryStore{
36-
meta: meta,
37-
stagedMeta: make(map[string]json.RawMessage),
38-
files: files,
39-
signers: make(map[string][]keys.Signer),
65+
meta: meta,
66+
stagedMeta: make(map[string]json.RawMessage),
67+
files: files,
68+
signerForKeyID: make(map[string]keys.Signer),
69+
keyIDsForRole: make(map[string][]string),
4070
}
4171
}
4272

4373
type memoryStore struct {
4474
meta map[string]json.RawMessage
4575
stagedMeta map[string]json.RawMessage
4676
files map[string][]byte
47-
signers map[string][]keys.Signer
77+
78+
signerForKeyID map[string]keys.Signer
79+
keyIDsForRole map[string][]string
4880
}
4981

5082
func (m *memoryStore) GetMeta() (map[string]json.RawMessage, error) {
@@ -105,14 +137,53 @@ func (m *memoryStore) Commit(consistentSnapshot bool, versions map[string]int, h
105137
}
106138

107139
func (m *memoryStore) GetSigners(role string) ([]keys.Signer, error) {
108-
return m.signers[role], nil
140+
keyIDs, ok := m.keyIDsForRole[role]
141+
if ok {
142+
return m.SignersForKeyIDs(keyIDs), nil
143+
}
144+
145+
return nil, nil
109146
}
110147

111148
func (m *memoryStore) SaveSigner(role string, signer keys.Signer) error {
112-
m.signers[role] = append(m.signers[role], signer)
149+
keyIDs := signer.PublicData().IDs()
150+
151+
for _, keyID := range keyIDs {
152+
m.signerForKeyID[keyID] = signer
153+
}
154+
155+
mergedKeyIDs := sets.DeduplicateStrings(append(m.keyIDsForRole[role], keyIDs...))
156+
m.keyIDsForRole[role] = mergedKeyIDs
113157
return nil
114158
}
115159

160+
func (m *memoryStore) SignersForKeyIDs(keyIDs []string) []keys.Signer {
161+
signers := []keys.Signer{}
162+
keyIDsSeen := map[string]struct{}{}
163+
164+
for _, keyID := range keyIDs {
165+
signer, ok := m.signerForKeyID[keyID]
166+
if !ok {
167+
continue
168+
}
169+
addSigner := false
170+
171+
for _, skid := range signer.PublicData().IDs() {
172+
if _, seen := keyIDsSeen[skid]; !seen {
173+
addSigner = true
174+
}
175+
176+
keyIDsSeen[skid] = struct{}{}
177+
}
178+
179+
if addSigner {
180+
signers = append(signers, signer)
181+
}
182+
}
183+
184+
return signers
185+
}
186+
116187
func (m *memoryStore) Clean() error {
117188
return nil
118189
}
@@ -126,16 +197,17 @@ func FileSystemStore(dir string, p util.PassphraseFunc) LocalStore {
126197
return &fileSystemStore{
127198
dir: dir,
128199
passphraseFunc: p,
129-
signers: make(map[string][]keys.Signer),
200+
signerForKeyID: make(map[string]keys.Signer),
201+
keyIDsForRole: make(map[string][]string),
130202
}
131203
}
132204

133205
type fileSystemStore struct {
134206
dir string
135207
passphraseFunc util.PassphraseFunc
136208

137-
// signers is a cache of persisted keys to avoid decrypting multiple times
138-
signers map[string][]keys.Signer
209+
signerForKeyID map[string]keys.Signer
210+
keyIDsForRole map[string][]string
139211
}
140212

141213
func (f *fileSystemStore) repoDir() string {
@@ -333,18 +405,63 @@ func (f *fileSystemStore) Commit(consistentSnapshot bool, versions map[string]in
333405
}
334406

335407
func (f *fileSystemStore) GetSigners(role string) ([]keys.Signer, error) {
336-
if keys, ok := f.signers[role]; ok {
337-
return keys, nil
408+
keyIDs, ok := f.keyIDsForRole[role]
409+
if ok {
410+
return f.SignersForKeyIDs(keyIDs), nil
338411
}
339-
keys, _, err := f.loadPrivateKeys(role)
412+
413+
privKeys, _, err := f.loadPrivateKeys(role)
340414
if err != nil {
341415
if os.IsNotExist(err) {
342416
return nil, nil
343417
}
344418
return nil, err
345419
}
346-
f.signers[role] = signers(keys)
347-
return f.signers[role], nil
420+
421+
signers := []keys.Signer{}
422+
for _, key := range privKeys {
423+
signer, err := keys.GetSigner(key)
424+
if err != nil {
425+
return nil, err
426+
}
427+
428+
// Cache the signers.
429+
for _, keyID := range signer.PublicData().IDs() {
430+
f.keyIDsForRole[role] = append(f.keyIDsForRole[role], keyID)
431+
f.signerForKeyID[keyID] = signer
432+
}
433+
signers = append(signers, signer)
434+
}
435+
436+
return signers, nil
437+
}
438+
439+
func (f *fileSystemStore) SignersForKeyIDs(keyIDs []string) []keys.Signer {
440+
signers := []keys.Signer{}
441+
keyIDsSeen := map[string]struct{}{}
442+
443+
for _, keyID := range keyIDs {
444+
signer, ok := f.signerForKeyID[keyID]
445+
if !ok {
446+
continue
447+
}
448+
449+
addSigner := false
450+
451+
for _, skid := range signer.PublicData().IDs() {
452+
if _, seen := keyIDsSeen[skid]; !seen {
453+
addSigner = true
454+
}
455+
456+
keyIDsSeen[skid] = struct{}{}
457+
}
458+
459+
if addSigner {
460+
signers = append(signers, signer)
461+
}
462+
}
463+
464+
return signers
348465
}
349466

350467
// ChangePassphrase changes the passphrase for a role keys file. Implements
@@ -391,15 +508,15 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
391508
}
392509

393510
// add the key to the existing keys (if any)
394-
keys, pass, err := f.loadPrivateKeys(role)
511+
privKeys, pass, err := f.loadPrivateKeys(role)
395512
if err != nil && !os.IsNotExist(err) {
396513
return err
397514
}
398515
key, err := signer.MarshalPrivateKey()
399516
if err != nil {
400517
return err
401518
}
402-
keys = append(keys, key)
519+
privKeys = append(privKeys, key)
403520

404521
// if loadPrivateKeys didn't return a passphrase (because no keys yet exist)
405522
// and passphraseFunc is set, get the passphrase so the keys file can
@@ -414,13 +531,13 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
414531

415532
pk := &persistedKeys{}
416533
if pass != nil {
417-
pk.Data, err = encrypted.Marshal(keys, pass)
534+
pk.Data, err = encrypted.Marshal(privKeys, pass)
418535
if err != nil {
419536
return err
420537
}
421538
pk.Encrypted = true
422539
} else {
423-
pk.Data, err = json.MarshalIndent(keys, "", "\t")
540+
pk.Data, err = json.MarshalIndent(privKeys, "", "\t")
424541
if err != nil {
425542
return err
426543
}
@@ -432,7 +549,27 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
432549
if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
433550
return err
434551
}
435-
f.signers[role] = append(f.signers[role], signer)
552+
553+
// Merge privKeys into f.keyIDsForRole and register signers with
554+
// f.signerForKeyID.
555+
keyIDsForRole := f.keyIDsForRole[role]
556+
for _, key := range privKeys {
557+
signer, err := keys.GetSigner(key)
558+
if err != nil {
559+
return err
560+
}
561+
562+
keyIDs := signer.PublicData().IDs()
563+
564+
for _, keyID := range keyIDs {
565+
f.signerForKeyID[keyID] = signer
566+
}
567+
568+
keyIDsForRole = append(keyIDsForRole, keyIDs...)
569+
}
570+
571+
f.keyIDsForRole[role] = sets.DeduplicateStrings(keyIDsForRole)
572+
436573
return nil
437574
}
438575

local_store_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package tuf
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/theupdateframework/go-tuf/pkg/keys"
9+
)
10+
11+
func TestLocalStoreSigners(t *testing.T) {
12+
tmpdir, err := os.MkdirTemp("", "")
13+
if err != nil {
14+
t.Fatal(err)
15+
}
16+
defer func() {
17+
os.RemoveAll(tmpdir)
18+
}()
19+
20+
stores := map[string]LocalStore{
21+
"MemoryStore": MemoryStore(nil, nil),
22+
"FileSystemStore": FileSystemStore(tmpdir, nil),
23+
}
24+
25+
for name, store := range stores {
26+
t.Run(name, func(t *testing.T) {
27+
signers, err := store.GetSigners("abc")
28+
assert.NoError(t, err)
29+
assert.Equal(t, len(signers), 0)
30+
31+
// Add two signers to role "a".
32+
aSigner1, err := keys.GenerateEd25519Key()
33+
assert.NoError(t, err)
34+
err = store.SaveSigner("a", aSigner1)
35+
assert.NoError(t, err)
36+
37+
aSigner2, err := keys.GenerateEd25519Key()
38+
assert.NoError(t, err)
39+
err = store.SaveSigner("a", aSigner2)
40+
assert.NoError(t, err)
41+
42+
// Add one signer to role "b".
43+
bSigner, err := keys.GenerateEd25519Key()
44+
assert.NoError(t, err)
45+
err = store.SaveSigner("b", bSigner)
46+
assert.NoError(t, err)
47+
48+
// Add to b again to test deduplication.
49+
err = store.SaveSigner("b", bSigner)
50+
assert.NoError(t, err)
51+
52+
signers, err = store.GetSigners("a")
53+
assert.NoError(t, err)
54+
assert.ElementsMatch(t, []keys.Signer{aSigner1, aSigner2}, signers)
55+
56+
signers, err = store.GetSigners("b")
57+
assert.NoError(t, err)
58+
assert.ElementsMatch(t, []keys.Signer{bSigner}, signers)
59+
60+
a1KeyIDs := aSigner1.PublicData().IDs()
61+
a2KeyIDs := aSigner2.PublicData().IDs()
62+
bKeyIDs := bSigner.PublicData().IDs()
63+
64+
assert.Equal(t, []keys.Signer{aSigner1}, store.SignersForKeyIDs(a1KeyIDs))
65+
assert.Equal(t, []keys.Signer{aSigner2}, store.SignersForKeyIDs(a2KeyIDs))
66+
assert.ElementsMatch(t, []keys.Signer{aSigner1, aSigner2}, store.SignersForKeyIDs(append(a1KeyIDs, a2KeyIDs...)))
67+
assert.Equal(t, []keys.Signer{bSigner}, store.SignersForKeyIDs(bKeyIDs))
68+
})
69+
}
70+
}

0 commit comments

Comments
 (0)