From b9aca8b76006473d673c7cb4167700175546d0ad Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Thu, 28 Jul 2016 10:24:11 -0700 Subject: [PATCH] tests for witness Signed-off-by: David Lawrence (github: endophage) --- client/tufclient.go | 1 + cmd/notary/integration_test.go | 126 +++++++++++++++++++++++++++++++++ tuf/builder.go | 18 +++-- tuf/builder_test.go | 51 +++++++++++++ tuf/signed/verify.go | 4 +- 5 files changed, 194 insertions(+), 6 deletions(-) diff --git a/client/tufclient.go b/client/tufclient.go index ba59134c35..1d7091bf00 100644 --- a/client/tufclient.go +++ b/client/tufclient.go @@ -158,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:] diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go index 8c58e9a09f..9e12d5e525 100644 --- a/cmd/notary/integration_test.go +++ b/cmd/notary/integration_test.go @@ -1366,3 +1366,129 @@ func TestPurge(t *testing.T) { require.NoError(t, err) require.Contains(t, out, delgName) } + +// 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) +} diff --git a/tuf/builder.go b/tuf/builder.go index 61ceef976d..9e1d3efb6e 100644 --- a/tuf/builder.go +++ b/tuf/builder.go @@ -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, @@ -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 @@ -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 a 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 } diff --git a/tuf/builder_test.go b/tuf/builder_test.go index eb249e567f..9473d66446 100644 --- a/tuf/builder_test.go +++ b/tuf/builder_test.go @@ -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{}) diff --git a/tuf/signed/verify.go b/tuf/signed/verify.go index fadcbd0349..2a497b7076 100644 --- a/tuf/signed/verify.go +++ b/tuf/signed/verify.go @@ -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