mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			733 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			733 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package tuf
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
 | 
						|
	"github.com/docker/go/canonical/json"
 | 
						|
	"github.com/theupdateframework/notary"
 | 
						|
 | 
						|
	"github.com/theupdateframework/notary/trustpinning"
 | 
						|
	"github.com/theupdateframework/notary/tuf/data"
 | 
						|
	"github.com/theupdateframework/notary/tuf/signed"
 | 
						|
	"github.com/theupdateframework/notary/tuf/utils"
 | 
						|
)
 | 
						|
 | 
						|
// ErrBuildDone is returned when any functions are called on RepoBuilder, and it
 | 
						|
// is already finished building
 | 
						|
var ErrBuildDone = fmt.Errorf(
 | 
						|
	"the builder has finished building and cannot accept any more input or produce any more output")
 | 
						|
 | 
						|
// ErrInvalidBuilderInput is returned when RepoBuilder.Load is called
 | 
						|
// with the wrong type of metadata for the state that it's in
 | 
						|
type ErrInvalidBuilderInput struct{ msg string }
 | 
						|
 | 
						|
func (e ErrInvalidBuilderInput) Error() string {
 | 
						|
	return e.msg
 | 
						|
}
 | 
						|
 | 
						|
// ConsistentInfo is the consistent name and size of a role, or just the name
 | 
						|
// of the role and a -1 if no file metadata for the role is known
 | 
						|
type ConsistentInfo struct {
 | 
						|
	RoleName data.RoleName
 | 
						|
	fileMeta data.FileMeta
 | 
						|
}
 | 
						|
 | 
						|
// ChecksumKnown determines whether or not we know enough to provide a size and
 | 
						|
// consistent name
 | 
						|
func (c ConsistentInfo) ChecksumKnown() bool {
 | 
						|
	// empty hash, no size : this is the zero value
 | 
						|
	return len(c.fileMeta.Hashes) > 0 || c.fileMeta.Length != 0
 | 
						|
}
 | 
						|
 | 
						|
// ConsistentName returns the consistent name (rolename.sha256) for the role
 | 
						|
// given this consistent information
 | 
						|
func (c ConsistentInfo) ConsistentName() string {
 | 
						|
	return utils.ConsistentName(c.RoleName.String(), c.fileMeta.Hashes[notary.SHA256])
 | 
						|
}
 | 
						|
 | 
						|
// Length returns the expected length of the role as per this consistent
 | 
						|
// information - if no checksum information is known, the size is -1.
 | 
						|
func (c ConsistentInfo) Length() int64 {
 | 
						|
	if c.ChecksumKnown() {
 | 
						|
		return c.fileMeta.Length
 | 
						|
	}
 | 
						|
	return -1
 | 
						|
}
 | 
						|
 | 
						|
// RepoBuilder is an interface for an object which builds a tuf.Repo
 | 
						|
type RepoBuilder interface {
 | 
						|
	Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error
 | 
						|
	LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error
 | 
						|
	GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
 | 
						|
	GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
 | 
						|
	Finish() (*Repo, *Repo, error)
 | 
						|
	BootstrapNewBuilder() RepoBuilder
 | 
						|
	BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder
 | 
						|
 | 
						|
	// informative functions
 | 
						|
	IsLoaded(roleName data.RoleName) bool
 | 
						|
	GetLoadedVersion(roleName data.RoleName) int
 | 
						|
	GetConsistentInfo(roleName data.RoleName) ConsistentInfo
 | 
						|
}
 | 
						|
 | 
						|
// finishedBuilder refuses any more input or output
 | 
						|
type finishedBuilder struct{}
 | 
						|
 | 
						|
func (f finishedBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
 | 
						|
	return ErrBuildDone
 | 
						|
}
 | 
						|
func (f finishedBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
 | 
						|
	return ErrBuildDone
 | 
						|
}
 | 
						|
func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
 | 
						|
	return nil, 0, ErrBuildDone
 | 
						|
}
 | 
						|
func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
 | 
						|
	return nil, 0, ErrBuildDone
 | 
						|
}
 | 
						|
func (f finishedBuilder) Finish() (*Repo, *Repo, error)    { return nil, nil, ErrBuildDone }
 | 
						|
