Skip to content

Commit

Permalink
tests for witness
Browse files Browse the repository at this point in the history
Signed-off-by: David Lawrence <[email protected]> (github: endophage)
  • Loading branch information
David Lawrence committed Jul 29, 2016
1 parent 76647da commit 78cec6c
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 13 deletions.
8 changes: 8 additions & 0 deletions client/client_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,11 @@ func testUpdateRemoteCorruptValidChecksum(t *testing.T, opts updateOpts, expt sw
}
}
err := repo.Update(opts.forWrite)

if data.IsDelegation(opts.role) && (expt.desc == "invalid signatures" || expt.desc == "expired metadata" || expt.desc == "meta signed by wrong key" || expt.desc == "insufficient signatures") {
shouldErr = false
}

if shouldErr {
require.Error(t, err, "expected failure updating when %s", msg)

Expand Down Expand Up @@ -1323,6 +1328,9 @@ func testUpdateRemoteKeyRotated(t *testing.T, role string) {
msg := fmt.Sprintf("swizzling %s remotely to rotate key (forWrite: false)", role)

err = repo.Update(false)
if data.IsDelegation(role) {
return
}
require.Error(t, err, "expected failure updating when %s", msg)
switch role {
case data.CanonicalRootRole:
Expand Down
18 changes: 11 additions & 7 deletions client/tufclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
store "github.com/docker/notary/storage"
"github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
)

// ErrCorruptedCache - local data is incorrect
Expand Down Expand Up @@ -157,6 +158,7 @@ func (c *TUFClient) downloadTargets() error {
BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole},
Paths: []string{""},
}}

for len(toDownload) > 0 {
role := toDownload[0]
toDownload = toDownload[1:]
Expand All @@ -168,16 +170,18 @@ func (c *TUFClient) downloadTargets() error {
}

children, err := c.getTargetsFile(role, consistentInfo)
if err != nil {
if _, ok := err.(data.ErrMissingMeta); ok && role.Name != data.CanonicalTargetsRole {
// if the role meta hasn't been published,
// that's ok, continue
continue
switch err.(type) {
case signed.ErrExpired, signed.ErrRoleThreshold:
if role.Name == data.CanonicalTargetsRole {
return err
}
logrus.Debugf("Error getting %s: %s", role.Name, err)
logrus.Warnf("Error getting %s: %s", role.Name, err)
break
case nil:
toDownload = append(children, toDownload...)
default:
return err
}
toDownload = append(children, toDownload...)
}
return nil
}
Expand Down
126 changes: 126 additions & 0 deletions cmd/notary/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1291,3 +1291,129 @@ func TestMain(m *testing.M) {
}
os.Exit(m.Run())
}

// Initialize repo and test witnessing. The following steps are performed:
// 1. init a repo
// 2. add a delegation with a key and --all-paths
// 3. add a target to the delegation
// 4. list targets and ensure it really is in the delegation
// 5. add a new (different) key to the delegation
// 6. remove the key from the delegation
// 7. list targets and ensure the target is no longer visible
// 8. witness the delegation
// 9.list targets and ensure target is visible again
func TestWitness(t *testing.T) {
setUp(t)

tempDir := tempDirWithConfig(t, "{}")
defer os.RemoveAll(tempDir)

server := setupServer()
defer server.Close()

startTime := time.Now()
endTime := startTime.AddDate(10, 0, 0)

// Setup certificates
tempFile, err := ioutil.TempFile("", "pemfile")
require.NoError(t, err)
privKey, err := utils.GenerateECDSAKey(rand.Reader)
cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime)
require.NoError(t, err)
_, err = tempFile.Write(utils.CertToPEM(cert))
require.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())
rawPubBytes, _ := ioutil.ReadFile(tempFile.Name())
parsedPubKey, _ := utils.ParsePEMPublicKey(rawPubBytes)
keyID, err := utils.CanonicalKeyID(parsedPubKey)
require.NoError(t, err)

tempFile2, err := ioutil.TempFile("", "pemfile")
require.NoError(t, err)
privKey2, err := utils.GenerateECDSAKey(rand.Reader)
cert2, err := cryptoservice.GenerateCertificate(privKey2, "gun", startTime, endTime)
require.NoError(t, err)
_, err = tempFile2.Write(utils.CertToPEM(cert2))
require.NoError(t, err)
tempFile2.Close()
defer os.Remove(tempFile2.Name())

