// Creates metadata in the following manner: // - the snapshot has bad checksums for itself and for timestamp, to show that those aren't checked // - snapshot has valid checksums for root, targets, and targets/other // - snapshot doesn't have a checksum for targets/other/other, but targets/other/other is a valid // delegation role in targets/other and there is metadata for targets/other/other that is correctly // signed func setupSnapshotChecksumming(t *testing.T, gun string) map[string][]byte { 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) // fake the snapshot and timestamp checksums repo.Snapshot.Signed.Meta[data.CanonicalSnapshotRole] = fakeChecksum repo.Snapshot.Signed.Meta[data.CanonicalTimestampRole] = fakeChecksum meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) // ensure that the fake metadata for other roles wasn't destroyed by signing require.Len(t, repo.Snapshot.Signed.Meta, 5) // create delegation metadata that should not be in snapshot, but has a valid role and signature _, err = repo.InitTargets("targets/other/other") require.NoError(t, err) s, err := repo.SignTargets("targets/other/other", data.DefaultExpires(data.CanonicalTargetsRole)) require.NoError(t, err) meta["targets/other/other"], err = json.Marshal(s) require.NoError(t, err) return meta }
func TestBuilderLoadInvalidDelegationsOldVersion(t *testing.T) { gun := "docker.com/notary" tufRepo, _, err := testutils.EmptyRepo(gun, "targets/a", "targets/a/b", "targets/b") require.NoError(t, err) meta, err := testutils.SignAndSerialize(tufRepo) require.NoError(t, err) builder := tuf.NewBuilderFromRepo(gun, tufRepo, trustpinning.TrustPinConfig{}) delete(tufRepo.Targets, "targets/a") // load targets/a with high min-version so this one is too old err = builder.Load( "targets/a", meta["targets/a"], 10, false, ) require.Error(t, err) require.IsType(t, signed.ErrLowVersion{}, err) _, invalid, err := builder.Finish() require.NoError(t, err) _, ok := invalid.Targets["targets/a"] require.False(t, ok) }
// If there is no previous timestamp or the previous timestamp is corrupt, then // even if everything else is in place, getting the timestamp fails func TestGetTimestampNoPreviousTimestamp(t *testing.T) { repo, crypto, err := testutils.EmptyRepo("gun") require.NoError(t, err) meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) for _, timestampJSON := range [][]byte{nil, []byte("invalid JSON")} { store := storage.NewMemStorage() // so we know it's not a failure in getting root or snapshot require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalRootRole, Version: 0, Data: meta[data.CanonicalRootRole]})) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalSnapshotRole, Version: 0, Data: meta[data.CanonicalSnapshotRole]})) if timestampJSON != nil { require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalTimestampRole, Version: 0, Data: timestampJSON})) } _, _, err = GetOrCreateTimestamp("gun", store, crypto) require.Error(t, err, "GetTimestamp should have failed") if timestampJSON == nil { require.IsType(t, storage.ErrNotFound{}, err) } else { require.IsType(t, &json.SyntaxError{}, err) } } }
func TestCreateTimestampNoKeyInCrypto(t *testing.T) { store := storage.NewMemStorage() repo, _, err := testutils.EmptyRepo("gun") require.NoError(t, err) meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) // create an expired timestamp _, err = repo.SignTimestamp(time.Now().AddDate(-1, -1, -1)) require.True(t, repo.Timestamp.Signed.Expires.Before(time.Now())) require.NoError(t, err) timestampJSON, err := json.Marshal(repo.Timestamp) require.NoError(t, err) // set all the metadata so we know the failure to sign is just because of the key require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalRootRole, Version: 0, Data: meta[data.CanonicalRootRole]})) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalSnapshotRole, Version: 0, Data: meta[data.CanonicalSnapshotRole]})) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalTimestampRole, Version: 1, Data: timestampJSON})) // pass it a new cryptoservice without the key _, _, err = GetOrCreateTimestamp("gun", store, signed.NewEd25519()) require.Error(t, err) require.IsType(t, signed.ErrInsufficientSignatures{}, err) }
func TestGetTimestampOldTimestampExpired(t *testing.T) { store := storage.NewMemStorage() repo, crypto, err := testutils.EmptyRepo("gun") require.NoError(t, err) meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) // create an expired timestamp _, err = repo.SignTimestamp(time.Now().AddDate(-1, -1, -1)) require.True(t, repo.Timestamp.Signed.Expires.Before(time.Now())) require.NoError(t, err) timestampJSON, err := json.Marshal(repo.Timestamp) require.NoError(t, err) // set all the metadata require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalRootRole, Version: 0, Data: meta[data.CanonicalRootRole]})) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalSnapshotRole, Version: 0, Data: meta[data.CanonicalSnapshotRole]})) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalTimestampRole, Version: 1, Data: timestampJSON})) _, gottenTimestamp, err := GetOrCreateTimestamp("gun", store, crypto) require.NoError(t, err, "GetTimestamp errored") require.False(t, bytes.Equal(timestampJSON, gottenTimestamp), "Timestamp was not regenerated when old one was expired") signedMeta := &data.SignedMeta{} require.NoError(t, json.Unmarshal(gottenTimestamp, signedMeta)) // the new metadata is not expired require.True(t, signedMeta.Signed.Expires.After(time.Now())) }
// 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 the root or snapshot is missing or corrupt, no timestamp can be generated func TestCannotMakeNewTimestampIfNoRootOrSnapshot(t *testing.T) { repo, crypto, err := testutils.EmptyRepo("gun") require.NoError(t, err) meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) // create an expired timestamp _, err = repo.SignTimestamp(time.Now().AddDate(-1, -1, -1)) require.True(t, repo.Timestamp.Signed.Expires.Before(time.Now())) require.NoError(t, err) timestampJSON, err := json.Marshal(repo.Timestamp) require.NoError(t, err) rootJSON := meta[data.CanonicalRootRole] snapJSON := meta[data.CanonicalSnapshotRole] invalids := []struct { test map[string][]byte err error }{ { test: map[string][]byte{data.CanonicalRootRole: rootJSON, data.CanonicalSnapshotRole: []byte("invalid JSON")}, err: storage.ErrNotFound{}, }, { test: map[string][]byte{data.CanonicalRootRole: []byte("invalid JSON"), data.CanonicalSnapshotRole: snapJSON}, err: &json.SyntaxError{}, }, { test: map[string][]byte{data.CanonicalRootRole: rootJSON}, err: storage.ErrNotFound{}, }, { test: map[string][]byte{data.CanonicalSnapshotRole: snapJSON}, err: storage.ErrNotFound{}, }, } for _, test := range invalids { dataToSet := test.test store := storage.NewMemStorage() for roleName, jsonBytes := range dataToSet { require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: roleName, Version: 0, Data: jsonBytes})) } require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalTimestampRole, Version: 1, Data: timestampJSON})) _, _, err := GetOrCreateTimestamp("gun", store, crypto) require.Error(t, err, "GetTimestamp errored") require.IsType(t, test.err, err) } }
func TestBuilderLoadInvalidDelegations(t *testing.T) { gun := "docker.com/notary" tufRepo, _, err := testutils.EmptyRepo(gun, "targets/a", "targets/a/b", "targets/b") require.NoError(t, err) meta, err := testutils.SignAndSerialize(tufRepo) require.NoError(t, err) builder := tuf.NewBuilderFromRepo(gun, tufRepo, trustpinning.TrustPinConfig{}) // modify targets/a to remove the signature and update the snapshot // (we're not going to load the timestamp so no need to modify) targetsAJSON := meta["targets/a"] targetsA := data.Signed{} err = json.Unmarshal(targetsAJSON, &targetsA) require.NoError(t, err) targetsA.Signatures = make([]data.Signature, 0) targetsAJSON, err = json.Marshal(&targetsA) require.NoError(t, err) meta["targets/a"] = targetsAJSON delete(tufRepo.Targets, "targets/a") snap := tufRepo.Snapshot m, err := data.NewFileMeta( bytes.NewReader(targetsAJSON), "sha256", "sha512", ) require.NoError(t, err) snap.AddMeta("targets/a", m) // load snapshot directly into repo to bypass signature check (we've invalidated // the signature by modifying it) tufRepo.Snapshot = snap // load targets/a require.Error( t, builder.Load( "targets/a", meta["targets/a"], 1, false, ), ) _, invalid, err := builder.Finish() require.NoError(t, err) _, ok := invalid.Targets["targets/a"] require.True(t, ok) }
// If there WAS a pre-existing timestamp, and it is not expired, then just return it (it doesn't // load any other metadata that it doesn't need, like root) func TestGetTimestampReturnsPreviousTimestampIfUnexpired(t *testing.T) { store := storage.NewMemStorage() repo, crypto, err := testutils.EmptyRepo("gun") require.NoError(t, err) meta, err := testutils.SignAndSerialize(repo) require.NoError(t, err) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalSnapshotRole, Version: 0, Data: meta[data.CanonicalSnapshotRole]})) require.NoError(t, store.UpdateCurrent("gun", storage.MetaUpdate{Role: data.CanonicalTimestampRole, Version: 0, Data: meta[data.CanonicalTimestampRole]})) _, gottenTimestamp, err := GetOrCreateTimestamp("gun", store, crypto) require.NoError(t, err, "GetTimestamp should not have failed") require.True(t, bytes.Equal(meta[data.CanonicalTimestampRole], gottenTimestamp)) }
// Produce a series of tufMeta objects and updates given a TUF repo func metaFromRepo(t *testing.T, gun string, version int) map[string]StoredTUFMeta { tufRepo, _, err := testutils.EmptyRepo(gun, "targets/a", "targets/a/b") require.NoError(t, err) tufRepo.Root.Signed.Version = version - 1 tufRepo.Timestamp.Signed.Version = version - 1 tufRepo.Snapshot.Signed.Version = version - 1 for _, signedObj := range tufRepo.Targets { signedObj.Signed.Version = version - 1 } metaBytes, err := testutils.SignAndSerialize(tufRepo) require.NoError(t, err) tufMeta := make(map[string]StoredTUFMeta) for role, tufdata := range metaBytes { tufMeta[role] = SampleCustomTUFObj(gun, role, version, tufdata) } return tufMeta }
// 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) }
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()) } } }
// 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)) }
// 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()) } } }