1package tuf
2
3import (
4	"fmt"
5
6	"github.com/docker/go/canonical/json"
7	"github.com/theupdateframework/notary"
8
9	"github.com/theupdateframework/notary/trustpinning"
10	"github.com/theupdateframework/notary/tuf/data"
11	"github.com/theupdateframework/notary/tuf/signed"
12	"github.com/theupdateframework/notary/tuf/utils"
13)
14
15// ErrBuildDone is returned when any functions are called on RepoBuilder, and it
16// is already finished building
17var ErrBuildDone = fmt.Errorf(
18	"the builder has finished building and cannot accept any more input or produce any more output")
19
20// ErrInvalidBuilderInput is returned when RepoBuilder.Load is called
21// with the wrong type of metadata for the state that it's in
22type ErrInvalidBuilderInput struct{ msg string }
23
24func (e ErrInvalidBuilderInput) Error() string {
25	return e.msg
26}
27
28// ConsistentInfo is the consistent name and size of a role, or just the name
29// of the role and a -1 if no file metadata for the role is known
30type ConsistentInfo struct {
31	RoleName data.RoleName
32	fileMeta data.FileMeta
33}
34
35// ChecksumKnown determines whether or not we know enough to provide a size and
36// consistent name
37func (c ConsistentInfo) ChecksumKnown() bool {
38	// empty hash, no size : this is the zero value
39	return len(c.fileMeta.Hashes) > 0 || c.fileMeta.Length != 0
40}
41
42// ConsistentName returns the consistent name (rolename.sha256) for the role
43// given this consistent information
44func (c ConsistentInfo) ConsistentName() string {
45	return utils.ConsistentName(c.RoleName.String(), c.fileMeta.Hashes[notary.SHA256])
46}
47
48// Length returns the expected length of the role as per this consistent
49// information - if no checksum information is known, the size is -1.
50func (c ConsistentInfo) Length() int64 {
51	if c.ChecksumKnown() {
52		return c.fileMeta.Length
53	}
54	return -1
55}
56
57// RepoBuilder is an interface for an object which builds a tuf.Repo
58type RepoBuilder interface {
59	Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error
60	LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error
61	GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
62	GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
63	Finish() (*Repo, *Repo, error)
64	BootstrapNewBuilder() RepoBuilder
65	BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder
66
67	// informative functions
68	IsLoaded(roleName data.RoleName) bool
69	GetLoadedVersion(roleName data.RoleName) int
70	GetConsistentInfo(roleName data.RoleName) ConsistentInfo
71}
72
73// finishedBuilder refuses any more input or output
74type finishedBuilder struct{}
75
76func (f finishedBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
77	return ErrBuildDone
78}
79func (f finishedBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
80	return ErrBuildDone
81}
82func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
83	return nil, 0, ErrBuildDone
84}
85func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
86	return nil, 0, ErrBuildDone
87}
88func (f finishedBuilder) Finish() (*Repo, *Repo, error)    { return nil, nil, ErrBuildDone }
89func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
90func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
91	return f
92}
93func (f finishedBuilder) IsLoaded(roleName data.RoleName) bool        { return false }
94func (f finishedBuilder) GetLoadedVersion(roleName data.RoleName) int { return 0 }
95func (f finishedBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
96	return ConsistentInfo{RoleName: roleName}
97}
98
99// NewRepoBuilder is the only way to get a pre-built RepoBuilder
100func NewRepoBuilder(gun data.GUN, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
101	return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
102}
103
104// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
105// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
106func NewBuilderFromRepo(gun data.GUN, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
107	return &repoBuilderWrapper{
108		RepoBuilder: &repoBuilder{
109			repo:                 repo,
110			invalidRoles:         NewRepo(nil),
111			gun:                  gun,
112			trustpin:             trustpin,
113			loadedNotChecksummed: make(map[data.RoleName][]byte),
114		},
115	}
116}
117
118// repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps
119// the embed out with a finishedBuilder
120type repoBuilderWrapper struct {
121	RepoBuilder
122}
123
124func (rbw *repoBuilderWrapper) Finish() (*Repo, *Repo, error) {
125	switch rbw.RepoBuilder.(type) {
126	case finishedBuilder:
127		return rbw.RepoBuilder.Finish()
128	default:
129		old := rbw.RepoBuilder
130		rbw.RepoBuilder = finishedBuilder{}
131		return old.Finish()
132	}
133}
134
135// repoBuilder actually builds a tuf.Repo
136type repoBuilder struct {
137	repo         *Repo
138	invalidRoles *Repo
139
140	// needed for root trust pininng verification
141	gun      data.GUN
142	trustpin trustpinning.TrustPinConfig
143
144	// in case we load root and/or targets before snapshot and timestamp (
145	// or snapshot and not timestamp), so we know what to verify when the
146	// data with checksums come in
147	loadedNotChecksummed map[data.RoleName][]byte
148
149	// bootstrapped values to validate a new root
150	prevRoot                 *data.SignedRoot
151	bootstrappedRootChecksum *data.FileMeta
152
153	// for bootstrapping the next builder
154	nextRootChecksum *data.FileMeta
155}
156
157func (rb *repoBuilder) Finish() (*Repo, *Repo, error) {
158	return rb.repo, rb.invalidRoles, nil
159}
160
161func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
162	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
163		repo:                 NewRepo(rb.repo.cryptoService),
164		invalidRoles:         NewRepo(nil),
165		gun:                  rb.gun,
166		loadedNotChecksummed: make(map[data.RoleName][]byte),
167		trustpin:             rb.trustpin,
168
169		prevRoot:                 rb.repo.Root,
170		bootstrappedRootChecksum: rb.nextRootChecksum,
171	}}
172}
173
174func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
175	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
176		repo:                 NewRepo(rb.repo.cryptoService),
177		gun:                  rb.gun,
178		loadedNotChecksummed: make(map[data.RoleName][]byte),
179		trustpin:             trustpin,
180
181		prevRoot:                 rb.repo.Root,
182		bootstrappedRootChecksum: rb.nextRootChecksum,
183	}}
184}
185
186// IsLoaded returns whether a particular role has already been loaded
187func (rb *repoBuilder) IsLoaded(roleName data.RoleName) bool {
188	switch roleName {
189	case data.CanonicalRootRole:
190		return rb.repo.Root != nil
191	case data.CanonicalSnapshotRole:
192		return rb.repo.Snapshot != nil
193	case data.CanonicalTimestampRole:
194		return rb.repo.Timestamp != nil
195	default:
196		return rb.repo.Targets[roleName] != nil
197	}
198}
199
200// GetLoadedVersion returns the metadata version, if it is loaded, or 1 (the
201// minimum valid version number) otherwise
202func (rb *repoBuilder) GetLoadedVersion(roleName data.RoleName) int {
203	switch {
204	case roleName == data.CanonicalRootRole && rb.repo.Root != nil:
205		return rb.repo.Root.Signed.Version
206	case roleName == data.CanonicalSnapshotRole && rb.repo.Snapshot != nil:
207		return rb.repo.Snapshot.Signed.Version
208	case roleName == data.CanonicalTimestampRole && rb.repo.Timestamp != nil:
209		return rb.repo.Timestamp.Signed.Version
210	default:
211		if tgts, ok := rb.repo.Targets[roleName]; ok {
212			return tgts.Signed.Version
213		}
214	}
215
216	return 1
217}
218
219// GetConsistentInfo returns the consistent name and size of a role, if it is known,
220// otherwise just the rolename and a -1 for size (both of which are inside a
221// ConsistentInfo object)
222func (rb *repoBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
223	info := ConsistentInfo{RoleName: roleName} // starts out with unknown filemeta
224	switch roleName {
225	case data.CanonicalTimestampRole:
226		// we do not want to get a consistent timestamp, but we do want to
227		// limit its size
228		info.fileMeta.Length = notary.MaxTimestampSize
229	case data.CanonicalSnapshotRole:
230		if rb.repo.Timestamp != nil {
231			info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName.String()]
232		}
233	case data.CanonicalRootRole:
234		switch {
235		case rb.bootstrappedRootChecksum != nil:
236			info.fileMeta = *rb.bootstrappedRootChecksum
237		case rb.repo.Snapshot != nil:
238			info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
239		}
240	default:
241		if rb.repo.Snapshot != nil {
242			info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
243		}
244	}
245	return info
246}
247
248func (rb *repoBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
249	return rb.loadOptions(roleName, content, minVersion, allowExpired, false, false)
250}
251
252// LoadRootForUpdate adds additional flags for updating the root.json file
253func (rb *repoBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
254	if err := rb.loadOptions(data.CanonicalRootRole, content, minVersion, !isFinal, !isFinal, true); err != nil {
255		return err
256	}
257	if !isFinal {
258		rb.prevRoot = rb.repo.Root
259	}
260	return nil
261}
262
263// loadOptions adds additional flags that should only be used for updating the root.json
264func (rb *repoBuilder) loadOptions(roleName data.RoleName, content []byte, minVersion int, allowExpired, skipChecksum, allowLoaded bool) error {
265	if !data.ValidRole(roleName) {
266		return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)}
267	}
268
269	if !allowLoaded && rb.IsLoaded(roleName) {
270		return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s has already been loaded", roleName)}
271	}
272
273	var err error
274	switch roleName {
275	case data.CanonicalRootRole:
276		break
277	case data.CanonicalTimestampRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole:
278		err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole})
279	default: // delegations
280		err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalTargetsRole})
281	}
282	if err != nil {
283		return err
284	}
285
286	switch roleName {
287	case data.CanonicalRootRole:
288		return rb.loadRoot(content, minVersion, allowExpired, skipChecksum)
289	case data.CanonicalSnapshotRole:
290		return rb.loadSnapshot(content, minVersion, allowExpired)
291	case data.CanonicalTimestampRole:
292		return rb.loadTimestamp(content, minVersion, allowExpired)
293	case data.CanonicalTargetsRole:
294		return rb.loadTargets(content, minVersion, allowExpired)
295	default:
296		return rb.loadDelegation(roleName, content, minVersion, allowExpired)
297	}
298}
299
300func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []data.RoleName) error {
301	for _, req := range prereqRoles {
302		if !rb.IsLoaded(req) {
303			return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s must be loaded first", req)}
304		}
305	}
306	return nil
307}
308
309// GenerateSnapshot generates a new snapshot given a previous (optional) snapshot
310// We can't just load the previous snapshot, because it may have been signed by a different
311// snapshot key (maybe from a previous root version).  Note that we need the root role and
312// targets role to be loaded, because we need to generate metadata for both (and we need
313// the root to be loaded so we can get the snapshot role to sign with)
314func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
315	switch {
316	case rb.repo.cryptoService == nil:
317		return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot without a cryptoservice"}
318	case rb.IsLoaded(data.CanonicalSnapshotRole):
319		return nil, 0, ErrInvalidBuilderInput{msg: "snapshot has already been loaded"}
320	case rb.IsLoaded(data.CanonicalTimestampRole):
321		return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot if timestamp has already been loaded"}
322	}
323
324	if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole}); err != nil {
325		return nil, 0, err
326	}
327
328	// If there is no previous snapshot, we need to generate one, and so the targets must
329	// have already been loaded.  Otherwise, so long as the previous snapshot structure is
330	// valid (it has a targets meta), we're good.
331	switch prev {
332	case nil:
333		if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalTargetsRole}); err != nil {
334			return nil, 0, err
335		}
336
337		if err := rb.repo.InitSnapshot(); err != nil {
338			rb.repo.Snapshot = nil
339			return nil, 0, err
340		}
341	default:
342		if err := data.IsValidSnapshotStructure(prev.Signed); err != nil {
343			return nil, 0, err
344		}
345		rb.repo.Snapshot = prev
346	}
347
348	sgnd, err := rb.repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole))
349	if err != nil {
350		rb.repo.Snapshot = nil
351		return nil, 0, err
352	}
353
354	sgndJSON, err := json.Marshal(sgnd)
355	if err != nil {
356		rb.repo.Snapshot = nil
357		return nil, 0, err
358	}
359
360	// loadedNotChecksummed should currently contain the root awaiting checksumming,
361	// since it has to have been loaded.  Since the snapshot was generated using
362	// the root and targets data (there may not be any) that that have been loaded,
363	// remove all of them from rb.loadedNotChecksummed
364	for tgtName := range rb.repo.Targets {
365		delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
366	}
367	delete(rb.loadedNotChecksummed, data.CanonicalRootRole)
368
369	// The timestamp can't have been loaded yet, so we want to cache the snapshot
370	// bytes so we can validate the checksum when a timestamp gets generated or
371	// loaded later.
372	rb.loadedNotChecksummed[data.CanonicalSnapshotRole] = sgndJSON
373
374	return sgndJSON, rb.repo.Snapshot.Signed.Version, nil
375}
376
377// GenerateTimestamp generates a new timestamp given a previous (optional) timestamp
378// We can't just load the previous timestamp, because it may have been signed by a different
379// timestamp key (maybe from a previous root version)
380func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
381	switch {
382	case rb.repo.cryptoService == nil:
383		return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate timestamp without a cryptoservice"}
384	case rb.IsLoaded(data.CanonicalTimestampRole):
385		return nil, 0, ErrInvalidBuilderInput{msg: "timestamp has already been loaded"}
386	}
387
388	// SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always
389	// have the snapshot loaded first
390	if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
391		return nil, 0, err
392	}
393
394	switch prev {
395	case nil:
396		if err := rb.repo.InitTimestamp(); err != nil {
397			rb.repo.Timestamp = nil
398			return nil, 0, err
399		}
400	default:
401		if err := data.IsValidTimestampStructure(prev.Signed); err != nil {
402			return nil, 0, err
403		}
404		rb.repo.Timestamp = prev
405	}
406
407	sgnd, err := rb.repo.SignTimestamp(data.DefaultExpires(data.CanonicalTimestampRole))
408	if err != nil {
409		rb.repo.Timestamp = nil
410		return nil, 0, err
411	}
412
413	sgndJSON, err := json.Marshal(sgnd)
414	if err != nil {
415		rb.repo.Timestamp = nil
416		return nil, 0, err
417	}
418
419	// The snapshot should have been loaded (and not checksummed, since a timestamp
420	// cannot have been loaded), so it is awaiting checksumming. Since this
421	// timestamp was generated using the snapshot awaiting checksumming, we can
422	// remove it from rb.loadedNotChecksummed. There should be no other items
423	// awaiting checksumming now since loading/generating a snapshot should have
424	// cleared out everything else in `loadNotChecksummed`.
425	delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
426
427	return sgndJSON, rb.repo.Timestamp.Signed.Version, nil
428}
429
430// loadRoot loads a root if one has not been loaded
431func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired, skipChecksum bool) error {
432	roleName := data.CanonicalRootRole
433
434	signedObj, err := rb.bytesToSigned(content, data.CanonicalRootRole, skipChecksum)
435	if err != nil {
436		return err
437	}
438	// ValidateRoot validates against the previous root's role, as well as validates that the root
439	// itself is self-consistent with its own signatures and thresholds.
440	// This assumes that ValidateRoot calls data.RootFromSigned, which validates
441	// the metadata, rather than just unmarshalling signedObject into a SignedRoot object itself.
442	signedRoot, err := trustpinning.ValidateRoot(rb.prevRoot, signedObj, rb.gun, rb.trustpin)
443	if err != nil {
444		return err
445	}
446
447	if err := signed.VerifyVersion(&(signedRoot.Signed.SignedCommon), minVersion); err != nil {
448		return err
449	}
450
451	if !allowExpired { // check must go at the end because all other validation should pass
452		if err := signed.VerifyExpiry(&(signedRoot.Signed.SignedCommon), roleName); err != nil {
453			return err
454		}
455	}
456
457	rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
458	if err != nil { // this should never happen since the root has been validated
459		return err
460	}
461	rb.repo.Root = signedRoot
462	rb.repo.originalRootRole = rootRole
463	return nil
464}
465
466func (rb *repoBuilder) loadTimestamp(content []byte, minVersion int, allowExpired bool) error {
467	roleName := data.CanonicalTimestampRole
468
469	timestampRole, err := rb.repo.Root.BuildBaseRole(roleName)
470	if err != nil { // this should never happen, since it's already been validated
471		return err
472	}
473
474	signedObj, err := rb.bytesToSignedAndValidateSigs(timestampRole, content)
475	if err != nil {
476		return err
477	}
478
479	signedTimestamp, err := data.TimestampFromSigned(signedObj)
480	if err != nil {
481		return err
482	}
483
484	if err := signed.VerifyVersion(&(signedTimestamp.Signed.SignedCommon), minVersion); err != nil {
485		return err
486	}
487
488	if !allowExpired { // check must go at the end because all other validation should pass
489		if err := signed.VerifyExpiry(&(signedTimestamp.Signed.SignedCommon), roleName); err != nil {
490			return err
491		}
492	}
493
494	if err := rb.validateChecksumsFromTimestamp(signedTimestamp); err != nil {
495		return err
496	}
497
498	rb.repo.Timestamp = signedTimestamp
499	return nil
500}
501
502func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired bool) error {
503	roleName := data.CanonicalSnapshotRole
504
505	snapshotRole, err := rb.repo.Root.BuildBaseRole(roleName)
506	if err != nil { // this should never happen, since it's already been validated
507		return err
508	}
509
510	signedObj, err := rb.bytesToSignedAndValidateSigs(snapshotRole, content)
511	if err != nil {
512		return err
513	}
514
515	signedSnapshot, err := data.SnapshotFromSigned(signedObj)
516	if err != nil {
517		return err
518	}
519
520	if err := signed.VerifyVersion(&(signedSnapshot.Signed.SignedCommon), minVersion); err != nil {
521		return err
522	}
523
524	if !allowExpired { // check must go at the end because all other validation should pass
525		if err := signed.VerifyExpiry(&(signedSnapshot.Signed.SignedCommon), roleName); err != nil {
526			return err
527		}
528	}
529
530	// at this point, the only thing left to validate is existing checksums - we can use
531	// this snapshot to bootstrap the next builder if needed - and we don't need to do
532	// the 2-value assignment since we've already validated the signedSnapshot, which MUST
533	// have root metadata
534	rootMeta := signedSnapshot.Signed.Meta[data.CanonicalRootRole.String()]
535	rb.nextRootChecksum = &rootMeta
536
537	if err := rb.validateChecksumsFromSnapshot(signedSnapshot); err != nil {
538		return err
539	}
540
541	rb.repo.Snapshot = signedSnapshot
542	return nil
543}
544
545func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired bool) error {
546	roleName := data.CanonicalTargetsRole
547
548	targetsRole, err := rb.repo.Root.BuildBaseRole(roleName)
549	if err != nil { // this should never happen, since it's already been validated
550		return err
551	}
552
553	signedObj, err := rb.bytesToSignedAndValidateSigs(targetsRole, content)
554	if err != nil {
555		return err
556	}
557
558	signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
559	if err != nil {
560		return err
561	}
562
563	if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
564		return err
565	}
566
567	if !allowExpired { // check must go at the end because all other validation should pass
568		if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
569			return err
570		}
571	}
572
573	signedTargets.Signatures = signedObj.Signatures
574	rb.repo.Targets[roleName] = signedTargets
575	return nil
576}
577
578func (rb *repoBuilder) loadDelegation(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
579	delegationRole, err := rb.repo.GetDelegationRole(roleName)
580	if err != nil {
581		return err
582	}
583
584	// bytesToSigned checks checksum
585	signedObj, err := rb.bytesToSigned(content, roleName, false)
586	if err != nil {
587		return err
588	}
589
590	signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
591	if err != nil {
592		return err
593	}
594
595	if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
596		// don't capture in invalidRoles because the role we received is a rollback
597		return err
598	}
599
600	// verify signature
601	if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
602		rb.invalidRoles.Targets[roleName] = signedTargets
603		return err
604	}
605
606	if !allowExpired { // check must go at the end because all other validation should pass
607		if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
608			rb.invalidRoles.Targets[roleName] = signedTargets
609			return err
610		}
611	}
612
613	signedTargets.Signatures = signedObj.Signatures
614	rb.repo.Targets[roleName] = signedTargets
615	return nil
616}
617
618func (rb *repoBuilder) validateChecksumsFromTimestamp(ts *data.SignedTimestamp) error {
619	sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole]
620	if ok {
621		// by this point, the SignedTimestamp has been validated so it must have a snapshot hash
622		snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
623		if err := data.CheckHashes(sn, data.CanonicalSnapshotRole.String(), snMeta); err != nil {
624			return err
625		}
626		delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
627	}
628	return nil
629}
630
631func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) error {
632	var goodRoles []data.RoleName
633	for roleName, loadedBytes := range rb.loadedNotChecksummed {
634		switch roleName {
635		case data.CanonicalSnapshotRole, data.CanonicalTimestampRole:
636			break
637		default:
638			if err := data.CheckHashes(loadedBytes, roleName.String(), sn.Signed.Meta[roleName.String()].Hashes); err != nil {
639				return err
640			}
641			goodRoles = append(goodRoles, roleName)
642		}
643	}
644	for _, roleName := range goodRoles {
645		delete(rb.loadedNotChecksummed, roleName)
646	}
647	return nil
648}
649
650func (rb *repoBuilder) validateChecksumFor(content []byte, roleName data.RoleName) error {
651	// validate the bootstrap checksum for root, if provided
652	if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil {
653		if err := data.CheckHashes(content, roleName.String(), rb.bootstrappedRootChecksum.Hashes); err != nil {
654			return err
655		}
656	}
657
658	// but we also want to cache the root content, so that when the snapshot is
659	// loaded it is validated (to make sure everything in the repo is self-consistent)
660	checksums := rb.getChecksumsFor(roleName)
661	if checksums != nil { // as opposed to empty, in which case hash check should fail
662		if err := data.CheckHashes(content, roleName.String(), *checksums); err != nil {
663			return err
664		}
665	} else if roleName != data.CanonicalTimestampRole {
666		// timestamp is the only role which does not need to be checksummed, but
667		// for everything else, cache the contents in the list of roles that have
668		// not been checksummed by the snapshot/timestamp yet
669		rb.loadedNotChecksummed[roleName] = content
670	}
671
672	return nil
673}
674
675// Checksums the given bytes, and if they validate, convert to a data.Signed object.
676// If a checksums are nil (as opposed to empty), adds the bytes to the list of roles that
677// haven't been checksummed (unless it's a timestamp, which has no checksum reference).
678func (rb *repoBuilder) bytesToSigned(content []byte, roleName data.RoleName, skipChecksum bool) (*data.Signed, error) {
679	if !skipChecksum {
680		if err := rb.validateChecksumFor(content, roleName); err != nil {
681			return nil, err
682		}
683	}
684
685	// unmarshal to signed
686	signedObj := &data.Signed{}
687	if err := json.Unmarshal(content, signedObj); err != nil {
688		return nil, err
689	}
690
691	return signedObj, nil
692}
693
694func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content []byte) (*data.Signed, error) {
695
696	signedObj, err := rb.bytesToSigned(content, role.Name, false)
697	if err != nil {
698		return nil, err
699	}
700
701	// verify signature
702	if err := signed.VerifySignatures(signedObj, role); err != nil {
703		return nil, err
704	}
705
706	return signedObj, nil
707}
708
709// If the checksum reference (the loaded timestamp for the snapshot role, and
710// the loaded snapshot for every other role except timestamp and snapshot) is nil,
711// then return nil for the checksums, meaning that the checksum is not yet
712// available.  If the checksum reference *is* loaded, then always returns the
713// Hashes object for the given role - if it doesn't exist, returns an empty Hash
714// object (against which any checksum validation would fail).
715func (rb *repoBuilder) getChecksumsFor(role data.RoleName) *data.Hashes {
716	var hashes data.Hashes
717	switch role {
718	case data.CanonicalTimestampRole:
719		return nil
720	case data.CanonicalSnapshotRole:
721		if rb.repo.Timestamp == nil {
722			return nil
723		}
724		hashes = rb.repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
725	default:
726		if rb.repo.Snapshot == nil {
727			return nil
728		}
729		hashes = rb.repo.Snapshot.Signed.Meta[role.String()].Hashes
730	}
731	return &hashes
732}
733