1 package tuf
2
3 import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "io/fs"
10 "log"
11 "os"
12 "path/filepath"
13 "strings"
14
15 "github.com/secure-systems-lab/go-securesystemslib/encrypted"
16 "github.com/theupdateframework/go-tuf/data"
17 "github.com/theupdateframework/go-tuf/internal/fsutil"
18 "github.com/theupdateframework/go-tuf/internal/sets"
19 "github.com/theupdateframework/go-tuf/pkg/keys"
20 "github.com/theupdateframework/go-tuf/util"
21 )
22
23 type LocalStore interface {
24
25 GetMeta() (map[string]json.RawMessage, error)
26
27
28 SetMeta(name string, meta json.RawMessage) error
29
30
31
32 WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error
33
34
35
36 FileIsStaged(filename string) bool
37
38
39
40
41
42
43 Commit(bool, map[string]int64, map[string]data.Hashes) error
44
45
46
47
48 GetSigners(role string) ([]keys.Signer, error)
49
50
51 SaveSigner(role string, signer keys.Signer) error
52
53
54 SignersForKeyIDs(keyIDs []string) []keys.Signer
55
56
57 Clean() error
58 }
59
60 type PassphraseChanger interface {
61
62 ChangePassphrase(string) error
63 }
64
65 func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) LocalStore {
66 if meta == nil {
67 meta = make(map[string]json.RawMessage)
68 }
69 return &memoryStore{
70 meta: meta,
71 stagedMeta: make(map[string]json.RawMessage),
72 files: files,
73 signerForKeyID: make(map[string]keys.Signer),
74 keyIDsForRole: make(map[string][]string),
75 }
76 }
77
78 type memoryStore struct {
79 meta map[string]json.RawMessage
80 stagedMeta map[string]json.RawMessage
81 files map[string][]byte
82
83 signerForKeyID map[string]keys.Signer
84 keyIDsForRole map[string][]string
85 }
86
87 func (m *memoryStore) GetMeta() (map[string]json.RawMessage, error) {
88 meta := make(map[string]json.RawMessage, len(m.meta)+len(m.stagedMeta))
89 for key, value := range m.meta {
90 meta[key] = value
91 }
92 for key, value := range m.stagedMeta {
93 meta[key] = value
94 }
95 return meta, nil
96 }
97
98 func (m *memoryStore) SetMeta(name string, meta json.RawMessage) error {
99 m.stagedMeta[name] = meta
100 return nil
101 }
102
103 func (m *memoryStore) FileIsStaged(name string) bool {
104 _, ok := m.stagedMeta[name]
105 return ok
106 }
107
108 func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error {
109 if len(paths) == 0 {
110 for path, data := range m.files {
111 if err := targetsFn(path, bytes.NewReader(data)); err != nil {
112 return err
113 }
114 }
115 return nil
116 }
117
118 for _, path := range paths {
119 data, ok := m.files[path]
120 if !ok {
121 return ErrFileNotFound{path}
122 }
123 if err := targetsFn(path, bytes.NewReader(data)); err != nil {
124 return err
125 }
126 }
127 return nil
128 }
129
130 func (m *memoryStore) Commit(consistentSnapshot bool, versions map[string]int64, hashes map[string]data.Hashes) error {
131 for name, meta := range m.stagedMeta {
132 paths := computeMetadataPaths(consistentSnapshot, name, versions)
133 for _, path := range paths {
134 m.meta[path] = meta
135 }
136
137
138
139 delete(m.stagedMeta, name)
140 }
141 return nil
142 }
143
144 func (m *memoryStore) GetSigners(role string) ([]keys.Signer, error) {
145 keyIDs, ok := m.keyIDsForRole[role]
146 if ok {
147 return m.SignersForKeyIDs(keyIDs), nil
148 }
149
150 return nil, nil
151 }
152
153 func (m *memoryStore) SaveSigner(role string, signer keys.Signer) error {
154 keyIDs := signer.PublicData().IDs()
155
156 for _, keyID := range keyIDs {
157 m.signerForKeyID[keyID] = signer
158 }
159
160 mergedKeyIDs := sets.DeduplicateStrings(append(m.keyIDsForRole[role], keyIDs...))
161 m.keyIDsForRole[role] = mergedKeyIDs
162 return nil
163 }
164
165 func (m *memoryStore) SignersForKeyIDs(keyIDs []string) []keys.Signer {
166 signers := []keys.Signer{}
167 keyIDsSeen := map[string]struct{}{}
168
169 for _, keyID := range keyIDs {
170 signer, ok := m.signerForKeyID[keyID]
171 if !ok {
172 continue
173 }
174 addSigner := false
175
176 for _, skid := range signer.PublicData().IDs() {
177 if _, seen := keyIDsSeen[skid]; !seen {
178 addSigner = true
179 }
180
181 keyIDsSeen[skid] = struct{}{}
182 }
183
184 if addSigner {
185 signers = append(signers, signer)
186 }
187 }
188
189 return signers
190 }
191
192 func (m *memoryStore) Clean() error {
193 return nil
194 }
195
196 type persistedKeys struct {
197 Encrypted bool `json:"encrypted"`
198 Data json.RawMessage `json:"data"`
199 }
200
201 type StoreOpts struct {
202 Logger *log.Logger
203 PassFunc util.PassphraseFunc
204 }
205
206 func FileSystemStore(dir string, p util.PassphraseFunc) LocalStore {
207 return &fileSystemStore{
208 dir: dir,
209 passphraseFunc: p,
210 logger: log.New(io.Discard, "", 0),
211 signerForKeyID: make(map[string]keys.Signer),
212 keyIDsForRole: make(map[string][]string),
213 }
214 }
215
216 func FileSystemStoreWithOpts(dir string, opts ...StoreOpts) LocalStore {
217 store := &fileSystemStore{
218 dir: dir,
219 passphraseFunc: nil,
220 logger: log.New(io.Discard, "", 0),
221 signerForKeyID: make(map[string]keys.Signer),
222 keyIDsForRole: make(map[string][]string),
223 }
224 for _, opt := range opts {
225 if opt.Logger != nil {
226 store.logger = opt.Logger
227 }
228 if opt.PassFunc != nil {
229 store.passphraseFunc = opt.PassFunc
230 }
231 }
232 return store
233 }
234
235 type fileSystemStore struct {
236 dir string
237 passphraseFunc util.PassphraseFunc
238 logger *log.Logger
239
240 signerForKeyID map[string]keys.Signer
241 keyIDsForRole map[string][]string
242 }
243
244 func (f *fileSystemStore) repoDir() string {
245 return filepath.Join(f.dir, "repository")
246 }
247
248 func (f *fileSystemStore) stagedDir() string {
249 return filepath.Join(f.dir, "staged")
250 }
251
252 func (f *fileSystemStore) GetMeta() (map[string]json.RawMessage, error) {
253
254
255 metaPaths := map[string]string{}
256
257 rd := f.repoDir()
258 committed, err := os.ReadDir(f.repoDir())
259 if err != nil && !errors.Is(err, fs.ErrNotExist) {
260 return nil, fmt.Errorf("could not list repo dir: %w", err)
261 }
262
263 for _, e := range committed {
264 imf, err := fsutil.IsMetaFile(e)
265 if err != nil {
266 return nil, err
267 }
268 if imf {
269 name := e.Name()
270 metaPaths[name] = filepath.Join(rd, name)
271 }
272 }
273
274 sd := f.stagedDir()
275 staged, err := os.ReadDir(sd)
276 if err != nil && !errors.Is(err, fs.ErrNotExist) {
277 return nil, fmt.Errorf("could not list staged dir: %w", err)
278 }
279
280 for _, e := range staged {
281 imf, err := fsutil.IsMetaFile(e)
282 if err != nil {
283 return nil, err
284 }
285 if imf {
286 name := e.Name()
287 metaPaths[name] = filepath.Join(sd, name)
288 }
289 }
290
291 meta := make(map[string]json.RawMessage)
292 for name, path := range metaPaths {
293 f, err := os.ReadFile(path)
294 if err != nil {
295 return nil, err
296 }
297 meta[name] = f
298 }
299 return meta, nil
300 }
301
302 func (f *fileSystemStore) SetMeta(name string, meta json.RawMessage) error {
303 if err := f.createDirs(); err != nil {
304 return err
305 }
306 if err := util.AtomicallyWriteFile(filepath.Join(f.stagedDir(), name), meta, 0644); err != nil {
307 return err
308 }
309 return nil
310 }
311
312 func (f *fileSystemStore) FileIsStaged(name string) bool {
313 _, err := os.Stat(filepath.Join(f.stagedDir(), name))
314 return err == nil
315 }
316
317 func (f *fileSystemStore) createDirs() error {
318 for _, dir := range []string{"keys", "repository", "staged/targets"} {
319 if err := os.MkdirAll(filepath.Join(f.dir, dir), 0755); err != nil {
320 return err
321 }
322 }
323 return nil
324 }
325
326 func (f *fileSystemStore) WalkStagedTargets(paths []string, targetsFn TargetsWalkFunc) error {
327 if len(paths) == 0 {
328 walkFunc := func(fpath string, info os.FileInfo, err error) error {
329 if err != nil {
330 return err
331 }
332 if info.IsDir() || !info.Mode().IsRegular() {
333 return nil
334 }
335 rel, err := filepath.Rel(filepath.Join(f.stagedDir(), "targets"), fpath)
336 if err != nil {
337 return err
338 }
339 file, err := os.Open(fpath)
340 if err != nil {
341 return err
342 }
343 defer file.Close()
344 return targetsFn(filepath.ToSlash(rel), file)
345 }
346 return filepath.Walk(filepath.Join(f.stagedDir(), "targets"), walkFunc)
347 }
348
349
350 for _, path := range paths {
351 realFilepath := filepath.Join(f.stagedDir(), "targets", path)
352 if _, err := os.Stat(realFilepath); err != nil {
353 if os.IsNotExist(err) {
354 return ErrFileNotFound{realFilepath}
355 }
356 return err
357 }
358 }
359
360 for _, path := range paths {
361 realFilepath := filepath.Join(f.stagedDir(), "targets", path)
362 file, err := os.Open(realFilepath)
363 if err != nil {
364 if os.IsNotExist(err) {
365 return ErrFileNotFound{realFilepath}
366 }
367 return err
368 }
369 err = targetsFn(path, file)
370 file.Close()
371 if err != nil {
372 return err
373 }
374 }
375 return nil
376 }
377
378 func (f *fileSystemStore) createRepoFile(path string) (*os.File, error) {
379 dst := filepath.Join(f.repoDir(), path)
380 if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
381 return nil, err
382 }
383 return os.Create(dst)
384 }
385
386 func (f *fileSystemStore) Commit(consistentSnapshot bool, versions map[string]int64, hashes map[string]data.Hashes) error {
387 isTarget := func(path string) bool {
388 return strings.HasPrefix(path, "targets/")
389 }
390 copyToRepo := func(fpath string, info os.FileInfo, err error) error {
391 if err != nil {
392 return err
393 }
394 if info.IsDir() || !info.Mode().IsRegular() {
395 return nil
396 }
397 rel, err := filepath.Rel(f.stagedDir(), fpath)
398 if err != nil {
399 return err
400 }
401 relpath := filepath.ToSlash(rel)
402
403 var paths []string
404 if isTarget(relpath) {
405 paths = computeTargetPaths(consistentSnapshot, relpath, hashes)
406 } else {
407 paths = computeMetadataPaths(consistentSnapshot, relpath, versions)
408 }
409 var files []io.Writer
410 for _, path := range paths {
411 file, err := f.createRepoFile(path)
412 if err != nil {
413 return err
414 }
415 defer file.Close()
416 files = append(files, file)
417 }
418 staged, err := os.Open(fpath)
419 if err != nil {
420 return err
421 }
422 defer staged.Close()
423 if _, err = io.Copy(io.MultiWriter(files...), staged); err != nil {
424 return err
425 }
426 return nil
427 }
428
429 needsRemoval := func(fpath string) bool {
430 if consistentSnapshot {
431
432 name := strings.SplitN(filepath.Base(fpath), ".", 2)
433 if len(name) != 2 || name[1] == "" {
434 return false
435 }
436 fpath = filepath.Join(filepath.Dir(fpath), name[1])
437 }
438 _, ok := hashes[filepath.ToSlash(fpath)]
439 return !ok
440 }
441
442 folderNeedsRemoval := func(fpath string) bool {
443 f, err := os.Open(fpath)
444 if err != nil {
445 return false
446 }
447 defer f.Close()
448 _, err = f.Readdirnames(1)
449 return err == io.EOF
450 }
451 removeFile := func(fpath string, info os.FileInfo, err error) error {
452 if err != nil {
453 return err
454 }
455 rel, err := filepath.Rel(f.repoDir(), fpath)
456 if err != nil {
457 return err
458 }
459 relpath := filepath.ToSlash(rel)
460 if !info.IsDir() && isTarget(relpath) && needsRemoval(rel) {
461
462 if err := os.Remove(fpath); err != nil {
463 return err
464 }
465
466 targetFolder := filepath.Dir(fpath)
467 if folderNeedsRemoval(targetFolder) {
468 if err := os.Remove(targetFolder); err != nil {
469 return err
470 }
471 }
472 }
473 return nil
474 }
475 if err := filepath.Walk(f.stagedDir(), copyToRepo); err != nil {
476 return err
477 }
478 if err := filepath.Walk(f.repoDir(), removeFile); err != nil {
479 return err
480 }
481 return f.Clean()
482 }
483
484 func (f *fileSystemStore) GetSigners(role string) ([]keys.Signer, error) {
485 keyIDs, ok := f.keyIDsForRole[role]
486 if ok {
487 return f.SignersForKeyIDs(keyIDs), nil
488 }
489
490 privKeys, _, err := f.loadPrivateKeys(role)
491 if err != nil {
492 if os.IsNotExist(err) {
493 return nil, nil
494 }
495 return nil, err
496 }
497
498 signers := []keys.Signer{}
499 for _, key := range privKeys {
500 signer, err := keys.GetSigner(key)
501 if err != nil {
502 return nil, err
503 }
504
505
506 for _, keyID := range signer.PublicData().IDs() {
507 f.keyIDsForRole[role] = append(f.keyIDsForRole[role], keyID)
508 f.signerForKeyID[keyID] = signer
509 }
510 signers = append(signers, signer)
511 }
512
513 return signers, nil
514 }
515
516 func (f *fileSystemStore) SignersForKeyIDs(keyIDs []string) []keys.Signer {
517 signers := []keys.Signer{}
518 keyIDsSeen := map[string]struct{}{}
519
520 for _, keyID := range keyIDs {
521 signer, ok := f.signerForKeyID[keyID]
522 if !ok {
523 continue
524 }
525
526 addSigner := false
527
528 for _, skid := range signer.PublicData().IDs() {
529 if _, seen := keyIDsSeen[skid]; !seen {
530 addSigner = true
531 }
532
533 keyIDsSeen[skid] = struct{}{}
534 }
535
536 if addSigner {
537 signers = append(signers, signer)
538 }
539 }
540
541 return signers
542 }
543
544
545
546 func (f *fileSystemStore) ChangePassphrase(role string) error {
547
548 if f.passphraseFunc == nil {
549 return ErrPassphraseRequired{role}
550 }
551
552
553 keys, _, err := f.loadPrivateKeys(role)
554 if err != nil {
555 if os.IsNotExist(err) {
556 f.logger.Printf("Failed to change passphrase. Missing keys file for %s role. \n", role)
557 }
558 return err
559 }
560
561 pass, err := f.passphraseFunc(role, true, true)
562 if err != nil {
563 return err
564 }
565
566 pk := &persistedKeys{Encrypted: true}
567 pk.Data, err = encrypted.Marshal(keys, pass)
568 if err != nil {
569 return err
570 }
571 data, err := json.MarshalIndent(pk, "", "\t")
572 if err != nil {
573 return err
574 }
575 if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
576 return err
577 }
578 f.logger.Printf("Successfully changed passphrase for %s keys file\n", role)
579 return nil
580 }
581
582 func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
583 if err := f.createDirs(); err != nil {
584 return err
585 }
586
587
588 privKeys, pass, err := f.loadPrivateKeys(role)
589 if err != nil && !os.IsNotExist(err) {
590 return err
591 }
592 key, err := signer.MarshalPrivateKey()
593 if err != nil {
594 return err
595 }
596 privKeys = append(privKeys, key)
597
598
599
600
601
602 if pass == nil && f.passphraseFunc != nil {
603 pass, err = f.passphraseFunc(role, true, false)
604 if err != nil {
605 return err
606 }
607 }
608
609 pk := &persistedKeys{}
610 if pass != nil {
611 pk.Data, err = encrypted.Marshal(privKeys, pass)
612 if err != nil {
613 return err
614 }
615 pk.Encrypted = true
616 } else {
617 pk.Data, err = json.MarshalIndent(privKeys, "", "\t")
618 if err != nil {
619 return err
620 }
621 }
622 data, err := json.MarshalIndent(pk, "", "\t")
623 if err != nil {
624 return err
625 }
626 if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
627 return err
628 }
629
630
631
632 keyIDsForRole := f.keyIDsForRole[role]
633 for _, key := range privKeys {
634 signer, err := keys.GetSigner(key)
635 if err != nil {
636 return err
637 }
638
639 keyIDs := signer.PublicData().IDs()
640
641 for _, keyID := range keyIDs {
642 f.signerForKeyID[keyID] = signer
643 }
644
645 keyIDsForRole = append(keyIDsForRole, keyIDs...)
646 }
647
648 f.keyIDsForRole[role] = sets.DeduplicateStrings(keyIDsForRole)
649
650 return nil
651 }
652
653
654
655 func (f *fileSystemStore) loadPrivateKeys(role string) ([]*data.PrivateKey, []byte, error) {
656 file, err := os.Open(f.keysPath(role))
657 if err != nil {
658 return nil, nil, err
659 }
660 defer file.Close()
661
662 pk := &persistedKeys{}
663 if err := json.NewDecoder(file).Decode(pk); err != nil {
664 return nil, nil, err
665 }
666
667 var keys []*data.PrivateKey
668 if !pk.Encrypted {
669 if err := json.Unmarshal(pk.Data, &keys); err != nil {
670 return nil, nil, err
671 }
672 return keys, nil, nil
673 }
674
675
676 if f.passphraseFunc == nil {
677 return nil, nil, ErrPassphraseRequired{role}
678 }
679
680
681 pass := []byte("")
682 if err := encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
683 pass, err = f.passphraseFunc(role, false, false)
684 if err != nil {
685 return nil, nil, err
686 }
687 if err = encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
688 return nil, nil, err
689 }
690 }
691 return keys, pass, nil
692 }
693
694 func (f *fileSystemStore) keysPath(role string) string {
695 return filepath.Join(f.dir, "keys", role+".json")
696 }
697
698 func (f *fileSystemStore) Clean() error {
699 _, err := os.Stat(filepath.Join(f.repoDir(), "root.json"))
700 if os.IsNotExist(err) {
701 return ErrNewRepository
702 } else if err != nil {
703 return err
704 }
705 if err := os.RemoveAll(f.stagedDir()); err != nil {
706 return err
707 }
708 return os.MkdirAll(filepath.Join(f.stagedDir(), "targets"), 0755)
709 }
710
711 func computeTargetPaths(consistentSnapshot bool, name string, hashes map[string]data.Hashes) []string {
712 if consistentSnapshot {
713 return util.HashedPaths(name, hashes[name])
714 } else {
715 return []string{name}
716 }
717 }
718
719 func computeMetadataPaths(consistentSnapshot bool, name string, versions map[string]int64) []string {
720 copyVersion := false
721
722 switch name {
723 case "root.json":
724 copyVersion = true
725 case "timestamp.json":
726 copyVersion = false
727 default:
728 if consistentSnapshot {
729 copyVersion = true
730 } else {
731 copyVersion = false
732 }
733 }
734
735 paths := []string{name}
736 if copyVersion {
737 paths = append(paths, util.VersionedPath(name, versions[name]))
738 }
739
740 return paths
741 }
742
View as plain text