mirror of https://github.com/docker/docs.git
Builder interface cleanup and bugfixes:
- can never set an invalid repo, so we can remove the failed builder state - once a builder is "finished", it's swapped out with one that has no state and cannot be modified - add builder tests for the negative path cases where builder should error - fix bug with GenerateSnapshot where we didn't check for a targets to be loaded when generating for the first time This also adds some negative path tests (cases in which the builder errors due to invalid input or things not being loaded) Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
d1ccf0048d
commit
c9e91446a1
|
@ -642,7 +642,6 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
|
|||
// a not yet published repo or a possibly obsolete local copy) into
|
||||
// r.tufRepo. This attempts to load metadata for all roles. Since server
|
||||
// snapshots are supported, if the snapshot metadata fails to load, that's ok.
|
||||
// This can also be unified with some cache reading tools from tuf/client.
|
||||
// This assumes that bootstrapRepo is only used by Publish() or RotateKey()
|
||||
func (r *NotaryRepository) bootstrapRepo() error {
|
||||
b := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning)
|
||||
|
@ -653,6 +652,9 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
|||
jsonBytes, err := r.fileStore.GetMeta(role, -1)
|
||||
if err != nil {
|
||||
if _, ok := err.(store.ErrMetaNotFound); ok &&
|
||||
// server snapshots are supported, and server timestamp management
|
||||
// is required, so if either of these fail to load that's ok - especially
|
||||
// if the repo is new
|
||||
role == data.CanonicalSnapshotRole || role == data.CanonicalTimestampRole {
|
||||
continue
|
||||
}
|
||||
|
@ -767,7 +769,7 @@ func (r *NotaryRepository) Update(forWrite bool) error {
|
|||
// Returns a tufclient.Client for the remote server, which may not be actually
|
||||
// operational (if the URL is invalid but a root.json is cached).
|
||||
func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) {
|
||||
version := 0
|
||||
minVersion := 0
|
||||
oldBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning)
|
||||
var newBuilder tuf.RepoBuilder
|
||||
|
||||
|
@ -776,16 +778,16 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
// us to download a new root and perform a rotation.
|
||||
if rootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1); err == nil {
|
||||
// if we can't load the cached root, fail hard because that is how we pin trust
|
||||
if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, version, true); err != nil {
|
||||
if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use the old builder to bootstrap the new builder - we're just going to
|
||||
// verify the same data again, but with this time we want to validate the expiry
|
||||
version = oldBuilder.GetLoadedVersion(data.CanonicalRootRole)
|
||||
minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole)
|
||||
newBuilder = oldBuilder.BootstrapNewBuilder()
|
||||
// ignore error - if there's an error, the root won't be loaded
|
||||
newBuilder.Load(data.CanonicalRootRole, rootJSON, version, false)
|
||||
newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false)
|
||||
}
|
||||
|
||||
if newBuilder == nil {
|
||||
|
@ -810,7 +812,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
|
||||
if !newBuilder.IsLoaded(data.CanonicalRootRole) {
|
||||
// we always want to use the downloaded root if we couldn't load from cache
|
||||
if err := newBuilder.Load(data.CanonicalRootRole, tmpJSON, version, false); err != nil {
|
||||
if err := newBuilder.Load(data.CanonicalRootRole, tmpJSON, minVersion, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
337
tuf/builder.go
337
tuf/builder.go
|
@ -14,19 +14,8 @@ import (
|
|||
|
||||
// ErrBuildDone is returned when any functions are called on RepoBuilder, and it
|
||||
// is already finished building
|
||||
type ErrBuildDone struct{}
|
||||
|
||||
func (e ErrBuildDone) Error() string {
|
||||
return "the builder is done building and cannot accept any more input or produce any more output"
|
||||
}
|
||||
|
||||
// ErrBuildFailed is returned when any functions are called on RepoBuilder, and it
|
||||
// is already failed building and will not accept any other data
|
||||
type ErrBuildFailed struct{}
|
||||
|
||||
func (e ErrBuildFailed) Error() string {
|
||||
return "the builder has failed building and cannot accept any more input or produce any more output"
|
||||
}
|
||||
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 thes tate that it's in
|
||||
|
@ -79,20 +68,56 @@ type RepoBuilder interface {
|
|||
GetConsistentInfo(roleName string) ConsistentInfo
|
||||
}
|
||||
|
||||
// finishedBuilder refuses any more input or output
|
||||
type finishedBuilder struct{}
|
||||
|
||||
func (f finishedBuilder) Load(roleName string, content []byte, minVersion int, allowExpired 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, error) { return nil, ErrBuildDone }
|
||||
func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
|
||||
func (f finishedBuilder) IsLoaded(roleName string) bool { return false }
|
||||
func (f finishedBuilder) GetLoadedVersion(roleName string) int { return 0 }
|
||||
func (f finishedBuilder) GetConsistentInfo(roleName string) ConsistentInfo {
|
||||
return ConsistentInfo{RoleName: roleName}
|
||||
}
|
||||
|
||||
// NewRepoBuilder is the only way to get a pre-built RepoBuilder
|
||||
func NewRepoBuilder(gun string, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
|
||||
return &repoBuilder{
|
||||
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
|
||||
repo: NewRepo(cs),
|
||||
gun: gun,
|
||||
trustpin: trustpin,
|
||||
loadedNotChecksummed: make(map[string][]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, 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 {
|
||||
finished bool
|
||||
failed bool
|
||||
repo *Repo
|
||||
repo *Repo
|
||||
|
||||
// needed for root trust pininng verification
|
||||
gun string
|
||||
|
@ -103,38 +128,88 @@ type repoBuilder struct {
|
|||
// data with checksums come in
|
||||
loadedNotChecksummed map[string][]byte
|
||||
|
||||
// needed for bootstrapping a builder to validate a new root
|
||||
prevRoot *data.SignedRoot
|
||||
rootChecksum *data.FileMeta
|
||||
// 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, error) {
|
||||
if rb.finished {
|
||||
return nil, ErrBuildDone{}
|
||||
}
|
||||
|
||||
rb.finished = true
|
||||
return rb.repo, nil
|
||||
}
|
||||
|
||||
func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
|
||||
var rootChecksum *data.FileMeta
|
||||
|
||||
if rb.repo.Snapshot != nil {
|
||||
meta := rb.repo.Snapshot.Signed.Meta[data.CanonicalRootRole]
|
||||
rootChecksum = &meta
|
||||
}
|
||||
|
||||
return &repoBuilder{
|
||||
return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
|
||||
repo: NewRepo(rb.repo.cryptoService),
|
||||
gun: rb.gun,
|
||||
loadedNotChecksummed: make(map[string][]byte),
|
||||
trustpin: rb.trustpin,
|
||||
|
||||
prevRoot: rb.repo.Root,
|
||||
rootChecksum: rootChecksum,
|
||||
prevRoot: rb.repo.Root,
|
||||
bootstrappedRootChecksum: rb.nextRootChecksum,
|
||||
}}
|
||||
}
|
||||
|
||||
// IsLoaded returns whether a particular role has already been loaded
|
||||
func (rb *repoBuilder) IsLoaded(roleName string) 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 0 otherwise
|
||||
func (rb *repoBuilder) GetLoadedVersion(roleName string) 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 0
|
||||
}
|
||||
|
||||
// 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 string) 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]
|
||||
}
|
||||
case data.CanonicalRootRole:
|
||||
if rb.bootstrappedRootChecksum != nil {
|
||||
info.fileMeta = *rb.bootstrappedRootChecksum
|
||||
}
|
||||
default:
|
||||
if rb.repo.Snapshot != nil {
|
||||
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName]
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (rb *repoBuilder) Load(roleName string, content []byte, minVersion int, allowExpired bool) error {
|
||||
if !data.ValidRole(roleName) {
|
||||
return ErrInvalidBuilderInput{msg: fmt.Sprintf("%s is an invalid role", roleName)}
|
||||
|
@ -180,80 +255,42 @@ func (rb *repoBuilder) checkPrereqsLoaded(prereqRoles []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// IsLoaded returns whether a particular role has already been loaded
|
||||
func (rb *repoBuilder) IsLoaded(roleName string) 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 otherwise
|
||||
func (rb *repoBuilder) GetLoadedVersion(roleName string) 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
|
||||
func (rb *repoBuilder) GetConsistentInfo(roleName string) 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]
|
||||
}
|
||||
case data.CanonicalRootRole:
|
||||
if rb.rootChecksum != nil {
|
||||
info.fileMeta = *rb.rootChecksum
|
||||
}
|
||||
default:
|
||||
if rb.repo.Snapshot != nil {
|
||||
info.fileMeta = rb.repo.Snapshot.Signed.Meta[roleName]
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if rb.IsLoaded(data.CanonicalSnapshotRole) {
|
||||
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 rb.IsLoaded(data.CanonicalTimestampRole) {
|
||||
return nil, 0, ErrInvalidBuilderInput{msg: "Cannot generate snapshot if timestamp has already been loaded"}
|
||||
}
|
||||
|
||||
if err := rb.checkPrereqsLoaded([]string{data.CanonicalRootRole}); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if prev == nil {
|
||||
// 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([]string{data.CanonicalTargetsRole}); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if err := rb.repo.InitSnapshot(); err != nil {
|
||||
rb.repo.Snapshot = nil
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
if err := data.IsValidSnapshotStructure(prev.Signed); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rb.repo.Snapshot = prev
|
||||
}
|
||||
|
||||
|
@ -263,53 +300,56 @@ func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int,
|
|||
return nil, 0, err
|
||||
}
|
||||
|
||||
// verify that we have enough signatures to pass the threshold
|
||||
snapRole, err := rb.repo.GetBaseRole(data.CanonicalSnapshotRole)
|
||||
if err != nil { // this should never happen, since it's already been validated
|
||||
rb.repo.Snapshot = nil
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(sgnd.Signatures) < snapRole.Threshold {
|
||||
rb.repo.Snapshot = nil
|
||||
return nil, 0, signed.ErrRoleThreshold{}
|
||||
}
|
||||
|
||||
sgndJSON, err := json.Marshal(sgnd)
|
||||
if err != nil {
|
||||
rb.repo.Snapshot = nil
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// since the snapshot was generated using the root and targets data that
|
||||
// that have been loaded, remove all of them from rb.loadedNotChecksummed
|
||||
// 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, tgtName)
|
||||
}
|
||||
delete(rb.loadedNotChecksummed, data.CanonicalRootRole)
|
||||
|
||||
// cache the snapshot bytes so we can validate hte checksum in case a timestamp
|
||||
// is loaded later (which should not happen, because that's almost certain
|
||||
// to be an automatic failure)
|
||||
// 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) {
|
||||
if rb.IsLoaded(data.CanonicalTimestampRole) {
|
||||
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"}
|
||||
}
|
||||
|
||||
// SignTimetamp always serializes the loaded snapshot and signs in the data, so we must always
|
||||
// have the snapshot loaded first
|
||||
if err := rb.checkPrereqsLoaded([]string{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if prev == nil {
|
||||
switch prev {
|
||||
case nil:
|
||||
if err := rb.repo.InitTimestamp(); err != nil {
|
||||
rb.repo.Timestamp = nil
|
||||
return nil, 0, err
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
if err := data.IsValidTimestampStructure(prev.Signed); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rb.repo.Timestamp = prev
|
||||
}
|
||||
|
||||
|
@ -319,26 +359,18 @@ func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, in
|
|||
return nil, 0, err
|
||||
}
|
||||
|
||||
// verify that we have enough signatures to pass the threshold
|
||||
tsRole, err := rb.repo.GetBaseRole(data.CanonicalTimestampRole)
|
||||
if err != nil { // this should never happen, since it's already been validated
|
||||
rb.repo.Timestamp = nil
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(sgnd.Signatures) < tsRole.Threshold {
|
||||
rb.repo.Timestamp = nil
|
||||
return nil, 0, signed.ErrRoleThreshold{}
|
||||
}
|
||||
|
||||
sgndJSON, err := json.Marshal(sgnd)
|
||||
if err != nil {
|
||||
rb.repo.Timestamp = nil
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// since the timestamp was generated using the snapshot that has been loaded,
|
||||
// remove it from rb.loadedNotChecksummed
|
||||
// The snapshot should have been loaded (and not checksumemd, 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
|
||||
|
@ -409,8 +441,12 @@ func (rb *repoBuilder) loadTimestamp(content []byte, minVersion int, allowExpire
|
|||
}
|
||||
}
|
||||
|
||||
if err := rb.validateCachedTimestampChecksums(signedTimestamp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rb.repo.Timestamp = signedTimestamp
|
||||
return rb.validateCachedTimestampChecksums(signedTimestamp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired bool) error {
|
||||
|
@ -441,8 +477,19 @@ func (rb *repoBuilder) loadSnapshot(content []byte, minVersion int, allowExpired
|
|||
}
|
||||
}
|
||||
|
||||
// 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]
|
||||
rb.nextRootChecksum = &rootMeta
|
||||
|
||||
if err := rb.validateCachedSnapshotChecksums(signedSnapshot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rb.repo.Snapshot = signedSnapshot
|
||||
return rb.validateCachedSnapshotChecksums(signedSnapshot)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired bool) error {
|
||||
|
@ -508,24 +555,26 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio
|
|||
}
|
||||
|
||||
func (rb *repoBuilder) validateCachedTimestampChecksums(ts *data.SignedTimestamp) error {
|
||||
var err error
|
||||
sn, ok := rb.loadedNotChecksummed[data.CanonicalSnapshotRole]
|
||||
if ok {
|
||||
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
|
||||
err = data.CheckHashes(sn, data.CanonicalSnapshotRole, ts.Signed.Meta[data.CanonicalSnapshotRole].Hashes)
|
||||
if err != nil {
|
||||
rb.failed = true
|
||||
// by this point, the SignedTimestamp has been validated so it must have a snapshot hash
|
||||
snMeta := ts.Signed.Meta[data.CanonicalSnapshotRole].Hashes
|
||||
if err := data.CheckHashes(sn, data.CanonicalSnapshotRole, snMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(rb.loadedNotChecksummed, data.CanonicalSnapshotRole)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rb *repoBuilder) validateCachedSnapshotChecksums(sn *data.SignedSnapshot) error {
|
||||
var goodRoles []string
|
||||
for roleName, loadedBytes := range rb.loadedNotChecksummed {
|
||||
if roleName != data.CanonicalSnapshotRole {
|
||||
switch roleName {
|
||||
case data.CanonicalSnapshotRole, data.CanonicalTimestampRole:
|
||||
break
|
||||
default:
|
||||
if err := data.CheckHashes(loadedBytes, roleName, sn.Signed.Meta[roleName].Hashes); err != nil {
|
||||
rb.failed = true
|
||||
return err
|
||||
}
|
||||
goodRoles = append(goodRoles, roleName)
|
||||
|
@ -539,8 +588,8 @@ func (rb *repoBuilder) validateCachedSnapshotChecksums(sn *data.SignedSnapshot)
|
|||
|
||||
func (rb *repoBuilder) validateChecksumFor(content []byte, roleName string) error {
|
||||
// validate the bootstrap checksum for root, if provided
|
||||
if roleName == data.CanonicalRootRole && rb.rootChecksum != nil {
|
||||
if err := data.CheckHashes(content, roleName, rb.rootChecksum.Hashes); err != nil {
|
||||
if roleName == data.CanonicalRootRole && rb.bootstrappedRootChecksum != nil {
|
||||
if err := data.CheckHashes(content, roleName, rb.bootstrappedRootChecksum.Hashes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
package tuf_test
|
||||
|
||||
// package tuf_test to avoid an import cycle since we are using testutils.EmptyRepo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _cachedMeta map[string][]byte
|
||||
|
||||
// we just want sample metadata for a role - so we can build cached metadata
|
||||
// and use it once.
|
||||
func getSampleMeta(t *testing.T) (map[string][]byte, string) {
|
||||
gun := "docker.com/notary"
|
||||
delgNames := []string{"targets/a", "targets/a/b", "targets/a/b/force_parent_metadata"}
|
||||
if _cachedMeta == nil {
|
||||
meta, _, err := testutils.NewRepoMetadata(gun, delgNames...)
|
||||
require.NoError(t, err)
|
||||
|
||||
_cachedMeta = meta
|
||||
}
|
||||
return _cachedMeta, gun
|
||||
}
|
||||
|
||||
// We load only if the rolename is a valid rolename - even if the metadata we provided is valid
|
||||
func TestBuilderLoadsValidRolesOnly(t *testing.T) {
|
||||
meta, gun := getSampleMeta(t)
|
||||
builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
err := builder.Load("NotRoot", meta[data.CanonicalRootRole], 0, false)
|
||||
require.Error(t, err)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "is an invalid role")
|
||||
}
|
||||
|
||||
func TestBuilderOnlyAcceptsRootFirstWhenLoading(t *testing.T) {
|
||||
meta, gun := getSampleMeta(t)
|
||||
builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
|
||||
for roleName, content := range meta {
|
||||
if roleName != data.CanonicalRootRole {
|
||||
err := builder.Load(roleName, content, 0, true)
|
||||
require.Error(t, err)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "root must be loaded first")
|
||||
require.False(t, builder.IsLoaded(roleName))
|
||||
require.Equal(t, 0, builder.GetLoadedVersion(roleName))
|
||||
}
|
||||
}
|
||||
|
||||
// we can load the root
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
require.True(t, builder.IsLoaded(data.CanonicalRootRole))
|
||||
}
|
||||
|
||||
func TestBuilderOnlyAcceptsDelegationsAfterParent(t *testing.T) {
|
||||
meta, gun := getSampleMeta(t)
|
||||
builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
|
||||
// load the root
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
|
||||
// delegations can't be loaded without target
|
||||
for _, delgName := range []string{"targets/a", "targets/a/b"} {
|
||||
err := builder.Load(delgName, meta[delgName], 0, false)
|
||||
require.Error(t, err)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "targets must be loaded first")
|
||||
require.False(t, builder.IsLoaded(delgName))
|
||||
require.Equal(t, 0, builder.GetLoadedVersion(delgName))
|
||||
}
|
||||
|
||||
// load the targets
|
||||
require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 0, false))
|
||||
|
||||
// targets/a/b can't be loaded because targets/a isn't loaded
|
||||
err := builder.Load("targets/a/b", meta["targets/a/b"], 0, false)
|
||||
require.Error(t, err)
|
||||
require.IsType(t, data.ErrInvalidRole{}, err)
|
||||
|
||||
// targets/a can be loaded now though because targets is loaded
|
||||
require.NoError(t, builder.Load("targets/a", meta["targets/a"], 0, false))
|
||||
|
||||
// and now targets/a/b can be loaded because targets/a is loaded
|
||||
require.NoError(t, builder.Load("targets/a/b", meta["targets/a/b"], 0, false))
|
||||
}
|
||||
|
||||
func TestBuilderAcceptRoleOnce(t *testing.T) {
|
||||
meta, gun := getSampleMeta(t)
|
||||
builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
|
||||
for _, roleName := range append(data.BaseRoles, "targets/a", "targets/a/b") {
|
||||
// first time loading is ok
|
||||
require.NoError(t, builder.Load(roleName, meta[roleName], 0, false))
|
||||
require.True(t, builder.IsLoaded(roleName))
|
||||
require.Equal(t, 1, builder.GetLoadedVersion(roleName))
|
||||
|
||||
// second time loading is not
|
||||
err := builder.Load(roleName, meta[roleName], 0, false)
|
||||
require.Error(t, err)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "has already been loaded")
|
||||
|
||||
// still loaded
|
||||
require.True(t, builder.IsLoaded(roleName))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilderStopsAcceptingOrProducingDataOnceDone(t *testing.T) {
|
||||
meta, gun := getSampleMeta(t)
|
||||
builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
|
||||
for _, roleName := range data.BaseRoles {
|
||||
require.NoError(t, builder.Load(roleName, meta[roleName], 0, false))
|
||||
require.True(t, builder.IsLoaded(roleName))
|
||||
}
|
||||
|
||||
_, err := builder.Finish()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = builder.Load("targets/a", meta["targets/a"], 0, false)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tuf.ErrBuildDone, err)
|
||||
|
||||
// a new bootstrapped builder can also not have any more input output
|
||||
bootstrapped := builder.BootstrapNewBuilder()
|
||||
|
||||
err = bootstrapped.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tuf.ErrBuildDone, err)
|
||||
|
||||
for _, b := range []tuf.RepoBuilder{builder, bootstrapped} {
|
||||
_, err = b.Finish()
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tuf.ErrBuildDone, err)
|
||||
|
||||
_, _, err = b.GenerateSnapshot(nil)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tuf.ErrBuildDone, err)
|
||||
|
||||
_, _, err = b.GenerateTimestamp(nil)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tuf.ErrBuildDone, err)
|
||||
|
||||
for roleName := range meta {
|
||||
// a finished builder thinks nothing is loaded
|
||||
require.False(t, b.IsLoaded(roleName))
|
||||
// checksums are all empty, versions are all zero
|
||||
require.Equal(t, 0, b.GetLoadedVersion(roleName))
|
||||
require.Equal(t, tuf.ConsistentInfo{RoleName: roleName}, b.GetConsistentInfo(roleName))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Test the cases in which GenerateSnapshot fails
|
||||
func TestGenerateSnapshotInvalidOperations(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
repo, cs, err := testutils.EmptyRepo(gun)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make snapshot have 2 keys and a threshold of 2
|
||||
snapKeys := make([]data.PublicKey, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
snapKeys[i], err = cs.Create(data.CanonicalSnapshotRole, gun, data.ECDSAKey)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.NoError(t, repo.ReplaceBaseKeys(data.CanonicalSnapshotRole, snapKeys...))
|
||||
repo.Root.Signed.Roles[data.CanonicalSnapshotRole].Threshold = 2
|
||||
|
||||
meta, err := testutils.SignAndSerialize(repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, prevSnapshot := range []*data.SignedSnapshot{nil, repo.Snapshot} {
|
||||
// copy keys, since we expect one of these generation attempts to succeed and we do
|
||||
// some key deletion tests later
|
||||
newCS := testutils.CopyKeys(t, cs, data.CanonicalSnapshotRole)
|
||||
|
||||
// --- we can't generate a snapshot if the root isn't loaded
|
||||
builder := tuf.NewRepoBuilder(gun, newCS, trustpinning.TrustPinConfig{})
|
||||
_, _, err := builder.GenerateSnapshot(prevSnapshot)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "root must be loaded first")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||
|
||||
// --- we can't generate a snapshot if the targets isn't loaded and we have no previous snapshot,
|
||||
// --- but if we have a previous snapshot with a valid targets, we're good even if no snapshot
|
||||
// --- is loaded
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
_, _, err = builder.GenerateSnapshot(prevSnapshot)
|
||||
if prevSnapshot == nil {
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "targets must be loaded first")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// --- we can't generate a snapshot if we've loaded the timestamp already
|
||||
builder = tuf.NewRepoBuilder(gun, newCS, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
if prevSnapshot == nil {
|
||||
require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 0, false))
|
||||
}
|
||||
require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateSnapshot(prevSnapshot)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "cannot generate snapshot if timestamp has already been loaded")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||
|
||||
// --- we cannot generate a snapshot if we've already loaded a snapshot
|
||||
builder = tuf.NewRepoBuilder(gun, newCS, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
if prevSnapshot == nil {
|
||||
require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 0, false))
|
||||
}
|
||||
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateSnapshot(prevSnapshot)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "snapshot has already been loaded")
|
||||
|
||||
// --- we cannot generate a snapshot if we can't satisfy the role threshold
|
||||
for i := 0; i < len(snapKeys); i++ {
|
||||
require.NoError(t, newCS.RemoveKey(snapKeys[i].ID()))
|
||||
builder = tuf.NewRepoBuilder(gun, newCS, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
if prevSnapshot == nil {
|
||||
require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 0, false))
|
||||
}
|
||||
|
||||
_, _, err = builder.GenerateSnapshot(prevSnapshot)
|
||||
require.IsType(t, signed.ErrInsufficientSignatures{}, err)
|
||||
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||
}
|
||||
|
||||
// --- we cannot generate a snapshot if we don't have a cryptoservice
|
||||
builder = tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
if prevSnapshot == nil {
|
||||
require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 0, false))
|
||||
}
|
||||
|
||||
_, _, err = builder.GenerateSnapshot(prevSnapshot)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "cannot generate snapshot without a cryptoservice")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||
}
|
||||
|
||||
// --- we can't generate a snapshot if we're given an invalid previous snapshot (for instance, an empty one),
|
||||
// --- even if we have a targets loaded
|
||||
builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateSnapshot(&data.SignedSnapshot{})
|
||||
require.IsType(t, data.ErrInvalidMetadata{}, err)
|
||||
require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole))
|
||||
}
|
||||
|
||||
// Test the cases in which GenerateTimestamp fails
|
||||
func TestGenerateTimestampInvalidOperations(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
repo, cs, err := testutils.EmptyRepo(gun)
|
||||
require.NoError(t, err)
|
||||
|
||||
// make timsetamp have 2 keys and a threshold of 2
|
||||
tsKeys := make([]data.PublicKey, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
tsKeys[i], err = cs.Create(data.CanonicalTimestampRole, gun, data.ECDSAKey)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.NoError(t, repo.ReplaceBaseKeys(data.CanonicalTimestampRole, tsKeys...))
|
||||
repo.Root.Signed.Roles[data.CanonicalTimestampRole].Threshold = 2
|
||||
|
||||
meta, err := testutils.SignAndSerialize(repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, prevTimestamp := range []*data.SignedTimestamp{nil, repo.Timestamp} {
|
||||
// --- we can't generate a timestamp if the root isn't loaded
|
||||
builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
|
||||
_, _, err := builder.GenerateTimestamp(prevTimestamp)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "root must be loaded first")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||
|
||||
// --- we can't generate a timestamp if the snapshot isn't loaded, no matter if we have a previous
|
||||
// --- timestamp or not
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
_, _, err = builder.GenerateTimestamp(prevTimestamp)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "snapshot must be loaded first")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||
|
||||
// --- we can't generate a timestamp if we've loaded the timestamp already
|
||||
builder = tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||
require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateTimestamp(prevTimestamp)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "timestamp has already been loaded")
|
||||
|
||||
// --- we cannot generate a timestamp if we can't satisfy the role threshold
|
||||
for i := 0; i < len(tsKeys); i++ {
|
||||
require.NoError(t, cs.RemoveKey(tsKeys[i].ID()))
|
||||
builder = tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateTimestamp(prevTimestamp)
|
||||
require.IsType(t, signed.ErrInsufficientSignatures{}, err)
|
||||
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||
}
|
||||
|
||||
// --- we cannot generate a timestamp if we don't have a cryptoservice
|
||||
builder = tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateTimestamp(prevTimestamp)
|
||||
require.IsType(t, tuf.ErrInvalidBuilderInput{}, err)
|
||||
require.Contains(t, err.Error(), "cannot generate timestamp without a cryptoservice")
|
||||
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||
}
|
||||
|
||||
// --- we can't generate a timsetamp if we're given an invalid previous timestamp (for instance, an empty one),
|
||||
// --- even if we have a snapshot loaded
|
||||
builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
|
||||
require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false))
|
||||
require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 0, false))
|
||||
|
||||
_, _, err = builder.GenerateTimestamp(&data.SignedTimestamp{})
|
||||
require.IsType(t, data.ErrInvalidMetadata{}, err)
|
||||
require.False(t, builder.IsLoaded(data.CanonicalTimestampRole))
|
||||
}
|
|
@ -181,7 +181,8 @@ func (c Client) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// we know it unmarshals fine
|
||||
// we know it unmarshals because if `tryLoadCacheThenRemote` didn't fail, then
|
||||
// the raw has already been loaded into the builder
|
||||
json.Unmarshal(raw, tgs)
|
||||
return tgs.GetValidDelegations(role), nil
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ type Snapshot struct {
|
|||
Meta Files `json:"meta"`
|
||||
}
|
||||
|
||||
// isValidSnapshotStructure returns an error, or nil, depending on whether the content of the
|
||||
// IsValidSnapshotStructure returns an error, or nil, depending on whether the content of the
|
||||
// struct is valid for snapshot metadata. This does not check signatures or expiry, just that
|
||||
// the metadata content is valid.
|
||||
func isValidSnapshotStructure(s Snapshot) error {
|
||||
func IsValidSnapshotStructure(s Snapshot) error {
|
||||
expectedType := TUFTypes[CanonicalSnapshotRole]
|
||||
if s.Type != expectedType {
|
||||
return ErrInvalidMetadata{
|
||||
|
@ -157,7 +157,7 @@ func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
|
|||
if err := defaultSerializer.Unmarshal(*s.Signed, &sp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := isValidSnapshotStructure(sp); err != nil {
|
||||
if err := IsValidSnapshotStructure(sp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := make([]Signature, len(s.Signatures))
|
||||
|
|
|
@ -21,10 +21,10 @@ type Timestamp struct {
|
|||
Meta Files `json:"meta"`
|
||||
}
|
||||
|
||||
// isValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
|
||||
// IsValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
|
||||
// is valid for timestamp metadata. This does not check signatures or expiry, just that
|
||||
// the metadata content is valid.
|
||||
func isValidTimestampStructure(t Timestamp) error {
|
||||
func IsValidTimestampStructure(t Timestamp) error {
|
||||
expectedType := TUFTypes[CanonicalTimestampRole]
|
||||
if t.Type != expectedType {
|
||||
return ErrInvalidMetadata{
|
||||
|
@ -124,7 +124,7 @@ func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
|
|||
if err := defaultSerializer.Unmarshal(*s.Signed, &ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := isValidTimestampStructure(ts); err != nil {
|
||||
if err := IsValidTimestampStructure(ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := make([]Signature, len(s.Signatures))
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go/canonical/json"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
fuzz "github.com/google/gofuzz"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tuf "github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
|
@ -52,6 +54,19 @@ func CreateKey(cs signed.CryptoService, gun, role, keyAlgorithm string) (data.Pu
|
|||
return key, nil
|
||||
}
|
||||
|
||||
// CopyKeys copies keys of a particular role to a new cryptoservice, and returns that cryptoservice
|
||||
func CopyKeys(t *testing.T, from signed.CryptoService, roles ...string) signed.CryptoService {
|
||||
memKeyStore := trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("pass"))
|
||||
for _, role := range roles {
|
||||
for _, keyID := range from.ListKeys(role) {
|
||||
key, _, err := from.GetPrivateKey(keyID)
|
||||
require.NoError(t, err)
|
||||
memKeyStore.AddKey(trustmanager.KeyInfo{Role: role}, key)
|
||||
}
|
||||
}
|
||||
return cryptoservice.NewCryptoService(memKeyStore)
|
||||
}
|
||||
|
||||
// EmptyRepo creates an in memory crypto service
|
||||
// and initializes a repo with no targets. Delegations are only created
|
||||
// if delegation roles are passed in.
|
||||
|
|
Loading…
Reference in New Issue