From 84c43e5a7b5a05cc747275dff24872bda4356495 Mon Sep 17 00:00:00 2001 From: Ethan Lowman Date: Fri, 10 Dec 2021 17:48:28 -0500 Subject: [PATCH] [Delegations prereq] Make signers addressible by key ID in LocalStore Splitting up https://github.com/theupdateframework/go-tuf/pull/175 --- local_store.go | 188 +++++++++++++++++++++++++++++++++++++++++-------- repo.go | 30 -------- 2 files changed, 158 insertions(+), 60 deletions(-) diff --git a/local_store.go b/local_store.go index 786a0e76..ff3e32db 100644 --- a/local_store.go +++ b/local_store.go @@ -12,20 +12,41 @@ import ( "github.com/theupdateframework/go-tuf/data" "github.com/theupdateframework/go-tuf/encrypted" + "github.com/theupdateframework/go-tuf/internal/sets" "github.com/theupdateframework/go-tuf/pkg/keys" "github.com/theupdateframework/go-tuf/util" ) -func signers(privateKeys []*data.PrivateKey) []keys.Signer { - res := make([]keys.Signer, 0, len(privateKeys)) - for _, k := range privateKeys { - signer, err := keys.GetSigner(k) - if err != nil { - continue - } - res = append(res, signer) - } - return res +type LocalStore interface { + // GetMeta returns a map from metadata file names (e.g. root.json) to their raw JSON payload or an error. + GetMeta() (map[string]json.RawMessage, error) + + // SetMeta is used to update a metadata file name with a JSON payload. + SetMeta(name string, meta json.RawMessage) error + + // WalkStagedTargets calls targetsFn for each staged target file in paths. + // If paths is empty, all staged target files will be walked. + WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error + + // Commit is used to publish staged files to the repository + Commit(consistentSnapshot bool, versions map[string]int, hashes map[string]data.Hashes) error + + // GetSigners return a list of signers for a role. + GetSigners(role string) ([]keys.Signer, error) + + // SaveSigner adds a signer to a role. + SaveSigner(role string, signer keys.Signer) error + + // SignersForRole return a list of signing keys for a role. + SignersForKeyIDs(keyIDs []string) []keys.Signer + + // Clean is used to remove all staged manifests. + Clean() error +} + +type PassphraseChanger interface { + // ChangePassphrase changes the passphrase for a role keys file. + ChangePassphrase(string) error } func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) LocalStore { @@ -33,10 +54,11 @@ func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) Local meta = make(map[string]json.RawMessage) } return &memoryStore{ - meta: meta, - stagedMeta: make(map[string]json.RawMessage), - files: files, - signers: make(map[string][]keys.Signer), + meta: meta, + stagedMeta: make(map[string]json.RawMessage), + files: files, + signerForKeyID: make(map[string]keys.Signer), + keyIDsForRole: make(map[string][]string), } } @@ -44,7 +66,9 @@ type memoryStore struct { meta map[string]json.RawMessage stagedMeta map[string]json.RawMessage files map[string][]byte - signers map[string][]keys.Signer + + signerForKeyID map[string]keys.Signer + keyIDsForRole map[string][]string } func (m *memoryStore) GetMeta() (map[string]json.RawMessage, error) { @@ -96,14 +120,53 @@ func (m *memoryStore) Commit(consistentSnapshot bool, versions map[string]int, h } func (m *memoryStore) GetSigners(role string) ([]keys.Signer, error) { - return m.signers[role], nil + keyIDs, ok := m.keyIDsForRole[role] + if ok { + return m.SignersForKeyIDs(keyIDs), nil + } + + return nil, nil } func (m *memoryStore) SaveSigner(role string, signer keys.Signer) error { - m.signers[role] = append(m.signers[role], signer) + keyIDs := signer.PublicData().IDs() + + for _, keyID := range keyIDs { + m.signerForKeyID[keyID] = signer + } + + mergedKeyIDs := sets.DeduplicateStrings(append(m.keyIDsForRole[role], keyIDs...)) + m.keyIDsForRole[role] = mergedKeyIDs return nil } +func (m *memoryStore) SignersForKeyIDs(keyIDs []string) []keys.Signer { + signers := []keys.Signer{} + keyIDsSeen := map[string]struct{}{} + + for _, keyID := range keyIDs { + signer, ok := m.signerForKeyID[keyID] + if !ok { + continue + } + addSigner := false + + for _, skid := range signer.PublicData().IDs() { + if _, seen := keyIDsSeen[skid]; !seen { + addSigner = true + } + + keyIDsSeen[skid] = struct{}{} + } + + if addSigner { + signers = append(signers, signer) + } + } + + return signers +} + func (m *memoryStore) Clean() error { return nil } @@ -117,7 +180,8 @@ func FileSystemStore(dir string, p util.PassphraseFunc) LocalStore { return &fileSystemStore{ dir: dir, passphraseFunc: p, - signers: make(map[string][]keys.Signer), + signerForKeyID: make(map[string]keys.Signer), + keyIDsForRole: make(map[string][]string), } } @@ -125,8 +189,8 @@ type fileSystemStore struct { dir string passphraseFunc util.PassphraseFunc - // signers is a cache of persisted keys to avoid decrypting multiple times - signers map[string][]keys.Signer + signerForKeyID map[string]keys.Signer + keyIDsForRole map[string][]string } func (f *fileSystemStore) repoDir() string { @@ -319,18 +383,63 @@ func (f *fileSystemStore) Commit(consistentSnapshot bool, versions map[string]in } func (f *fileSystemStore) GetSigners(role string) ([]keys.Signer, error) { - if keys, ok := f.signers[role]; ok { - return keys, nil + keyIDs, ok := f.keyIDsForRole[role] + if ok { + return f.SignersForKeyIDs(keyIDs), nil } - keys, _, err := f.loadPrivateKeys(role) + + privKeys, _, err := f.loadPrivateKeys(role) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } - f.signers[role] = signers(keys) - return f.signers[role], nil + + signers := []keys.Signer{} + for _, key := range privKeys { + signer, err := keys.GetSigner(key) + if err != nil { + return nil, err + } + + // Cache the signers. + for _, keyID := range signer.PublicData().IDs() { + f.keyIDsForRole[role] = append(f.keyIDsForRole[role], keyID) + f.signerForKeyID[keyID] = signer + } + signers = append(signers, signer) + } + + return signers, nil +} + +func (f *fileSystemStore) SignersForKeyIDs(keyIDs []string) []keys.Signer { + signers := []keys.Signer{} + keyIDsSeen := map[string]struct{}{} + + for _, keyID := range keyIDs { + signer, ok := f.signerForKeyID[keyID] + if !ok { + continue + } + + addSigner := false + + for _, skid := range signer.PublicData().IDs() { + if _, seen := keyIDsSeen[skid]; !seen { + addSigner = true + } + + keyIDsSeen[skid] = struct{}{} + } + + if addSigner { + signers = append(signers, signer) + } + } + + return signers } // ChangePassphrase changes the passphrase for a role keys file. Implements @@ -377,7 +486,7 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error { } // add the key to the existing keys (if any) - keys, pass, err := f.loadPrivateKeys(role) + privKeys, pass, err := f.loadPrivateKeys(role) if err != nil && !os.IsNotExist(err) { return err } @@ -385,7 +494,7 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error { if err != nil { return err } - keys = append(keys, key) + privKeys = append(privKeys, key) // if loadPrivateKeys didn't return a passphrase (because no keys yet exist) // and passphraseFunc is set, get the passphrase so the keys file can @@ -400,13 +509,13 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error { pk := &persistedKeys{} if pass != nil { - pk.Data, err = encrypted.Marshal(keys, pass) + pk.Data, err = encrypted.Marshal(privKeys, pass) if err != nil { return err } pk.Encrypted = true } else { - pk.Data, err = json.MarshalIndent(keys, "", "\t") + pk.Data, err = json.MarshalIndent(privKeys, "", "\t") if err != nil { return err } @@ -418,7 +527,26 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error { if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil { return err } - f.signers[role] = append(f.signers[role], signer) + + innerKeyIdsForRole := f.keyIDsForRole[role] + + for _, key := range privKeys { + signer, err := keys.GetSigner(key) + if err != nil { + return err + } + + keyIDs := signer.PublicData().IDs() + + for _, keyID := range keyIDs { + f.signerForKeyID[keyID] = signer + } + + innerKeyIdsForRole = append(innerKeyIdsForRole, keyIDs...) + } + + f.keyIDsForRole[role] = sets.DeduplicateStrings(innerKeyIdsForRole) + return nil } diff --git a/repo.go b/repo.go index aef90da4..b8846e26 100644 --- a/repo.go +++ b/repo.go @@ -38,36 +38,6 @@ var snapshotMetadata = []string{ // names and generate target file metadata with additional custom metadata. type TargetsWalkFunc func(path string, target io.Reader) error -type LocalStore interface { - // GetMeta returns a map from metadata file names (e.g. root.json) to their raw JSON payload or an error. - GetMeta() (map[string]json.RawMessage, error) - - // SetMeta is used to update a metadata file name with a JSON payload. - SetMeta(string, json.RawMessage) error - - // WalkStagedTargets calls targetsFn for each staged target file in paths. - // - // If paths is empty, all staged target files will be walked. - WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error - - // Commit is used to publish staged files to the repository - Commit(bool, map[string]int, map[string]data.Hashes) error - - // GetSigners return a list of signers for a role. - GetSigners(string) ([]keys.Signer, error) - - // SaveSigner adds a signer to a role. - SaveSigner(string, keys.Signer) error - - // Clean is used to remove all staged metadata files. - Clean() error -} - -type PassphraseChanger interface { - // ChangePassphrase changes the passphrase for a role keys file. - ChangePassphrase(string) error -} - type Repo struct { local LocalStore hashAlgorithms []string