// No matter what order timestamp and snapshot is loaded, if the snapshot's checksum doesn't match // what's in the timestamp, the builder will error and refuse to load the latest piece of metadata // whether that is snapshot (because it was loaded after timestamp) or timestamp (because builder // retroactive checks the loaded snapshot's checksum). Timestamp ONLY checks the snapshot checksum. func TestTimestampPreAndPostChecksumming(t *testing.T) { gun := "docker.com/notary" repo, _, err := testutils.EmptyRepo(gun, "targets/other", "targets/other/other") require.NoError(t, err) // add invalid checkums for all the other roles to timestamp too, and show that // cached items aren't checksummed against this fakeChecksum, err := data.NewFileMeta(bytes.NewBuffer([]byte("fake")), notary.SHA256, notary.SHA512) require.NoError(t, err) for _, roleName := range append(data.BaseRoles, "targets/other") { // add a wrong checksum for every role, including timestamp itself repo.Timestamp.Signed.Meta[roleName] = fakeChecksum } // this will overwrite the snapshot checksum with the right one meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) // ensure that the fake meta for other roles weren't destroyed by signing the timestamp require.Len(t, repo.Timestamp.Signed.Meta, 5) snapJSON := append(meta[data.CanonicalSnapshotRole], ' ') // --- load timestamp first builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 1, false)) // timestamp doesn't fail, even though its checksum for root is wrong according to timestamp require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, false)) // loading the snapshot in fails, because of the checksum the timestamp has err = builder.Load(data.CanonicalSnapshotRole, snapJSON, 1, false) require.Error(t, err) require.IsType(t, data.ErrMismatchedChecksum{}, err) require.True(t, builder.IsLoaded(data.CanonicalTimestampRole)) require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole)) // all the other metadata can be loaded in, even though the checksums are wrong according to timestamp for _, roleName := range []string{data.CanonicalTargetsRole, "targets/other"} { require.NoError(t, builder.Load(roleName, meta[roleName], 1, false)) } // --- load snapshot first builder = tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) for _, roleName := range append(data.BaseRoles, "targets/other") { switch roleName { case data.CanonicalTimestampRole: continue case data.CanonicalSnapshotRole: require.NoError(t, builder.Load(roleName, snapJSON, 1, false)) default: require.NoError(t, builder.Load(roleName, meta[roleName], 1, false)) } } // timestamp fails because the snapshot checksum is wrong err = builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, false) require.Error(t, err) checksumErr, ok := err.(data.ErrMismatchedChecksum) require.True(t, ok) require.Contains(t, checksumErr.Error(), "checksum for snapshot did not match") require.False(t, builder.IsLoaded(data.CanonicalTimestampRole)) require.True(t, builder.IsLoaded(data.CanonicalSnapshotRole)) }
// If any other metadata is loaded first, when the snapshot is loaded it will retroactively go back // and validate that metadata. If anything fails to validate, or there is metadata for which this // snapshot has no checksums for, the snapshot will fail to validate. func TestSnapshotLoadedAfterChecksumsOthersRetroactively(t *testing.T) { gun := "docker.com/notary" meta := setupSnapshotChecksumming(t, gun) // --- load all the other metadata first, but with an extra space at the end which should // --- validate fine, except for the checksum. for _, roleNameToPermute := range append(data.BaseRoles, "targets/other") { builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) if roleNameToPermute == data.CanonicalSnapshotRole { continue } // load all the roles normally, except for roleToPermute, which has one space added // to the end, thus changing the checksum for _, roleNameToLoad := range append(data.BaseRoles, "targets/other") { switch roleNameToLoad { case data.CanonicalSnapshotRole: continue // we load this later case roleNameToPermute: // having a space added at the end should not affect any validity check except checksum require.NoError(t, builder.Load(roleNameToLoad, append(meta[roleNameToLoad], ' '), 0, false)) default: require.NoError(t, builder.Load(roleNameToLoad, meta[roleNameToLoad], 1, false)) } require.True(t, builder.IsLoaded(roleNameToLoad)) } // now load the snapshot - it should fail with the checksum failure for the permuted role err := builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false) switch roleNameToPermute { case data.CanonicalTimestampRole: require.NoError(t, err) // we don't check the timestamp's checksum default: require.Error(t, err) checksumErr, ok := err.(data.ErrMismatchedChecksum) require.True(t, ok) require.Contains(t, checksumErr.Error(), fmt.Sprintf("checksum for %s did not match", roleNameToPermute)) require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole)) } } // load all the metadata as is without alteration (so they should validate all checksums) // but also load the metadata that is not contained in the snapshot. Then when the snapshot // is loaded it will fail validation, because it doesn't have target/other/other's checksum builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) for _, roleNameToLoad := range append(data.BaseRoles, "targets/other", "targets/other/other") { if roleNameToLoad == data.CanonicalSnapshotRole { continue } require.NoError(t, builder.Load(roleNameToLoad, meta[roleNameToLoad], 1, false)) } err := builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false) require.Error(t, err) require.IsType(t, data.ErrMissingMeta{}, err) }
// If the snapshot is loaded first (-ish, because really root has to be loaded first) // it will be used to validate the checksums of all other metadata that gets loaded. // If the checksum doesn't match, or if there is no checksum, then the other metadata // cannot be loaded. func TestSnapshotLoadedFirstChecksumsOthers(t *testing.T) { gun := "docker.com/notary" meta := setupSnapshotChecksumming(t, gun) // --- load root then snapshot builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 1, false)) require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false)) // loading timestamp is fine, even though the timestamp metadata has the wrong checksum because // we don't check timestamp checksums require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, false)) // loading the other roles' metadata with a space will fail because of a checksum failure (builder // checks right away if the snapshot is loaded) - in the case of targets/other/other, which should // not be in snapshot at all, loading should fail even without a space because there is no checksum // for it for _, roleNameToLoad := range []string{data.CanonicalTargetsRole, "targets/other"} { err := builder.Load(roleNameToLoad, append(meta[roleNameToLoad], ' '), 0, false) require.Error(t, err) checksumErr, ok := err.(data.ErrMismatchedChecksum) require.True(t, ok) require.Contains(t, checksumErr.Error(), fmt.Sprintf("checksum for %s did not match", roleNameToLoad)) require.False(t, builder.IsLoaded(roleNameToLoad)) // now load it for real (since we need targets loaded before trying to load "targets/other") require.NoError(t, builder.Load(roleNameToLoad, meta[roleNameToLoad], 1, false)) } // loading the non-existent role wil fail err := builder.Load("targets/other/other", meta["targets/other/other"], 1, false) require.Error(t, err) require.IsType(t, data.ErrMissingMeta{}, err) require.False(t, builder.IsLoaded("targets/other/other")) }
// ### generateSnapshot tests ### func TestGenerateSnapshotRootNotLoaded(t *testing.T) { gun := "docker.com/notary" builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) _, err := generateSnapshot(gun, builder, storage.NewMemStorage()) require.Error(t, err) require.IsType(t, validation.ErrValidation{}, err) }
// CreateTimestamp creates a new timestamp. If a prev timestamp is provided, it // is assumed this is the immediately previous one, and the new one will have a // version number one higher than prev. The store is used to lookup the current // snapshot, this function does not save the newly generated timestamp. func createTimestamp(gun string, prev *data.SignedTimestamp, snapshot []byte, store storage.MetaStore, cryptoService signed.CryptoService) (*storage.MetaUpdate, error) { builder := tuf.NewRepoBuilder(gun, cryptoService, trustpinning.TrustPinConfig{}) // load the current root to ensure we use the correct timestamp key. _, root, err := store.GetCurrent(gun, data.CanonicalRootRole) if err != nil { logrus.Debug("Previous timestamp, but no root for GUN ", gun) return nil, err } if err := builder.Load(data.CanonicalRootRole, root, 1, false); err != nil { logrus.Debug("Could not load valid previous root for GUN ", gun) return nil, err } // load snapshot so we can include it in timestamp if err := builder.Load(data.CanonicalSnapshotRole, snapshot, 1, false); err != nil { logrus.Debug("Could not load valid previous snapshot for GUN ", gun) return nil, err } meta, ver, err := builder.GenerateTimestamp(prev) if err != nil { return nil, err } return &storage.MetaUpdate{ Role: data.CanonicalTimestampRole, Version: ver, Data: meta, }, nil }
// bootstrapRepo loads the repository from the local file system (i.e. // 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 assumes that bootstrapRepo is only used by Publish() or RotateKey() func (r *NotaryRepository) bootstrapRepo() error { b := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning) logrus.Debugf("Loading trusted collection.") for _, role := range data.BaseRoles { jsonBytes, err := r.fileStore.GetMeta(role, store.NoSizeLimit) 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 } return err } if err := b.Load(role, jsonBytes, 1, true); err != nil { return err } } tufRepo, err := b.Finish() if err == nil { r.tufRepo = tufRepo } return nil }
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], 1, false)) // delegations can't be loaded without target for _, delgName := range []string{"targets/a", "targets/a/b"} { err := builder.Load(delgName, meta[delgName], 1, 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, 1, builder.GetLoadedVersion(delgName)) } // load the targets require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, false)) // targets/a/b can't be loaded because targets/a isn't loaded err := builder.Load("targets/a/b", meta["targets/a/b"], 1, 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"], 1, 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"], 1, false)) }
// 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], 1, false) require.Error(t, err) require.IsType(t, tuf.ErrInvalidBuilderInput{}, err) require.Contains(t, err.Error(), "is an invalid role") }
func TestMarkingIsValid(t *testing.T) { meta, gun := getSampleMeta(t) builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) // testing that the signed objects have a false isValid value confirming // that verify signatures has not been called on them yet // now when we check that isValid is true after calling load which calls // verify signatures- we can be sure that verify signatures is actually // setting the isValid fields for our data.Signed objects for _, meta := range meta { signedObj := &data.Signed{} if err := json.Unmarshal(meta, signedObj); err != nil { require.NoError(t, err) } require.Len(t, signedObj.Signatures, 1) require.False(t, signedObj.Signatures[0].IsValid) } // load the root require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 1, false)) // load a timestamp require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, false)) // load a snapshot require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false)) // load the targets require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, false)) // targets/a can be loaded now though because targets is loaded require.NoError(t, builder.Load("targets/a", meta["targets/a"], 1, 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"], 1, false)) valid, _, err := builder.Finish() // TODO: Once ValidateRoot is changes to set IsValid as per PR #800 we should uncomment the below test to make // sure that IsValid is being set on loadRoot //require.True(t, valid.Root.Signatures[0].IsValid) require.True(t, valid.Timestamp.Signatures[0].IsValid) require.True(t, valid.Snapshot.Signatures[0].IsValid) require.True(t, valid.Targets[data.CanonicalTargetsRole].Signatures[0].IsValid) require.True(t, valid.Targets["targets/a"].Signatures[0].IsValid) require.True(t, valid.Targets["targets/a/b"].Signatures[0].IsValid) require.NoError(t, err) }
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], 1, false)) require.True(t, builder.IsLoaded(roleName)) } _, err := builder.Finish() require.NoError(t, err) err = builder.Load("targets/a", meta["targets/a"], 1, 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)) } } }
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, 1, 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, 1, builder.GetLoadedVersion(roleName)) } } // we can load the root require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 1, false)) require.True(t, builder.IsLoaded(data.CanonicalRootRole)) }
// If the parent is not in the store, then the parent must be in the update else // validation fails. func TestValidateTargetsParentInUpdate(t *testing.T) { gun := "docker.com/notary" delgName := "targets/level1" metadata, _, err := testutils.NewRepoMetadata(gun, delgName, path.Join(delgName, "other")) require.NoError(t, err) store := storage.NewMemStorage() // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) targetsUpdate := storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: []byte("Invalid metadata"), } delgUpdate := storage.MetaUpdate{ Role: delgName, Version: 1, Data: metadata[delgName], } upload := map[string]storage.MetaUpdate{ "targets/level1": delgUpdate, data.CanonicalTargetsRole: targetsUpdate, } // parent update not readable - fail _, err = loadAndValidateTargets(gun, builder, upload, store) require.Error(t, err) require.IsType(t, validation.ErrBadTargets{}, err) // because we sort the roles, the list of returned updates // will contain shallower roles first, in this case "targets", // and then "targets/level1" targetsUpdate.Data = metadata[data.CanonicalTargetsRole] upload[data.CanonicalTargetsRole] = targetsUpdate updates, err := loadAndValidateTargets(gun, builder, upload, store) require.NoError(t, err) require.Equal(t, []storage.MetaUpdate{targetsUpdate, delgUpdate}, updates) }
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], 1, 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], 1, 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 TestGenerateSnapshotNoKey(t *testing.T) { gun := "docker.com/notary" metadata, cs, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) store := storage.NewMemStorage() // delete snapshot key in the cryptoservice for _, keyID := range cs.ListKeys(data.CanonicalSnapshotRole) { require.NoError(t, cs.RemoveKey(keyID)) } builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{}) // only load root and targets require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) require.NoError(t, builder.Load(data.CanonicalTargetsRole, metadata[data.CanonicalTargetsRole], 0, false)) _, err = generateSnapshot(gun, builder, store) require.Error(t, err) require.IsType(t, validation.ErrBadHierarchy{}, err) }
// ### Target validation with delegations tests func TestLoadTargetsLoadsNothingIfNoUpdates(t *testing.T) { gun := "docker.com/notary" metadata, _, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) store := storage.NewMemStorage() store.UpdateCurrent(gun, storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: metadata[data.CanonicalTargetsRole], }) // if no updates, nothing is loaded targetsToUpdate, err := loadAndValidateTargets(gun, builder, nil, store) require.Empty(t, targetsToUpdate) require.NoError(t, err) require.False(t, builder.IsLoaded(data.CanonicalTargetsRole)) }
// GetOrCreateSnapshot either returns the existing latest snapshot, or uses // whatever the most recent snapshot is to generate the next one, only updating // the expiry time and version. Note that this function does not write generated // snapshots to the underlying data store, and will either return the latest snapshot time // or nil as the time modified func GetOrCreateSnapshot(gun, checksum string, store storage.MetaStore, cryptoService signed.CryptoService) ( *time.Time, []byte, error) { lastModified, currentJSON, err := store.GetChecksum(gun, data.CanonicalSnapshotRole, checksum) if err != nil { return nil, nil, err } prev := new(data.SignedSnapshot) if err := json.Unmarshal(currentJSON, prev); err != nil { logrus.Error("Failed to unmarshal existing snapshot for GUN ", gun) return nil, nil, err } if !snapshotExpired(prev) { return lastModified, currentJSON, nil } builder := tuf.NewRepoBuilder(gun, cryptoService, trustpinning.TrustPinConfig{}) // load the current root to ensure we use the correct snapshot key. _, rootJSON, err := store.GetCurrent(gun, data.CanonicalRootRole) if err != nil { logrus.Debug("Previous snapshot, but no root for GUN ", gun) return nil, nil, err } if err := builder.Load(data.CanonicalRootRole, rootJSON, 1, false); err != nil { logrus.Debug("Could not load valid previous root for GUN ", gun) return nil, nil, err } meta, _, err := builder.GenerateSnapshot(prev) if err != nil { return nil, nil, err } return nil, meta, nil }
// When a delegation role appears in the update and the parent does not, the // parent is loaded from the DB if it can func TestValidateTargetsRequiresStoredParent(t *testing.T) { gun := "docker.com/notary" delgName := "targets/level1" metadata, _, err := testutils.NewRepoMetadata(gun, delgName, path.Join(delgName, "other")) require.NoError(t, err) // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, metadata[data.CanonicalRootRole], 0, false)) delUpdate := storage.MetaUpdate{ Role: delgName, Version: 1, Data: metadata[delgName], } upload := map[string]storage.MetaUpdate{delgName: delUpdate} store := storage.NewMemStorage() // if the DB has no "targets" role _, err = loadAndValidateTargets(gun, builder, upload, store) require.Error(t, err) require.IsType(t, validation.ErrBadTargets{}, err) // ensure the "targets" (the parent) is in the "db" store.UpdateCurrent(gun, storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: metadata[data.CanonicalTargetsRole], }) updates, err := loadAndValidateTargets(gun, builder, upload, store) require.NoError(t, err) require.Len(t, updates, 1) require.Equal(t, delgName, updates[0].Role) require.Equal(t, metadata[delgName], updates[0].Data) }
// bootstrapClient attempts to bootstrap a root.json to be used as the trust // anchor for a repository. The checkInitialized argument indicates whether // we should always attempt to contact the server to determine if the repository // is initialized or not. If set to true, we will always attempt to download // and return an error if the remote repository errors. // // Populates a tuf.RepoBuilder with this root metadata (only use // tufclient.Client.Update to load the rest). // // Fails if the remote server is reachable and does not know the repo // (i.e. before the first r.Publish()), in which case the error is // store.ErrMetaNotFound, or if the root metadata (from whichever source is used) // is not trusted. // // 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) { minVersion := 1 // the old root on disk should not be validated against any trust pinning configuration // because if we have an old root, it itself is the thing that pins trust oldBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, trustpinning.TrustPinConfig{}) // by default, we want to use the trust pinning configuration on any new root that we download newBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning) // Try to read root from cache first. We will trust this root until we detect a problem // during update which will cause us to download a new root and perform a rotation. // If we have an old root, and it's valid, then we overwrite the newBuilder to be one // preloaded with the old root or one which uses the old root for trust bootstrapping. if rootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, store.NoSizeLimit); 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, minVersion, true); err != nil { return nil, err } // again, the root on disk is the source of trust pinning, so use an empty trust // pinning configuration newBuilder = tuf.NewRepoBuilder(r.gun, r.CryptoService, trustpinning.TrustPinConfig{}) if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil { // Ok, the old root is expired - we want to download a new one. But we want to use the // old root to verify the new root, so bootstrap a new builder with the old builder minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole) newBuilder = oldBuilder.BootstrapNewBuilder() } } remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if remoteErr != nil { logrus.Error(remoteErr) } else if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized { // remoteErr was nil and we were not able to load a root from cache or // are specifically checking for initialization of the repo. // if remote store successfully set up, try and get root from remote // We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB) tmpJSON, err := remote.GetMeta(data.CanonicalRootRole, store.NoSizeLimit) if err != nil { // we didn't have a root in cache and were unable to load one from // the server. Nothing we can do but error. return nil, err } 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, minVersion, false); err != nil { return nil, err } err = r.fileStore.SetMeta(data.CanonicalRootRole, tmpJSON) if err != nil { // if we can't write cache we should still continue, just log error logrus.Errorf("could not save root to cache: %s", err.Error()) } } } // We can only get here if remoteErr != nil (hence we don't download any new root), // and there was no root on disk if !newBuilder.IsLoaded(data.CanonicalRootRole) { return nil, ErrRepoNotInitialized{} } return tufclient.NewClient(oldBuilder, newBuilder, remote, r.fileStore), nil }
// validateUpload checks that the updates being pushed // are semantically correct and the signatures are correct // A list of possibly modified updates are returned if all // validation was successful. This allows the snapshot to be // created and added if snapshotting has been delegated to the // server func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) { // some delegated targets role may be invalid based on other updates // that have been made by other clients. We'll rebuild the slice of // updates with only the things we should actually update updatesToApply := make([]storage.MetaUpdate, 0, len(updates)) roles := make(map[string]storage.MetaUpdate) for _, v := range updates { roles[v.Role] = v } builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{}) if err := loadFromStore(gun, data.CanonicalRootRole, builder, store); err != nil { if _, ok := err.(storage.ErrNotFound); !ok { return nil, err } } if rootUpdate, ok := roles[data.CanonicalRootRole]; ok { builder = builder.BootstrapNewBuilder() if err := builder.Load(data.CanonicalRootRole, rootUpdate.Data, 1, false); err != nil { return nil, validation.ErrBadRoot{Msg: err.Error()} } logrus.Debug("Successfully validated root") updatesToApply = append(updatesToApply, rootUpdate) } else if !builder.IsLoaded(data.CanonicalRootRole) { return nil, validation.ErrValidation{Msg: "no pre-existing root and no root provided in update."} } targetsToUpdate, err := loadAndValidateTargets(gun, builder, roles, store) if err != nil { return nil, err } updatesToApply = append(updatesToApply, targetsToUpdate...) // there's no need to load files from the database if no targets etc... // were uploaded because that means they haven't been updated and // the snapshot will already contain the correct hashes and sizes for // those targets (incl. delegated targets) logrus.Debug("Successfully validated targets") // At this point, root and targets must have been loaded into the repo if snapshotUpdate, ok := roles[data.CanonicalSnapshotRole]; ok { if err := builder.Load(data.CanonicalSnapshotRole, snapshotUpdate.Data, 1, false); err != nil { return nil, validation.ErrBadSnapshot{Msg: err.Error()} } logrus.Debug("Successfully validated snapshot") updatesToApply = append(updatesToApply, roles[data.CanonicalSnapshotRole]) } else { // Check: // - we have a snapshot key // - it matches a snapshot key signed into the root.json // Then: // - generate a new snapshot // - add it to the updates update, err := generateSnapshot(gun, builder, store) if err != nil { return nil, err } updatesToApply = append(updatesToApply, *update) } // generate a timestamp immediately update, err := generateTimestamp(gun, builder, store) if err != nil { return nil, err } return append(updatesToApply, *update), nil }
// If the parent, either from the DB or from an update, does not contain the role // of the delegation update, validation fails func TestValidateTargetsRoleNotInParent(t *testing.T) { // no delegation at first gun := "docker.com/notary" repo, cs, err := testutils.EmptyRepo(gun) require.NoError(t, err) meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) // load the root into the builder, else we can't load anything else builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) require.NoError(t, builder.Load(data.CanonicalRootRole, meta[data.CanonicalRootRole], 0, false)) // prepare the original targets file, without a delegation role, as an update origTargetsUpdate := storage.MetaUpdate{ Role: data.CanonicalTargetsRole, Version: 1, Data: meta[data.CanonicalTargetsRole], } emptyStore := storage.NewMemStorage() storeWithParent := storage.NewMemStorage() storeWithParent.UpdateCurrent(gun, origTargetsUpdate) // add a delegation role now delgName := "targets/level1" level1Key, err := testutils.CreateKey(cs, gun, delgName, data.ECDSAKey) require.NoError(t, err) require.NoError(t, repo.UpdateDelegationKeys(delgName, []data.PublicKey{level1Key}, []string{}, 1)) // create the delegation metadata too repo.InitTargets(delgName) // re-serialize meta, err = testutils.SignAndSerialize(repo) require.NoError(t, err) delgMeta, ok := meta[delgName] require.True(t, ok) delgUpdate := storage.MetaUpdate{ Role: delgName, Version: 1, Data: delgMeta, } // parent in update does not have this role, whether or not there's a parent in the store, // so validation fails roles := map[string]storage.MetaUpdate{ delgName: delgUpdate, data.CanonicalTargetsRole: origTargetsUpdate, } for _, metaStore := range []storage.MetaStore{emptyStore, storeWithParent} { updates, err := loadAndValidateTargets(gun, builder, roles, metaStore) require.Error(t, err) require.Empty(t, updates) require.IsType(t, validation.ErrBadTargets{}, err) } // if the update is provided without the parent, then the parent from the // store is loaded - if it doesn't have the role, then the update fails updates, err := loadAndValidateTargets(gun, builder, map[string]storage.MetaUpdate{delgName: delgUpdate}, storeWithParent) require.Error(t, err) require.Empty(t, updates) require.IsType(t, validation.ErrBadTargets{}, err) }
// 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], 1, 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], 1, false)) if prevSnapshot == nil { require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, false)) } require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, 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], 1, false)) if prevSnapshot == nil { require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, false)) } require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, 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], 1, false)) if prevSnapshot == nil { require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, 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], 1, false)) if prevSnapshot == nil { require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, 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], 1, false)) require.NoError(t, builder.Load(data.CanonicalTargetsRole, meta[data.CanonicalTargetsRole], 1, false)) _, _, err = builder.GenerateSnapshot(&data.SignedSnapshot{}) require.IsType(t, data.ErrInvalidMetadata{}, err) require.False(t, builder.IsLoaded(data.CanonicalSnapshotRole)) }
func TestGetConsistentInfo(t *testing.T) { gun := "docker.com/notary" repo, _, err := testutils.EmptyRepo(gun) require.NoError(t, err) // add some hashes for items in the snapshot that don't correspond to real metadata, but that // will cause ConsistentInfo to behave differently realSha512Sum := sha512.Sum512([]byte("stuff")) repo.Snapshot.Signed.Meta["only512"] = data.FileMeta{Hashes: data.Hashes{notary.SHA512: realSha512Sum[:]}} repo.Snapshot.Signed.Meta["targets/random"] = data.FileMeta{Hashes: data.Hashes{"randomsha": []byte("12345")}} repo.Snapshot.Signed.Meta["targets/nohashes"] = data.FileMeta{Length: 1} extraMeta := []string{"only512", "targets/random", "targets/nohashes"} meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) checkTimestampSnapshotRequired(t, meta, extraMeta, builder) checkOnlySnapshotConsistentAfterTimestamp(t, repo, meta, extraMeta, builder) checkOtherRolesConsistentAfterSnapshot(t, repo, meta, builder) // the fake roles have invalid-ish checksums: the ConsistentInfos for those will return // non-consistent names but non -1 sizes for _, checkName := range extraMeta { ci := builder.GetConsistentInfo(checkName) require.Equal(t, checkName, ci.ConsistentName()) // because no sha256 hash require.True(t, ci.ChecksumKnown()) require.True(t, ci.Length() > -1) } // a non-existent role's ConsistentInfo is empty ci := builder.GetConsistentInfo("nonExistent") require.Equal(t, "nonExistent", ci.ConsistentName()) require.False(t, ci.ChecksumKnown()) require.Equal(t, int64(-1), ci.Length()) // when we bootstrap a new builder, the root has consistent info because the checksum is provided, // but nothing else does builder = builder.BootstrapNewBuilder() for _, checkName := range append(data.BaseRoles, extraMeta...) { ci := builder.GetConsistentInfo(checkName) switch checkName { case data.CanonicalTimestampRole: // timestamp's size is always the max timestamp size require.Equal(t, checkName, ci.ConsistentName()) require.True(t, ci.ChecksumKnown()) require.Equal(t, notary.MaxTimestampSize, ci.Length()) case data.CanonicalRootRole: cName := utils.ConsistentName(data.CanonicalRootRole, repo.Snapshot.Signed.Meta[data.CanonicalRootRole].Hashes[notary.SHA256]) require.Equal(t, cName, ci.ConsistentName()) require.True(t, ci.ChecksumKnown()) require.True(t, ci.Length() > -1) default: require.Equal(t, checkName, ci.ConsistentName()) require.False(t, ci.ChecksumKnown()) require.Equal(t, int64(-1), ci.Length()) } } }
// 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], 1, 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], 1, false)) require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false)) require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, 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], 1, false)) require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, 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], 1, false)) require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, 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], 1, false)) require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false)) _, _, err = builder.GenerateTimestamp(&data.SignedTimestamp{}) require.IsType(t, data.ErrInvalidMetadata{}, err) require.False(t, builder.IsLoaded(data.CanonicalTimestampRole)) }
func TestGetConsistentInfo(t *testing.T) { gun := "docker.com/notary" repo, _, err := testutils.EmptyRepo(gun) require.NoError(t, err) // add some hashes for items in the snapshot that don't correspond to real metadata, but that // will cause ConsistentInfo to behave differently realSha512Sum := sha512.Sum512([]byte("stuff")) repo.Snapshot.Signed.Meta["only512"] = data.FileMeta{Hashes: data.Hashes{notary.SHA512: realSha512Sum[:]}} repo.Snapshot.Signed.Meta["targets/random"] = data.FileMeta{Hashes: data.Hashes{"randomsha": []byte("12345")}} repo.Snapshot.Signed.Meta["targets/nohashes"] = data.FileMeta{Length: 1} extraMeta := []string{"only512", "targets/random", "targets/nohashes"} meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) // if neither snapshot nor timestamp are loaded, no matter how much other data is loaded, consistent info // is empty except for timestamp: timestamps have no checksums, and the length is always -1 for _, roleToLoad := range []string{data.CanonicalRootRole, data.CanonicalTargetsRole} { require.NoError(t, builder.Load(roleToLoad, meta[roleToLoad], 1, false)) for _, checkName := range append(data.BaseRoles, extraMeta...) { ci := builder.GetConsistentInfo(checkName) require.Equal(t, checkName, ci.ConsistentName()) switch checkName { case data.CanonicalTimestampRole: // timestamp's size is always the max timestamp size require.True(t, ci.ChecksumKnown()) require.Equal(t, notary.MaxTimestampSize, ci.Length()) default: require.False(t, ci.ChecksumKnown()) require.Equal(t, int64(-1), ci.Length()) } } } // once timestamp is loaded, we can get the consistent info for snapshot but nothing else require.NoError(t, builder.Load(data.CanonicalTimestampRole, meta[data.CanonicalTimestampRole], 1, false)) for _, checkName := range append(data.BaseRoles, extraMeta...) { ci := builder.GetConsistentInfo(checkName) switch checkName { case data.CanonicalSnapshotRole: cName := utils.ConsistentName(data.CanonicalSnapshotRole, repo.Timestamp.Signed.Meta[data.CanonicalSnapshotRole].Hashes[notary.SHA256]) require.Equal(t, cName, ci.ConsistentName()) require.True(t, ci.ChecksumKnown()) require.True(t, ci.Length() > -1) case data.CanonicalTimestampRole: // timestamp's canonical name is always "timestamp" and its size is always the max // timestamp size require.Equal(t, data.CanonicalTimestampRole, ci.ConsistentName()) require.True(t, ci.ChecksumKnown()) require.Equal(t, notary.MaxTimestampSize, ci.Length()) default: require.Equal(t, checkName, ci.ConsistentName()) require.False(t, ci.ChecksumKnown()) require.Equal(t, int64(-1), ci.Length()) } } // once the snapshot is loaded, we can get real consistent info for all loaded roles require.NoError(t, builder.Load(data.CanonicalSnapshotRole, meta[data.CanonicalSnapshotRole], 1, false)) for _, checkName := range data.BaseRoles { ci := builder.GetConsistentInfo(checkName) require.True(t, ci.ChecksumKnown(), "%s's checksum is not known", checkName) switch checkName { case data.CanonicalTimestampRole: // timestamp's canonical name is always "timestamp" and its size is always -1 require.Equal(t, data.CanonicalTimestampRole, ci.ConsistentName()) require.Equal(t, notary.MaxTimestampSize, ci.Length()) default: fileInfo := repo.Snapshot.Signed.Meta if checkName == data.CanonicalSnapshotRole { fileInfo = repo.Timestamp.Signed.Meta } cName := utils.ConsistentName(checkName, fileInfo[checkName].Hashes[notary.SHA256]) require.Equal(t, cName, ci.ConsistentName()) require.True(t, ci.Length() > -1) } } // the fake roles have invalid-ish checksums: the ConsistentInfos for those will return // non-consistent names but non -1 sizes for _, checkName := range extraMeta { ci := builder.GetConsistentInfo(checkName) require.Equal(t, checkName, ci.ConsistentName()) // because no sha256 hash require.True(t, ci.ChecksumKnown()) require.True(t, ci.Length() > -1) } // a non-existent role's ConsistentInfo is empty ci := builder.GetConsistentInfo("nonExistent") require.Equal(t, "nonExistent", ci.ConsistentName()) require.False(t, ci.ChecksumKnown()) require.Equal(t, int64(-1), ci.Length()) // when we bootstrap a new builder, the root has consistent info because the checksum is provided, // but nothing else does builder = builder.BootstrapNewBuilder() for _, checkName := range append(data.BaseRoles, extraMeta...) { ci := builder.GetConsistentInfo(checkName) switch checkName { case data.CanonicalTimestampRole: // timestamp's size is always the max timestamp size require.Equal(t, checkName, ci.ConsistentName()) require.True(t, ci.ChecksumKnown()) require.Equal(t, notary.MaxTimestampSize, ci.Length()) case data.CanonicalRootRole: cName := utils.ConsistentName(data.CanonicalRootRole, repo.Snapshot.Signed.Meta[data.CanonicalRootRole].Hashes[notary.SHA256]) require.Equal(t, cName, ci.ConsistentName()) require.True(t, ci.ChecksumKnown()) require.True(t, ci.Length() > -1) default: require.Equal(t, checkName, ci.ConsistentName()) require.False(t, ci.ChecksumKnown()) require.Equal(t, int64(-1), ci.Length()) } } }