docs/tuf/client/client_test.go

915 lines
30 KiB
Go

package client
import (
"crypto/sha256"
"encoding/json"
"fmt"
"strconv"
"testing"
"time"
"github.com/Sirupsen/logrus"
tuf "github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/testutils"
"github.com/stretchr/testify/assert"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/keys"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
)
func TestRotation(t *testing.T) {
kdb := keys.NewDB()
signer := signed.NewEd25519()
repo := tuf.NewRepo(kdb, signer)
remote := store.NewMemoryStore(nil, nil)
cache := store.NewMemoryStore(nil, nil)
// Generate initial root key and role and add to key DB
rootKey, err := signer.Create("root", data.ED25519Key)
assert.NoError(t, err, "Error creating root key")
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
assert.NoError(t, err, "Error creating root role")
kdb.AddKey(rootKey)
err = kdb.AddRole(rootRole)
assert.NoError(t, err, "Error adding root role to db")
// Generate new key and role. These will appear in the root.json
// but will not be added to the keyDB.
replacementKey, err := signer.Create("root", data.ED25519Key)
assert.NoError(t, err, "Error creating replacement root key")
replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil)
assert.NoError(t, err, "Error creating replacement root role")
// Generate a new root with the replacement key and role
testRoot, err := data.NewRoot(
map[string]data.PublicKey{replacementKey.ID(): replacementKey},
map[string]*data.RootRole{"root": &replacementRole.RootRole},
false,
)
assert.NoError(t, err, "Failed to create new root")
// Sign testRoot with both old and new keys
signedRoot, err := testRoot.ToSigned()
err = signed.Sign(signer, signedRoot, rootKey, replacementKey)
assert.NoError(t, err, "Failed to sign root")
var origKeySig bool
var replKeySig bool
for _, sig := range signedRoot.Signatures {
if sig.KeyID == rootKey.ID() {
origKeySig = true
} else if sig.KeyID == replacementKey.ID() {
replKeySig = true
}
}
assert.True(t, origKeySig, "Original root key signature not present")
assert.True(t, replKeySig, "Replacement root key signature not present")
client := NewClient(repo, remote, kdb, cache)
err = client.verifyRoot("root", signedRoot, 0)
assert.NoError(t, err, "Failed to verify key rotated root")
}
func TestRotationNewSigMissing(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
kdb := keys.NewDB()
signer := signed.NewEd25519()
repo := tuf.NewRepo(kdb, signer)
remote := store.NewMemoryStore(nil, nil)
cache := store.NewMemoryStore(nil, nil)
// Generate initial root key and role and add to key DB
rootKey, err := signer.Create("root", data.ED25519Key)
assert.NoError(t, err, "Error creating root key")
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
assert.NoError(t, err, "Error creating root role")
kdb.AddKey(rootKey)
err = kdb.AddRole(rootRole)
assert.NoError(t, err, "Error adding root role to db")
// Generate new key and role. These will appear in the root.json
// but will not be added to the keyDB.
replacementKey, err := signer.Create("root", data.ED25519Key)
assert.NoError(t, err, "Error creating replacement root key")
replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil)
assert.NoError(t, err, "Error creating replacement root role")
assert.NotEqual(t, rootKey.ID(), replacementKey.ID(), "Key IDs are the same")
// Generate a new root with the replacement key and role
testRoot, err := data.NewRoot(
map[string]data.PublicKey{replacementKey.ID(): replacementKey},
map[string]*data.RootRole{"root": &replacementRole.RootRole},
false,
)
assert.NoError(t, err, "Failed to create new root")
_, ok := testRoot.Signed.Keys[rootKey.ID()]
assert.False(t, ok, "Old root key appeared in test root")
// Sign testRoot with both old and new keys
signedRoot, err := testRoot.ToSigned()
err = signed.Sign(signer, signedRoot, rootKey)
assert.NoError(t, err, "Failed to sign root")
var origKeySig bool
var replKeySig bool
for _, sig := range signedRoot.Signatures {
if sig.KeyID == rootKey.ID() {
origKeySig = true
} else if sig.KeyID == replacementKey.ID() {
replKeySig = true
}
}
assert.True(t, origKeySig, "Original root key signature not present")
assert.False(t, replKeySig, "Replacement root key signature was present and shouldn't be")
client := NewClient(repo, remote, kdb, cache)
err = client.verifyRoot("root", signedRoot, 0)
assert.Error(t, err, "Should have errored on verify as replacement signature was missing.")
}
func TestRotationOldSigMissing(t *testing.T) {
logrus.SetLevel(logrus.DebugLevel)
kdb := keys.NewDB()
signer := signed.NewEd25519()
repo := tuf.NewRepo(kdb, signer)
remote := store.NewMemoryStore(nil, nil)
cache := store.NewMemoryStore(nil, nil)
// Generate initial root key and role and add to key DB
rootKey, err := signer.Create("root", data.ED25519Key)
assert.NoError(t, err, "Error creating root key")
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
assert.NoError(t, err, "Error creating root role")
kdb.AddKey(rootKey)
err = kdb.AddRole(rootRole)
assert.NoError(t, err, "Error adding root role to db")
// Generate new key and role. These will appear in the root.json
// but will not be added to the keyDB.
replacementKey, err := signer.Create("root", data.ED25519Key)
assert.NoError(t, err, "Error creating replacement root key")
replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil)
assert.NoError(t, err, "Error creating replacement root role")
assert.NotEqual(t, rootKey.ID(), replacementKey.ID(), "Key IDs are the same")
// Generate a new root with the replacement key and role
testRoot, err := data.NewRoot(
map[string]data.PublicKey{replacementKey.ID(): replacementKey},
map[string]*data.RootRole{"root": &replacementRole.RootRole},
false,
)
assert.NoError(t, err, "Failed to create new root")
_, ok := testRoot.Signed.Keys[rootKey.ID()]
assert.False(t, ok, "Old root key appeared in test root")
// Sign testRoot with both old and new keys
signedRoot, err := testRoot.ToSigned()
err = signed.Sign(signer, signedRoot, replacementKey)
assert.NoError(t, err, "Failed to sign root")
var origKeySig bool
var replKeySig bool
for _, sig := range signedRoot.Signatures {
if sig.KeyID == rootKey.ID() {
origKeySig = true
} else if sig.KeyID == replacementKey.ID() {
replKeySig = true
}
}
assert.False(t, origKeySig, "Original root key signature was present and shouldn't be")
assert.True(t, replKeySig, "Replacement root key signature was not present")
client := NewClient(repo, remote, kdb, cache)
err = client.verifyRoot("root", signedRoot, 0)
assert.Error(t, err, "Should have errored on verify as replacement signature was missing.")
}
func TestCheckRootExpired(t *testing.T) {
repo := tuf.NewRepo(nil, nil)
storage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, storage, nil, storage)
root := &data.SignedRoot{}
root.Signed.Expires = time.Now().AddDate(-1, 0, 0)
signedRoot, err := root.ToSigned()
assert.NoError(t, err)
rootJSON, err := json.Marshal(signedRoot)
assert.NoError(t, err)
rootHash := sha256.Sum256(rootJSON)
testSnap := &data.SignedSnapshot{
Signed: data.Snapshot{
Meta: map[string]data.FileMeta{
"root": {
Length: int64(len(rootJSON)),
Hashes: map[string][]byte{
"sha256": rootHash[:],
},
},
},
},
}
repo.SetRoot(root)
repo.SetSnapshot(testSnap)
storage.SetMeta("root", rootJSON)
err = client.checkRoot()
assert.Error(t, err)
assert.IsType(t, tuf.ErrLocalRootExpired{}, err)
}
func TestChecksumMismatch(t *testing.T) {
repo := tuf.NewRepo(nil, nil)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, nil, localStorage)
sampleTargets := data.NewTargets()
orig, err := json.Marshal(sampleTargets)
origSha256 := sha256.Sum256(orig)
orig[0] = '}' // corrupt data, should be a {
assert.NoError(t, err)
remoteStorage.SetMeta("targets", orig)
_, _, err = client.downloadSigned("targets", int64(len(orig)), origSha256[:])
assert.IsType(t, ErrChecksumMismatch{}, err)
}
func TestChecksumMatch(t *testing.T) {
repo := tuf.NewRepo(nil, nil)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, nil, localStorage)
sampleTargets := data.NewTargets()
orig, err := json.Marshal(sampleTargets)
origSha256 := sha256.Sum256(orig)
assert.NoError(t, err)
remoteStorage.SetMeta("targets", orig)
_, _, err = client.downloadSigned("targets", int64(len(orig)), origSha256[:])
assert.NoError(t, err)
}
func TestSizeMismatchLong(t *testing.T) {
repo := tuf.NewRepo(nil, nil)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, nil, localStorage)
sampleTargets := data.NewTargets()
orig, err := json.Marshal(sampleTargets)
origSha256 := sha256.Sum256(orig)
assert.NoError(t, err)
l := int64(len(orig))
orig = append([]byte(" "), orig...)
assert.Equal(t, l+1, int64(len(orig)))
remoteStorage.SetMeta("targets", orig)
_, _, err = client.downloadSigned("targets", l, origSha256[:])
// size just limits the data received, the error is caught
// either during checksum verification or during json deserialization
assert.IsType(t, ErrChecksumMismatch{}, err)
}
func TestSizeMismatchShort(t *testing.T) {
repo := tuf.NewRepo(nil, nil)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, nil, localStorage)
sampleTargets := data.NewTargets()
orig, err := json.Marshal(sampleTargets)
origSha256 := sha256.Sum256(orig)
assert.NoError(t, err)
l := int64(len(orig))
orig = orig[1:]
remoteStorage.SetMeta("targets", orig)
_, _, err = client.downloadSigned("targets", l, origSha256[:])
// size just limits the data received, the error is caught
// either during checksum verification or during json deserialization
assert.IsType(t, ErrChecksumMismatch{}, err)
}
func TestDownloadTargetsHappy(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("targets", orig)
assert.NoError(t, err)
// call repo.SignSnapshot to update the targets role in the snapshot
repo.SignSnapshot(data.DefaultExpires("snapshot"))
err = client.downloadTargets("targets")
assert.NoError(t, err)
}
// TestDownloadTargetsLarge: Check that we can download very large targets metadata files,
// which may be caused by adding a large number of targets.
// This test is slow, so it will not run in short mode.
func TestDownloadTargetsLarge(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
hash := sha256.Sum256([]byte{})
f := data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
// Add a ton of target files to the targets role to make this targets metadata huge
// 75,000 targets results in > 5MB (~6.5MB on recent runs)
for i := 0; i < 75000; i++ {
_, err = repo.AddTargets(data.CanonicalTargetsRole, data.Files{strconv.Itoa(i): f})
assert.NoError(t, err)
}
signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("targets", orig)
assert.NoError(t, err)
// call repo.SignSnapshot to update the targets role in the snapshot
repo.SignSnapshot(data.DefaultExpires("snapshot"))
// Clear the cache to force an online download
client.cache.RemoveAll()
err = client.downloadTargets("targets")
assert.NoError(t, err)
}
func TestDownloadTargetsDeepHappy(t *testing.T) {
kdb, repo, cs, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
delegations := []string{
// left subtree
"targets/level1",
"targets/level1/a",
"targets/level1/a/i",
"targets/level1/a/ii",
"targets/level1/a/iii",
// right subtree
"targets/level2",
"targets/level2/b",
"targets/level2/b/i",
"targets/level2/b/i/0",
"targets/level2/b/i/1",
}
for _, r := range delegations {
// create role
k, err := cs.Create(r, data.ED25519Key)
assert.NoError(t, err)
role, err := data.NewRole(r, 1, []string{k.ID()}, []string{""}, nil)
assert.NoError(t, err)
// add role to repo
repo.UpdateDelegations(role, []data.PublicKey{k})
repo.InitTargets(r)
}
// can only sign after adding all delegations
for _, r := range delegations {
// serialize and store role
signedOrig, err := repo.SignTargets(r, data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta(r, orig)
assert.NoError(t, err)
}
// serialize and store targets after adding all delegations
signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("targets", orig)
assert.NoError(t, err)
// call repo.SignSnapshot to update the targets role in the snapshot
repo.SignSnapshot(data.DefaultExpires("snapshot"))
delete(repo.Targets, "targets")
for _, r := range delegations {
delete(repo.Targets, r)
_, ok := repo.Targets[r]
assert.False(t, ok)
}
err = client.downloadTargets("targets")
assert.NoError(t, err)
_, ok := repo.Targets["targets"]
assert.True(t, ok)
for _, r := range delegations {
_, ok = repo.Targets[r]
assert.True(t, ok)
}
}
func TestDownloadTargetChecksumMismatch(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample targets
signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
origSha256 := sha256.Sum256(orig)
orig[0] = '}' // corrupt data, should be a {
err = remoteStorage.SetMeta("targets", orig)
assert.NoError(t, err)
// create local snapshot with targets file
// It's necessary to do it this way rather than calling repo.SignSnapshot
// so that we have the wrong sha256 in the snapshot.
snap := data.SignedSnapshot{
Signed: data.Snapshot{
Meta: data.Files{
"targets": data.FileMeta{
Length: int64(len(orig)),
Hashes: data.Hashes{
"sha256": origSha256[:],
},
},
},
},
}
repo.Snapshot = &snap
err = client.downloadTargets("targets")
assert.IsType(t, ErrChecksumMismatch{}, err)
}
// TestDownloadTargetsNoChecksum: it's never valid to download any targets
// role (incl. delegations) when a checksum is not available.
func TestDownloadTargetsNoChecksum(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample targets
signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("targets", orig)
assert.NoError(t, err)
delete(repo.Snapshot.Signed.Meta["targets"].Hashes, "sha256")
err = client.downloadTargets("targets")
assert.IsType(t, ErrMissingMeta{}, err)
}
// TestDownloadTargetsNoSnapshot: it's never valid to download any targets
// role (incl. delegations) when a checksum is not available.
func TestDownloadTargetsNoSnapshot(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample targets
signedOrig, err := repo.SignTargets("targets", data.DefaultExpires("targets"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("targets", orig)
assert.NoError(t, err)
repo.Snapshot = nil
err = client.downloadTargets("targets")
assert.IsType(t, ErrMissingMeta{}, err)
}
func TestBootstrapDownloadRootHappy(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample root
signedOrig, err := repo.SignRoot(data.DefaultExpires("root"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("root", orig)
assert.NoError(t, err)
// unset snapshot as if we're bootstrapping from nothing
repo.Snapshot = nil
err = client.downloadRoot()
assert.NoError(t, err)
}
func TestUpdateDownloadRootHappy(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample root, snapshot, and timestamp
signedOrig, err := repo.SignRoot(data.DefaultExpires("root"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("root", orig)
assert.NoError(t, err)
// sign snapshot to make root meta in snapshot get updated
signedOrig, err = repo.SignSnapshot(data.DefaultExpires("snapshot"))
err = client.downloadRoot()
assert.NoError(t, err)
}
func TestUpdateDownloadRootBadChecksum(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// sign snapshot to make sure we have a checksum for root
_, err = repo.SignSnapshot(data.DefaultExpires("snapshot"))
assert.NoError(t, err)
// create and "upload" sample root, snapshot, and timestamp
signedOrig, err := repo.SignRoot(data.DefaultExpires("root"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("root", orig)
assert.NoError(t, err)
// don't sign snapshot again to ensure checksum is out of date (bad)
err = client.downloadRoot()
assert.IsType(t, ErrChecksumMismatch{}, err)
}
func TestDownloadTimestampHappy(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample timestamp
signedOrig, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("timestamp", orig)
assert.NoError(t, err)
err = client.downloadTimestamp()
assert.NoError(t, err)
}
func TestDownloadSnapshotHappy(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample snapshot and timestamp
signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("snapshot", orig)
assert.NoError(t, err)
signedOrig, err = repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
orig, err = json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("timestamp", orig)
assert.NoError(t, err)
err = client.downloadSnapshot()
assert.NoError(t, err)
}
// TestDownloadSnapshotLarge: Check that we can download very large snapshot metadata files,
// which may be caused by adding a large number of delegations.
// This test is slow, so it will not run in short mode.
func TestDownloadSnapshotLarge(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// Add a ton of empty delegation roles to targets to make snapshot data huge
// This can also be done by adding legitimate delegations but it will be much slower
// 75,000 delegation roles results in > 5MB (~7.3MB on recent runs)
for i := 0; i < 75000; i++ {
newRole := &data.SignedTargets{}
repo.Targets[fmt.Sprintf("targets/%d", i)] = newRole
}
// create and "upload" sample snapshot and timestamp
signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("snapshot", orig)
assert.NoError(t, err)
signedOrig, err = repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
orig, err = json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("timestamp", orig)
assert.NoError(t, err)
// Clear the cache to force an online download
client.cache.RemoveAll()
err = client.downloadSnapshot()
assert.NoError(t, err)
}
// TestDownloadSnapshotNoChecksum: It should never be valid to download a
// snapshot if we don't have a checksum
func TestDownloadSnapshotNoTimestamp(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample snapshot and timestamp
signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("snapshot", orig)
assert.NoError(t, err)
repo.Timestamp = nil
err = client.downloadSnapshot()
assert.IsType(t, ErrMissingMeta{}, err)
}
func TestDownloadSnapshotNoChecksum(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// create and "upload" sample snapshot and timestamp
signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("snapshot", orig)
assert.NoError(t, err)
delete(repo.Timestamp.Signed.Meta["snapshot"].Hashes, "sha256")
err = client.downloadSnapshot()
assert.IsType(t, ErrMissingMeta{}, err)
}
func TestDownloadSnapshotBadChecksum(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
// sign timestamp to ensure it has a checksum for snapshot
_, err = repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
// create and "upload" sample snapshot and timestamp
signedOrig, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
assert.NoError(t, err)
orig, err := json.Marshal(signedOrig)
assert.NoError(t, err)
err = remoteStorage.SetMeta("snapshot", orig)
assert.NoError(t, err)
// by not signing timestamp again we ensure it has the wrong checksum
err = client.downloadSnapshot()
assert.IsType(t, ErrChecksumMismatch{}, err)
}
// TargetMeta returns the file metadata for a file path in the role subtree,
// if it exists. It also returns the role in that subtree in which the target
// was found. If the path doesn't exist in that role subtree, returns
// nil and an empty string.
func TestTargetMeta(t *testing.T) {
kdb, repo, cs, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, nil, kdb, localStorage)
delegations := []string{
"targets/level1",
"targets/level1/a",
"targets/level1/a/i",
}
k, err := cs.Create("", data.ED25519Key)
assert.NoError(t, err)
hash := sha256.Sum256([]byte{})
f := data.FileMeta{
Length: 1,
Hashes: map[string][]byte{
"sha256": hash[:],
},
}
for i, r := range delegations {
// create role
role, err := data.NewRole(r, 1, []string{k.ID()}, []string{""}, nil)
assert.NoError(t, err)
// add role to repo
repo.UpdateDelegations(role, []data.PublicKey{k})
repo.InitTargets(r)
// add a target to the role
_, err = repo.AddTargets(r, data.Files{strconv.Itoa(i): f})
assert.NoError(t, err)
}
// returns the right level
fileMeta, role := client.TargetMeta("targets", "1")
assert.Equal(t, &f, fileMeta)
assert.Equal(t, "targets/level1/a", role)
// looks only in subtree
fileMeta, role = client.TargetMeta("targets/level1/a", "0")
assert.Nil(t, fileMeta)
assert.Equal(t, "", role)
fileMeta, role = client.TargetMeta("targets/level1/a", "2")
assert.Equal(t, &f, fileMeta)
assert.Equal(t, "targets/level1/a/i", role)
}
// If there is no local cache and also no remote timestamp, downloading the timestamp
// fails with a store.ErrMetaNotFound
func TestDownloadTimestampNoTimestamps(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
err = client.downloadTimestamp()
assert.Error(t, err)
notFoundErr, ok := err.(store.ErrMetaNotFound)
assert.True(t, ok)
assert.Equal(t, data.CanonicalTimestampRole, notFoundErr.Resource)
}
// If there is no local cache and the remote timestamp is empty, downloading the timestamp
// fails with a store.ErrMetaNotFound
func TestDownloadTimestampNoLocalTimestampRemoteTimestampEmpty(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
remoteStorage := store.NewMemoryStore(map[string][]byte{data.CanonicalTimestampRole: {}}, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
err = client.downloadTimestamp()
assert.Error(t, err)
assert.IsType(t, &json.SyntaxError{}, err)
}
// If there is no local cache and the remote timestamp is invalid, downloading the timestamp
// fails with a store.ErrMetaNotFound
func TestDownloadTimestampNoLocalTimestampRemoteTimestampInvalid(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
localStorage := store.NewMemoryStore(nil, nil)
// add a timestamp to the remote cache
tsSigned, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
tsSigned.Signatures[0].Signature = []byte("12345") // invalidate the signature
ts, err := json.Marshal(tsSigned)
assert.NoError(t, err)
remoteStorage := store.NewMemoryStore(map[string][]byte{data.CanonicalTimestampRole: ts}, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
err = client.downloadTimestamp()
assert.Error(t, err)
assert.IsType(t, signed.ErrRoleThreshold{}, err)
}
// If there is is a local cache and no remote timestamp, we fall back on the cached timestamp
func TestDownloadTimestampLocalTimestampNoRemoteTimestamp(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
// add a timestamp to the local cache
tsSigned, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
ts, err := json.Marshal(tsSigned)
assert.NoError(t, err)
localStorage := store.NewMemoryStore(map[string][]byte{data.CanonicalTimestampRole: ts}, nil)
remoteStorage := store.NewMemoryStore(nil, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
err = client.downloadTimestamp()
assert.NoError(t, err)
}
// If there is is a local cache and the remote timestamp is invalid, we fall back on the cached timestamp
func TestDownloadTimestampLocalTimestampInvalidRemoteTimestamp(t *testing.T) {
kdb, repo, _, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
// add a timestamp to the local cache
tsSigned, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
assert.NoError(t, err)
ts, err := json.Marshal(tsSigned)
assert.NoError(t, err)
localStorage := store.NewMemoryStore(map[string][]byte{data.CanonicalTimestampRole: ts}, nil)
// add a timestamp to the remote cache
tsSigned.Signatures[0].Signature = []byte("12345") // invalidate the signature
ts, err = json.Marshal(tsSigned)
assert.NoError(t, err)
remoteStorage := store.NewMemoryStore(map[string][]byte{data.CanonicalTimestampRole: ts}, nil)
client := NewClient(repo, remoteStorage, kdb, localStorage)
err = client.downloadTimestamp()
assert.NoError(t, err)
}