Skip to content

Commit

Permalink
tests for witness
Browse files Browse the repository at this point in the history
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
  • Loading branch information
David Lawrence committed Aug 1, 2016
1 parent b431bc0 commit b1dd399
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 25 deletions.
11 changes: 1 addition & 10 deletions client/tufclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package client

import (
"encoding/json"
"fmt"

"github.com/Sirupsen/logrus"
"github.com/docker/notary"
Expand All @@ -12,15 +11,6 @@ import (
"github.com/docker/notary/tuf/signed"
)

// ErrCorruptedCache - local data is incorrect
type ErrCorruptedCache struct {
file string
}

func (e ErrCorruptedCache) Error() string {
return fmt.Sprintf("cache is corrupted: %s", e.file)
}

// TUFClient is a usability wrapper around a raw TUF repo
type TUFClient struct {
remote store.RemoteStore
Expand Down Expand Up @@ -158,6 +148,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 Down
22 changes: 13 additions & 9 deletions client/witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package client
import (
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data"
"path/filepath"
)

// Witness creates change objects to witness (i.e. resign) the given
// Witness creates change objects to witness (i.e. re-sign) the given
// roles on the next publish. One change is created per role
func (r *NotaryRepository) Witness(roles ...string) ([]string, error) {
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
Expand Down Expand Up @@ -40,14 +41,17 @@ func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role string) error {
r.Dirty = true
return nil
}
if invalid == nil {
// role isn't recognized, even as invalid
return ErrInvalidLocalRole{Role: role}
if invalid != nil {
if r, ok := invalid.Targets[role]; ok {
// role is recognized but invalid, move to valid data and mark for re-signing
repo.Targets[role] = r
r.Dirty = true
return nil
}
}
if r, ok := invalid.Targets[role]; ok {
// role is recognized but invalid, move to valid data and mark for re-signing
repo.Targets[role] = r
r.Dirty = true
// role isn't recognized, even as invalid
return data.ErrInvalidRole{
Role: role,
Reason: "this role is not known",
}
return nil
}
147 changes: 147 additions & 0 deletions cmd/notary/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1366,3 +1366,150 @@ 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 witness the valid delegation, make sure everything is successful
// 6. add a new (different) key to the delegation
// 7. remove the key from the delegation
// 8. list targets and ensure the target is no longer visible
// 9. witness the delegation
// 10. list targets and ensure target is visible again
// 11. witness an invalid role and check for error on publish
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. witness the valid delegation, make sure everything is successful
_, err = runCommand(t, tempDir, "witness", "gun", delgName)
require.NoError(t, err)

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

output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun")
require.NoError(t, err)
require.Contains(t, output, targetName)
require.Contains(t, output, targetHash)

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

// 7. 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)

// 8. 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)

// 9. 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)

// 10. 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)

// 11. witness an invalid role and check for error on publish
_, err = runCommand(t, tempDir, "witness", "gun", "targets/made/up")
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.Error(t, err)
}
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 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
}
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 b1dd399

Please sign in to comment.