func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
 | 
						|
func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
 | 
						|
	return f
 | 
						|
}
 | 
						|
func (f finishedBuilder) IsLoaded(roleName data.RoleName) bool        { return false }
 | 
						|
func (f finishedBuilder) GetLoadedVersion(roleName data.RoleName) int { return 0 }
 | 
						|
func (f finishedBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
 | 
						|
	return ConsistentInfo{RoleName: roleName}
 | 
						|
}
 | 
						|
 | 
						|
// NewRepoBuilder is the only way to get a pre-built RepoBuilder
 | 
						|
func NewRepoBuilder(gun data.GUN, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
 | 
						|
	return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
 | 
						|
}
 | 
						|
 | 
						|
// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
 | 
						|
// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
 | 
						|
func NewBuilderFromRepo(gun data.GUN, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
 | 
						|
	return &repoBuilderWrapper{
 | 
						|
		RepoBuilder: &repoBuilder{
 | 
						|
			repo:                 repo,
 | 
						|
			invalidRoles:         NewRepo(nil),
 | 
						|
			gun:                  gun,
 | 
						|
			trustpin:             trustpin,
 | 
						|
			loadedNotChecksummed: make(map[data.RoleName][]byte),
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps
 | 
						|
// the embed out with a finishedBuilder
 | 
						|
type repoBuilderWrapper struct {
 | 
						|
	RepoBuilder
 | 
						|
}
 | 
						|
 | 
						|
func (rbw *repoBuilderWrapper) Finish() (*Repo, *Repo, error) {
 | 
						|
	switch rbw.RepoBuilder.(type) {
 | 
						|
	case finishedBuilder:
 | 
						|
		return rbw.RepoBuilder.Finish()
 | 
						|
	default:
 | 
						|
		old := rbw.RepoBuilder
 | 
						|
		rbw.RepoBuilder = finishedBuilder{}
 | 
						|
		return old.Finish()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// repoBuilder actually builds a tuf.Repo
 | 
						|
type repoBuilder struct {
 | 
						|
	repo         *Repo
 | 
						|
	invalidRoles *Repo
 | 
						|
 | 
						|
	// needed for root trust pininng verification
 | 
						|
	gun      data.GUN
 | 
						|
	trustpin trustpinning.TrustPinConfig
 | 
						|
 | 
						|
	// in case we load root and/or targets before snapshot and timestamp (
 | 
						|
	// or snapshot and not timestamp), so we know what to verify when the
 | 
						|
	// data with checksums come in
 | 
						|
	loadedNotChecksummed map[data.RoleName][]byte
 | 
						|
 | 
						|
	// bootstrapped values to validate a new root
 | 
						|
	prevRoot                 *data.SignedRoot
 | 
						|
	bootstrappedRootChecksum *data.FileMeta
 | 
						|
 | 
						|
	// for bootstrapping the next builder
 | 
						|
	nextRootChecksum *data.FileMeta
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) Finish() (*Repo, *Repo, error) {
 | 
						|
	return rb.repo, rb.invalidRoles, nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
 | 
						|
	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
 | 
						|
		repo:                 NewRepo(rb.repo.cryptoService),
 | 
						|
		invalidRoles:         NewRepo(nil),
 | 
						|
		gun:                  rb.gun,
 | 
						|
		loadedNotChecksummed: make(map[data.RoleName][]byte),
 | 
						|
		trustpin:             rb.trustpin,
 | 
						|
 | 
						|
		prevRoot:                 rb.repo.Root,
 | 
						|
		bootstrappedRootChecksum: rb.nextRootChecksum,
 | 
						|
	}}
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
 | 
						|
	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
 | 
						|
		repo:                 NewRepo(rb.repo.cryptoService),
 | 
						|
		gun:                  rb.gun,
 | 
						|
		loadedNotChecksummed: make(map[data.RoleName][]byte),
 | 
						|
		trustpin:             trustpin,
 | 
						|
 | 
						|
		prevRoot:                 rb.repo.Root,
 | 
						|
		bootstrappedRootChecksum: rb.nextRootChecksum,
 | 
						|
	}}
 | 
						|
}
 | 
						|
 | 
						|
// IsLoaded returns whether a particular role has already been loaded
 | 
						|
func (rb *repoBuilder) IsLoaded(roleName data.RoleName) bool {
 | 
						|
	switch roleName {
 | 
						|
	case data.CanonicalRootRole:
 | 
						|
		return rb.repo.Root != nil
 | 
						|
	case data.CanonicalSnapshotRole:
 | 
						|
		return rb.repo.Snapshot != nil
 | 
						|
	case data.CanonicalTimestampRole:
 | 
						|
		return rb.repo.Timestamp != nil
 | 
						|
	default:
 | 
						|
		return rb.repo.Targets[roleName] != nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// GetLoadedVersion returns the metadata version, if it is loaded, or 1 (the
 | 
						|
// minimum valid version number) otherwise
 | 
						|
func (rb *repoBuilder) GetLoadedVersion(roleName data.RoleName) int {
 | 
						|
	switch {
 | 
						|
	case roleName == data.CanonicalRootRole && rb.repo.Root != nil:
 | 
						|
		return rb.repo.Root.Signed.Version
 | 
						|
	case roleName == data.CanonicalSnapshotRole && rb.repo.Snapshot != nil:
 | 
						|
		return rb.repo.Snapshot.Signed.Version
 | 
						|
	case roleName == data.CanonicalTimestampRole && rb.repo.Timestamp != nil:
 | 
						|
		return rb.repo.Timestamp.Signed.Version
 | 
						|
	default:
 | 
						|
		if tgts, ok := rb.repo.Targets[roleName]; ok {
 | 
						|
			return tgts.Signed.Version
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 1
 | 
						|
}
 | 
						|
 | 
						|
// GetConsistentInfo returns the consistent name and size of a role, if it is known,
 | 
						|
// otherwise just the rolename and a -1 for size (both of which are inside a
 | 
						|
// ConsistentInfo object)
 | 
						|
func (rb *repoBuilder) GetConsistentInfo(roleName data.RoleName) ConsistentInfo {
 | 
						|
	info := ConsistentInfo{RoleName: roleName} // starts out with unknown filemeta
 | 
						|
	switch roleName {
 | 
						|
	case data.CanonicalTimestampRole:
 | 
						|
		// we do not want to get a consistent timestamp, but we do want to
 | 
						|
		// limit its size
 | 
						|
		info.fileMeta.Length = notary.MaxTimestampSize
 | 
						|
	case data.CanonicalSnapshotRole:
 | 
						|
		if rb.repo.Timestamp != nil {
 | 
						|
			info.fileMeta = rb.repo.Timestamp.Signed.Meta[roleName.String()]
 | 
						|
		}
 | 
						|
	case data.CanonicalRootRole:
 | 
						|
		switch {
 | 
						|
		case rb.bootstrappedRootChecksum != nil:
 | 
						|
			info.fileMeta = *rb.bootstrappedRootChecksum
 | 
						|
		case rb.repo.Snapshot != nil:
 | 
						|
			info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		if rb.repo.Snapshot != nil {
 | 
						|
			info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName.String()]
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return info
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) Load(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
 | 
						|
	return rb.loadOptions(roleName, content, minVersion, allowExpired, false, false)
 | 
						|
}
 | 
						|
 | 
						|
// LoadRootForUpdate adds additional flags for updating the root.json file
 | 
						|
func (rb *repoBuilder) LoadRootForUpdate(content []byte, minVersion int, isFinal bool) error {
 | 
						|
	if err := rb.loadOptions(data.CanonicalRootRole, content, minVersion, !isFinal, !isFinal, true); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if !isFinal {
 | 
						|
		rb.prevRoot = rb.repo.Root
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// loadOptions adds additional flags that should only be used for updating the root.json
 | 
						|
func (rb *repoBuilder) loadOptions(roleName data.RoleName, content []byte, minVersion int, allowExpired, skipChecksum, allowLoaded bool) error {
 | 
						|
	if !data.ValidRole(roleName) {
 | 
						|
		return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)}
 | 
						|
	}
 | 
						|
 | 
						|
	if !allowLoaded && rb.IsLoaded(roleName) {
 | 
						|
		return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s has already been loaded", roleName)}
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
	switch roleName {
 | 
						|
	case data.CanonicalRootRole:
 | 
						|
		break
 | 
						|
	case data.CanonicalTimestampRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole:
 | 
						|
		err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole})
 | 
						|
	default: // delegations
 | 
						|
		err = rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalTargetsRole})
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	switch roleName {
 | 
						|
	case data.CanonicalRootRole:
 | 
						|
		return rb.loadRoot(content, minVersion, allowExpired, skipChecksum)
 | 
						|
	case data.CanonicalSnapshotRole:
 | 
						|
		return rb.loadSnapshot(content, minVersion, allowExpired)
 | 
						|
	case data.CanonicalTimestampRole:
 | 
						|
		return rb.loadTimestamp(content, minVersion, allowExpired)
 | 
						|
	case data.CanonicalTargetsRole:
 | 
						|
		return rb.loadTargets(content, minVersion, allowExpired)
 | 
						|
	default:
 | 
						|
		return rb.loadDelegation(roleName, content, minVersion, allowExpired)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []data.RoleName) error {
 | 
						|
	for _, req := range prereqRoles {
 | 
						|
		if !rb.IsLoaded(req) {
 | 
						|
			return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s must be loaded first", req)}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// GenerateSnapshot generates a new snapshot given a previous (optional) snapshot
 | 
						|
// We can't just load the previous snapshot, because it may have been signed by a different
 | 
						|
// snapshot key (maybe from a previous root version).  Note that we need the root role and
 | 
						|
// targets role to be loaded, because we need to generate metadata for both (and we need
 | 
						|
// the root to be loaded so we can get the snapshot role to sign with)
 | 
						|
func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) {
 | 
						|
	switch {
 | 
						|
	case rb.repo.cryptoService == nil:
 | 
						|
		return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot without a cryptoservice"}
 | 
						|
	case rb.IsLoaded(data.CanonicalSnapshotRole):
 | 
						|
		return nil, 0, ErrInvalidBuilderInput{msg: "snapshot has already been loaded"}
 | 
						|
	case rb.IsLoaded(data.CanonicalTimestampRole):
 | 
						|
		return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate snapshot if timestamp has already been loaded"}
 | 
						|
	}
 | 
						|
 | 
						|
	if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole}); err != nil {
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	// If there is no previous snapshot, we need to generate one, and so the targets must
 | 
						|
	// have already been loaded.  Otherwise, so long as the previous snapshot structure is
 | 
						|
	// valid (it has a targets meta), we're good.
 | 
						|
	switch prev {
 | 
						|
	case nil:
 | 
						|
		if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalTargetsRole}); err != nil {
 | 
						|
			return nil, 0, err
 | 
						|
		}
 | 
						|
 | 
						|
		if err := rb.repo.InitSnapshot(); err != nil {
 | 
						|
			rb.repo.Snapshot = nil
 | 
						|
			return nil, 0, err
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		if err := data.IsValidSnapshotStructure(prev.Signed); err != nil {
 | 
						|
			return nil, 0, err
 | 
						|
		}
 | 
						|
		rb.repo.Snapshot = prev
 | 
						|
	}
 | 
						|
 | 
						|
	sgnd, err := rb.repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole))
 | 
						|
	if err != nil {
 | 
						|
		rb.repo.Snapshot = nil
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	sgndJSON, err := json.Marshal(sgnd)
 | 
						|
	if err != nil {
 | 
						|
		rb.repo.Snapshot = nil
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	// loadedNotChecksummed should currently contain the root awaiting checksumming,
 | 
						|
	// since it has to have been loaded.  Since the snapshot was generated using
 | 
						|
	// the root and targets data (there may not be any) that that have been loaded,
 | 
						|
	// remove all of them from rb.loadedNotChecksummed
 | 
						|
	for tgtName := range rb.repo.Targets {
 | 
						|
		delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
 | 
						|
	}
 | 
						|
	delete(rb.loadedNotChecksummed, data.CanonicalRootRole)
 | 
						|
 | 
						|
	// The timestamp can't have been loaded yet, so we want to cache the snapshot
 | 
						|
	// bytes so we can validate the checksum when a timestamp gets generated or
 | 
						|
	// loaded later.
 | 
						|
	rb.loadedNotChecksummed[data.CanonicalSnapshotRole] = sgndJSON
 | 
						|
 | 
						|
	return sgndJSON, rb.repo.Snapshot.Signed.Version, nil
 | 
						|
}
 | 
						|
 | 
						|
// GenerateTimestamp generates a new timestamp given a previous (optional) timestamp
 | 
						|
// We can't just load the previous timestamp, because it may have been signed by a different
 | 
						|
// timestamp key (maybe from a previous root version)
 | 
						|
func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
 | 
						|
	switch {
 | 
						|
	case rb.repo.cryptoService == nil:
 | 
						|
		return nil, 0, ErrInvalidBuilderInput{msg: "cannot generate timestamp without a cryptoservice"}
 | 
						|
	case rb.IsLoaded(data.CanonicalTimestampRole):
 | 
						|
		return nil, 0, ErrInvalidBuilderInput{msg: "timestamp has already been loaded"}
 | 
						|
	}
 | 
						|
 | 
						|
	// SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always
 | 
						|
	// have the snapshot loaded first
 | 
						|
	if err := rb.checkPrereqsLoaded([]data.RoleName{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	switch prev {
 | 
						|
	case nil:
 | 
						|
		if err := rb.repo.InitTimestamp(); err != nil {
 | 
						|
			rb.repo.Timestamp = nil
 | 
						|
			return nil, 0, err
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		if err := data.IsValidTimestampStructure(prev.Signed); err != nil {
 | 
						|
			return nil, 0, err
 | 
						|
		}
 | 
						|
		rb.repo.Timestamp = prev
 | 
						|
	}
 | 
						|
 | 
						|
	sgnd, err := rb.repo.SignTimestamp(data.DefaultExpires(data.CanonicalTimestampRole))
 | 
						|
	if err != nil {
 | 
						|
		rb.repo.Timestamp = nil
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	sgndJSON, err := json.Marshal(sgnd)
 | 
						|
	if err != nil {
 | 
						|
		rb.repo.Timestamp = nil
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
 | 
						|
	// The snapshot should have been loaded (and not checksummed, since a timestamp
 | 
						|
	// cannot have been loaded), so it is awaiting checksumming. Since this
 | 
						|
	// timestamp was generated using the snapshot awaiting checksumming, we can
 | 
						|
	// remove it from rb.loadedNotChecksummed. There should be no other items
 | 
						|
	// awaiting checksumming now since loading/generating a snapshot should have
 | 
						|
	// cleared out everything else in `loadNotChecksummed`.
 | 
						|
	delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
 | 
						|
 | 
						|
	return sgndJSON, rb.repo.Timestamp.Signed.Version, nil
 | 
						|
}
 | 
						|
 | 
						|
// loadRoot loads a root if one has not been loaded
 | 
						|
func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired, skipChecksum bool) error {
 | 
						|
	roleName := data.CanonicalRootRole
 | 
						|
 | 
						|
	signedObj, err := rb.bytesToSigned(content, data.CanonicalRootRole, skipChecksum)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	// ValidateRoot validates against the previous root's role, as well as validates that the root
 | 
						|
	// itself is self-consistent with its own signatures and thresholds.
 | 
						|
	// This assumes that ValidateRoot calls data.RootFromSigned, which validates
 | 
						|
	// the metadata, rather than just unmarshalling signedObject into a SignedRoot object itself.
 | 
						|
	signedRoot, err := trustpinning.ValidateRoot(rb.prevRoot, signedObj, rb.gun, rb.trustpin)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := signed.VerifyVersion(&(signedRoot.Signed.SignedCommon), minVersion); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !allowExpired { // check must go at the end because all other validation should pass
 | 
						|
		if err := signed.VerifyExpiry(&(signedRoot.Signed.SignedCommon), roleName); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
 | 
						|
	if err != nil { // this should never happen since the root has been validated
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	rb.repo.Root = signedRoot
 | 
						|
	rb.repo.originalRootRole = rootRole
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) loadTimestamp(content []byte, minVersion int, allowExpired bool) error {
 | 
						|
	roleName := data.CanonicalTimestampRole
 | 
						|
 | 
						|
	timestampRole, err := rb.repo.Root.BuildBaseRole(roleName)
 | 
						|
	if err != nil { // this should never happen, since it's already been validated
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedObj, err := rb.bytesToSignedAndValidateSigs(timestampRole, content)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedTimestamp, err := data.TimestampFromSigned(signedObj)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := signed.VerifyVersion(&(signedTimestamp.Signed.SignedCommon), minVersion); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !allowExpired { // check must go at the end because all other validation should pass
 | 
						|
		if err := signed.VerifyExpiry(&(signedTimestamp.Signed.SignedCommon), roleName); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err := rb.validateChecksumsFromTimestamp(signedTimestamp); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	rb.repo.Timestamp = signedTimestamp
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired bool) error {
 | 
						|
	roleName := data.CanonicalSnapshotRole
 | 
						|
 | 
						|
	snapshotRole, err := rb.repo.Root.BuildBaseRole(roleName)
 | 
						|
	if err != nil { // this should never happen, since it's already been validated
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedObj, err := rb.bytesToSignedAndValidateSigs(snapshotRole, content)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedSnapshot, err := data.SnapshotFromSigned(signedObj)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := signed.VerifyVersion(&(signedSnapshot.Signed.SignedCommon), minVersion); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !allowExpired { // check must go at the end because all other validation should pass
 | 
						|
		if err := signed.VerifyExpiry(&(signedSnapshot.Signed.SignedCommon), roleName); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// at this point, the only thing left to validate is existing checksums - we can use
 | 
						|
	// this snapshot to bootstrap the next builder if needed - and we don't need to do
 | 
						|
	// the 2-value assignment since we've already validated the signedSnapshot, which MUST
 | 
						|
	// have root metadata
 | 
						|
	rootMeta := signedSnapshot.Signed.Meta[data.CanonicalRootRole.String()]
 | 
						|
	rb.nextRootChecksum = &rootMeta
 | 
						|
 | 
						|
	if err := rb.validateChecksumsFromSnapshot(signedSnapshot); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	rb.repo.Snapshot = signedSnapshot
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired bool) error {
 | 
						|
	roleName := data.CanonicalTargetsRole
 | 
						|
 | 
						|
	targetsRole, err := rb.repo.Root.BuildBaseRole(roleName)
 | 
						|
	if err != nil { // this should never happen, since it's already been validated
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedObj, err := rb.bytesToSignedAndValidateSigs(targetsRole, content)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !allowExpired { // check must go at the end because all other validation should pass
 | 
						|
		if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	signedTargets.Signatures = signedObj.Signatures
 | 
						|
	rb.repo.Targets[roleName] = signedTargets
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) loadDelegation(roleName data.RoleName, content []byte, minVersion int, allowExpired bool) error {
 | 
						|
	delegationRole, err := rb.repo.GetDelegationRole(roleName)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// bytesToSigned checks checksum
 | 
						|
	signedObj, err := rb.bytesToSigned(content, roleName, false)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	signedTargets, err := data.TargetsFromSigned(signedObj, roleName)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
 | 
						|
		// don't capture in invalidRoles because the role we received is a rollback
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// verify signature
 | 
						|
	if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
 | 
						|
		rb.invalidRoles.Targets[roleName] = signedTargets
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if !allowExpired { // check must go at the end because all other validation should pass
 | 
						|
		if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
 | 
						|
			rb.invalidRoles.Targets[roleName] = signedTargets
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	signedTargets.Signatures = signedObj.Signatures
 | 
						|
	rb.repo.Targets[roleName] = signedTargets
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) validateChecksumsFromTimestamp(ts *data.SignedTimestamp) error {
 | 
						|
	sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole]
 | 
						|
	if ok {
 | 
						|
		// by this point, the SignedTimestamp has been validated so it must have a snapshot hash
 | 
						|
		snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
 | 
						|
		if err := data.CheckHashes(sn, data.CanonicalSnapshotRole.String(), snMeta); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) validateChecksumsFromSnapshot(sn *data.SignedSnapshot) error {
 | 
						|
	var goodRoles []data.RoleName
 | 
						|
	for roleName, loadedBytes := range rb.loadedNotChecksummed {
 | 
						|
		switch roleName {
 | 
						|
		case data.CanonicalSnapshotRole, data.CanonicalTimestampRole:
 | 
						|
			break
 | 
						|
		default:
 | 
						|
			if err := data.CheckHashes(loadedBytes, roleName.String(), sn.Signed.Meta[roleName.String()].Hashes); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			goodRoles = append(goodRoles, roleName)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, roleName := range goodRoles {
 | 
						|
		delete(rb.loadedNotChecksummed, roleName)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) validateChecksumFor(content []byte, roleName data.RoleName) error {
 | 
						|
	// validate the bootstrap checksum for root, if provided
 | 
						|
	if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil {
 | 
						|
		if err := data.CheckHashes(content, roleName.String(), rb.bootstrappedRootChecksum.Hashes); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// but we also want to cache the root content, so that when the snapshot is
 | 
						|
	// loaded it is validated (to make sure everything in the repo is self-consistent)
 | 
						|
	checksums := rb.getChecksumsFor(roleName)
 | 
						|
	if checksums != nil { // as opposed to empty, in which case hash check should fail
 | 
						|
		if err := data.CheckHashes(content, roleName.String(), *checksums); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	} else if roleName != data.CanonicalTimestampRole {
 | 
						|
		// timestamp is the only role which does not need to be checksummed, but
 | 
						|
		// for everything else, cache the contents in the list of roles that have
 | 
						|
		// not been checksummed by the snapshot/timestamp yet
 | 
						|
		rb.loadedNotChecksummed[roleName] = content
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Checksums the given bytes, and if they validate, convert to a data.Signed object.
 | 
						|
// If a checksums are nil (as opposed to empty), adds the bytes to the list of roles that
 | 
						|
// haven't been checksummed (unless it's a timestamp, which has no checksum reference).
 | 
						|
func (rb *repoBuilder) bytesToSigned(content []byte, roleName data.RoleName, skipChecksum bool) (*data.Signed, error) {
 | 
						|
	if !skipChecksum {
 | 
						|
		if err := rb.validateChecksumFor(content, roleName); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// unmarshal to signed
 | 
						|
	signedObj := &data.Signed{}
 | 
						|
	if err := json.Unmarshal(content, signedObj); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return signedObj, nil
 | 
						|
}
 | 
						|
 | 
						|
func (rb *repoBuilder) bytesToSignedAndValidateSigs(role data.BaseRole, content []byte) (*data.Signed, error) {
 | 
						|
 | 
						|
	signedObj, err := rb.bytesToSigned(content, role.Name, false)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// verify signature
 | 
						|
	if err := signed.VerifySignatures(signedObj, role); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return signedObj, nil
 | 
						|
}
 | 
						|
 | 
						|
// If the checksum reference (the loaded timestamp for the snapshot role, and
 | 
						|
// the loaded snapshot for every other role except timestamp and snapshot) is nil,
 | 
						|
// then return nil for the checksums, meaning that the checksum is not yet
 | 
						|
// available.  If the checksum reference *is* loaded, then always returns the
 | 
						|
// Hashes object for the given role - if it doesn't exist, returns an empty Hash
 | 
						|
// object (against which any checksum validation would fail).
 | 
						|
func (rb *repoBuilder) getChecksumsFor(role data.RoleName) *data.Hashes {
 | 
						|
	var hashes data.Hashes
 | 
						|
	switch role {
 | 
						|
	case data.CanonicalTimestampRole:
 | 
						|
		return nil
 | 
						|
	case data.CanonicalSnapshotRole:
 | 
						|
		if rb.repo.Timestamp == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		hashes = rb.repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole.String()].Hashes
 | 
						|
	default:
 | 
						|
		if rb.repo.Snapshot == nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		hashes = rb.repo.Snapshot.Signed.Meta[role.String()].Hashes
 | 
						|
	}
 | 
						|
	return &hashes
 | 
						|
}
 |