delgName := "targets/delegation"
targetName := "test_target"
targetHash := "9d9e890af64dd0f44b8a1538ff5fa0511cc31bf1ab89f3a3522a9a581a70fad8" // sha256 of README.md at time of writing test

keyStore, err := trustmanager.NewKeyFileStore(tempDir, passphrase.ConstantRetriever(testPassphrase))
require.NoError(t, err)
err = keyStore.AddKey(
trustmanager.KeyInfo{
Gun: "gun",
Role: delgName,
},
privKey,
)
require.NoError(t, err)

// 1. init a repo
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun")
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

// 2. add a delegation with a key and --all-paths
_, err = runCommand(t, tempDir, "delegation", "add", "gun", delgName, tempFile.Name(), "--all-paths")
require.NoError(t, err)

// 3. add a target to the delegation
_, err = runCommand(t, tempDir, "addhash", "gun", targetName, "100", "--sha256", targetHash, "-r", delgName)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

// 4. list targets and ensure it really is in the delegation
output, err := runCommand(t, tempDir, "-s", server.URL, "list", "gun")
require.NoError(t, err)
require.Contains(t, output, targetName)
require.Contains(t, output, targetHash)

// 5. add a new (different) key to the delegation
_, err = runCommand(t, tempDir, "delegation", "add", "gun", delgName, tempFile2.Name(), "--all-paths")
require.NoError(t, err)

// 6. remove the key from the delegation
_, err = runCommand(t, tempDir, "delegation", "remove", "gun", delgName, keyID)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

// 7. list targets and ensure the target is no longer visible
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun")
require.NoError(t, err)
require.NotContains(t, output, targetName)
require.NotContains(t, output, targetHash)

err = keyStore.AddKey(
trustmanager.KeyInfo{
Gun: "gun",
Role: delgName,
},
privKey2,
)
require.NoError(t, err)

// 8. witness the delegation
_, err = runCommand(t, tempDir, "witness", "gun", delgName)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

// 9. list targets and ensure target is visible again
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun")
require.NoError(t, err)
require.Contains(t, output, targetName)
require.Contains(t, output, targetHash)
}
18 changes: 13 additions & 5 deletions tuf/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,15 @@ func (f finishedBuilder) GetConsistentInfo(roleName string) ConsistentInfo {

// NewRepoBuilder is the only way to get a pre-built RepoBuilder
func NewRepoBuilder(gun string, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
}

// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
func NewBuilderFromRepo(gun string, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
return &repoBuilderWrapper{
RepoBuilder: &repoBuilder{
repo: NewRepo(cs),
repo: repo,
invalidRoles: NewRepo(nil),
gun: gun,
trustpin: trustpin,
Expand Down Expand Up @@ -539,6 +545,7 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio
return err
}

// bytesToSigned checks checksum
signedObj, err := rb.bytesToSigned(content, roleName)
if err != nil {
return err
Expand All @@ -548,13 +555,14 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio
if err != nil {
return err
}
// verify signature
if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
rb.invalidRoles.Targets[roleName] = signedTargets

if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
// don't capture in invalidRoles because the role we received is rollback
return err
}

if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
// verify signature
if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
rb.invalidRoles.Targets[roleName] = signedTargets
return err
}
Expand Down
51 changes: 51 additions & 0 deletions tuf/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,57 @@ func TestBuilderOnlyAcceptsDelegationsAfterParent(t *testing.T) {
require.NoError(t, builder.Load("targets/a/b", meta["targets/a/b"], 1, false))
}

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)
}

func TestBuilderAcceptRoleOnce(t *testing.T) {
meta, gun := getSampleMeta(t)
builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{})
Expand Down
4 changes: 3 additions & 1 deletion tuf/signed/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {

}
if len(valid) < roleData.Threshold {
return ErrRoleThreshold{}
return ErrRoleThreshold{
Msg: fmt.Sprintf("valid signatures did not meet threshold for %s", roleData.Name),
}
}

return nil
Expand Down

0 comments on commit 78cec6c

Please sign in to comment.