func checkOnlySnapshotConsistentAfterTimestamp(t *testing.T, repo *tuf.Repo, meta map[string][]byte, extraMeta []string, builder tuf.RepoBuilder) { // 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()) } } }
// SetMeta sets the metadata value for the given name func (m *MemoryStore) SetMeta(name string, meta []byte) error { m.meta[name] = meta checksum := sha256.Sum256(meta) path := utils.ConsistentName(name, checksum[:]) m.consistent[path] = meta return nil }
// RemoveMeta removes the metadata for a single role - if the metadata doesn't // exist, no error is returned func (m *MemoryStore) RemoveMeta(name string) error { if meta, ok := m.meta[name]; ok { checksum := sha256.Sum256(meta) path := utils.ConsistentName(name, checksum[:]) delete(m.meta, name) delete(m.consistent, path) } return nil }
func TestMemoryStoreMetadataOperations(t *testing.T) { s := NewMemoryStore(nil) // GetMeta of a non-existent metadata fails _, err := s.GetMeta("nonexistent", 0) require.Error(t, err) require.IsType(t, ErrMetaNotFound{}, err) // Once SetMeta succeeds, GetMeta with the role name and the consistent name // should succeed metaContent := []byte("content") metaSize := int64(len(metaContent)) shasum := sha256.Sum256(metaContent) invalidShasum := sha256.Sum256([]byte{}) require.NoError(t, s.SetMeta("exists", metaContent)) require.NoError(t, s.SetMultiMeta(map[string][]byte{"multi1": metaContent, "multi2": metaContent})) for _, metaName := range []string{"exists", "multi1", "multi2"} { meta, err := s.GetMeta(metaName, metaSize) require.NoError(t, err) require.Equal(t, metaContent, meta) meta, err = s.GetMeta(utils.ConsistentName(metaName, shasum[:]), metaSize) require.NoError(t, err) require.Equal(t, metaContent, meta) _, err = s.GetMeta(utils.ConsistentName(metaName, invalidShasum[:]), metaSize) require.Error(t, err) require.IsType(t, ErrMetaNotFound{}, err) } // Once Metadata is removed, it's no longer accessible err = s.RemoveAll() require.NoError(t, err) _, err = s.GetMeta("exists", 0) require.Error(t, err) require.IsType(t, ErrMetaNotFound{}, err) }
func TestRepoPrefixDoesNotMatch(t *testing.T) { gun := "docker.io/notary" meta, cs, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) s := storage.NewMemStorage() ctx := context.WithValue(context.Background(), notary.CtxKeyMetaStore, s) ctx = context.WithValue(ctx, notary.CtxKeyKeyAlgo, data.ED25519Key) snChecksumBytes := sha256.Sum256(meta[data.CanonicalSnapshotRole]) // successful gets handler := RootHandler(ctx, nil, cs, nil, nil, []string{"nope"}) ts := httptest.NewServer(handler) url := fmt.Sprintf("%s/v2/%s/_trust/tuf/", ts.URL, gun) uploader, err := store.NewHTTPStore(url, "", "json", "key", http.DefaultTransport) require.NoError(t, err) require.Error(t, uploader.SetMulti(meta)) // update the storage so we don't fail just because the metadata is missing for _, roleName := range data.BaseRoles { require.NoError(t, s.UpdateCurrent(gun, storage.MetaUpdate{ Role: roleName, Data: meta[roleName], Version: 1, })) } _, err = uploader.GetSized(data.CanonicalSnapshotRole, notary.MaxDownloadSize) require.Error(t, err) _, err = uploader.GetSized( tufutils.ConsistentName(data.CanonicalSnapshotRole, snChecksumBytes[:]), notary.MaxDownloadSize) require.Error(t, err) _, err = uploader.GetKey(data.CanonicalTimestampRole) require.Error(t, err) // the httpstore doesn't actually delete all, so we do it manually req, err := http.NewRequest("DELETE", url, nil) require.NoError(t, err) res, err := http.DefaultTransport.RoundTrip(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusNotFound, res.StatusCode) }
// NewMemoryStore returns a MetadataStore that operates entirely in memory. // Very useful for testing func NewMemoryStore(initial map[string][]byte) *MemoryStore { var consistent = make(map[string][]byte) if initial == nil { initial = make(map[string][]byte) } else { // add all seed meta to consistent for name, data := range initial { checksum := sha256.Sum256(data) path := utils.ConsistentName(name, checksum[:]) consistent[path] = data } } return &MemoryStore{ data: initial, consistent: consistent, } }
// NewMemoryStore returns a MetadataStore that operates entirely in memory. // Very useful for testing func NewMemoryStore(meta map[string][]byte) *MemoryStore { var consistent = make(map[string][]byte) if meta == nil { meta = make(map[string][]byte) } else { // add all seed meta to consistent for name, data := range meta { checksum := sha256.Sum256(data) path := utils.ConsistentName(name, checksum[:]) consistent[path] = data } } return &MemoryStore{ meta: meta, consistent: consistent, keys: make(map[string][]data.PrivateKey), } }
func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) { rolePath := utils.ConsistentName(role, expectedSha256) raw, err := c.remote.GetMeta(rolePath, size) if err != nil { return nil, nil, err } if expectedSha256 != nil { genHash := sha256.Sum256(raw) if !bytes.Equal(genHash[:], expectedSha256) { return nil, nil, ErrChecksumMismatch{role: role} } } s := &data.Signed{} err = json.Unmarshal(raw, s) if err != nil { return nil, nil, err } return raw, s, nil }
func (c *Client) downloadSigned(role string, size int64, expectedHashes data.Hashes) ([]byte, *data.Signed, error) { rolePath := utils.ConsistentName(role, expectedHashes["sha256"]) raw, err := c.remote.GetMeta(rolePath, size) if err != nil { return nil, nil, err } if expectedHashes != nil { if err := data.CheckHashes(raw, expectedHashes); err != nil { return nil, nil, ErrChecksumMismatch{role: role} } } s := &data.Signed{} err = json.Unmarshal(raw, s) if err != nil { return nil, nil, err } return raw, s, nil }
func TestRepoPrefixMatches(t *testing.T) { gun := "docker.io/notary" meta, cs, err := testutils.NewRepoMetadata(gun) require.NoError(t, err) ctx := context.WithValue(context.Background(), notary.CtxKeyMetaStore, storage.NewMemStorage()) ctx = context.WithValue(ctx, notary.CtxKeyKeyAlgo, data.ED25519Key) snChecksumBytes := sha256.Sum256(meta[data.CanonicalSnapshotRole]) // successful gets handler := RootHandler(ctx, nil, cs, nil, nil, []string{"docker.io"}) ts := httptest.NewServer(handler) url := fmt.Sprintf("%s/v2/%s/_trust/tuf/", ts.URL, gun) uploader, err := store.NewHTTPStore(url, "", "json", "key", http.DefaultTransport) require.NoError(t, err) // uploading is cool require.NoError(t, uploader.SetMulti(meta)) // getting is cool _, err = uploader.GetSized(data.CanonicalSnapshotRole, notary.MaxDownloadSize) require.NoError(t, err) _, err = uploader.GetSized( tufutils.ConsistentName(data.CanonicalSnapshotRole, snChecksumBytes[:]), notary.MaxDownloadSize) require.NoError(t, err) _, err = uploader.GetKey(data.CanonicalTimestampRole) require.NoError(t, err) // the httpstore doesn't actually delete all, so we do it manually req, err := http.NewRequest("DELETE", url, nil) require.NoError(t, err) res, err := http.DefaultTransport.RoundTrip(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusOK, res.StatusCode) }
func checkOtherRolesConsistentAfterSnapshot(t *testing.T, repo *tuf.Repo, meta map[string][]byte, builder tuf.RepoBuilder) { // 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) } } }
// ConsistentName returns the consistent name (rolename.sha256) for the role // given this consistent information func (c ConsistentInfo) ConsistentName() string { return utils.ConsistentName(c.RoleName, c.fileMeta.Hashes[notary.SHA256]) }
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()) } } }
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()) } } }