mirror of https://github.com/docker/docs.git
915 lines
30 KiB
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)
|
|
}
|