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 Jul 29, 2016
1 parent 76647da commit fa9c366
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 42 deletions.
142 changes: 113 additions & 29 deletions client/client_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ type updateOpts struct {
localCache bool // whether the repo should have a local cache before updating
forWrite bool // whether the update is for writing or not (force check remote root.json)
role string // the role to mess up on the server

checkRepo func(*NotaryRepository, *testutils.MetadataSwizzler) // a callback that can examine the repo at the end
}

// If there's no local cache, we go immediately to check the remote server for
Expand Down Expand Up @@ -783,9 +785,7 @@ func testUpdateRemoteFileChecksumWrong(t *testing.T, opts updateOpts, errExpecte
// --- these tests below assume the checksums are correct (since the server can sign snapshots and
// timestamps, so can be malicious) ---

// this does not include delete, which is tested separately so we can try to get
// 404s and 503s
var waysToMessUpServer = []swizzleExpectations{
var waysToMessUpServerBadMeta = []swizzleExpectations{
{desc: "invalid JSON", expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.SyntaxError{}},
swizzle: (*testutils.MetadataSwizzler).SetInvalidJSON},

Expand All @@ -797,13 +797,20 @@ var waysToMessUpServer = []swizzleExpectations{
expectErrs: []interface{}{&trustpinning.ErrValidationFail{}, &json.UnmarshalTypeError{}, &time.ParseError{}},
swizzle: (*testutils.MetadataSwizzler).SetInvalidSignedMeta},

// for the errors below, when we bootstrap root, we get cert.ErrValidationFail failures
// for everything else, the errors come from tuf/signed

{desc: "invalid SignedMeta Type", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrWrongType, data.ErrInvalidMetadata{}},
swizzle: (*testutils.MetadataSwizzler).SetInvalidMetadataType},

{desc: "lower metadata version", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrLowVersion{}, data.ErrInvalidMetadata{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.OffsetMetadataVersion(role, -3)
}},
}

var waysToMessUpServerBadSigs = []swizzleExpectations{
{desc: "invalid signatures", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
swizzle: (*testutils.MetadataSwizzler).InvalidateMetadataSignatures},
Expand All @@ -812,23 +819,23 @@ var waysToMessUpServer = []swizzleExpectations{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}, &trustpinning.ErrRootRotationFail{}},
swizzle: (*testutils.MetadataSwizzler).SignMetadataWithInvalidKey},

{desc: "expired metadata", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrExpired{}},
swizzle: (*testutils.MetadataSwizzler).ExpireMetadata},

{desc: "lower metadata version", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrLowVersion{}, data.ErrInvalidMetadata{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.OffsetMetadataVersion(role, -3)
}},

{desc: "insufficient signatures", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrRoleThreshold{}},
swizzle: func(s *testutils.MetadataSwizzler, role string) error {
return s.SetThreshold(role, 2)
}},
}

var wayToMessUpServerBadExpiry = swizzleExpectations{
desc: "expired metadata", expectErrs: []interface{}{
&trustpinning.ErrValidationFail{}, signed.ErrExpired{}},
swizzle: (*testutils.MetadataSwizzler).ExpireMetadata,
}

// this does not include delete, which is tested separately so we can try to get
// 404s and 503s
var waysToMessUpServer = append(waysToMessUpServerBadMeta, append(waysToMessUpServerBadSigs, wayToMessUpServerBadExpiry)...)

var _waysToMessUpServerRoot []swizzleExpectations

// We also want to remove a every role from root once, or remove the role's keys.
Expand Down Expand Up @@ -993,16 +1000,35 @@ func TestUpdateNonRootRemoteCorruptedNoLocalCache(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
for _, role := range append(data.BaseRoles, delegationsWithNonEmptyMetadata...) {
if role == data.CanonicalRootRole {
continue

for _, role := range append(data.BaseRoles) {
switch role {
case data.CanonicalRootRole:
break
default:
for _, testData := range waysToMessUpServer {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
}, testData, true)
}
}
for _, testData := range waysToMessUpServer {
}

for _, role := range delegationsWithNonEmptyMetadata {
for _, testData := range waysToMessUpServerBadMeta {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
}, testData, true)
}

for _, testData := range append(waysToMessUpServerBadSigs, wayToMessUpServerBadExpiry) {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
role: role,
checkRepo: checkBadDelegationRoleSkipped(t, role),
}, testData, false)
}
}

for role, expectations := range waysToMessUpServerNonRootPerRole(t) {
for _, testData := range expectations {
switch role {
Expand Down Expand Up @@ -1038,6 +1064,7 @@ func TestUpdateNonRootRemoteCorruptedCanUseLocalCache(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}

for _, role := range append(data.BaseRoles, delegationsWithNonEmptyMetadata...) {
if role == data.CanonicalRootRole {
continue
Expand Down Expand Up @@ -1078,6 +1105,24 @@ func TestUpdateNonRootRemoteCorruptedCanUseLocalCache(t *testing.T) {
}
}

// requires that a delegation role and its descendants were not accepted as a valid part of the
// TUF repo, but everything else was
func checkBadDelegationRoleSkipped(t *testing.T, delgRoleName string) func(*NotaryRepository, *testutils.MetadataSwizzler) {
return func(repo *NotaryRepository, s *testutils.MetadataSwizzler) {
for _, roleName := range s.Roles {
if roleName != data.CanonicalTargetsRole && !data.IsDelegation(roleName) {
continue
}
_, hasTarget := repo.tufRepo.Targets[roleName]
require.Equal(t, !strings.HasPrefix(roleName, delgRoleName), hasTarget)
}

require.NotNil(t, repo.tufRepo.Root)
require.NotNil(t, repo.tufRepo.Snapshot)
require.NotNil(t, repo.tufRepo.Timestamp)
}
}

// Having a local cache, if the server has all new data (some being corrupt),
// and update should fail in all cases (except if we modify the timestamp)
// because the metadata is re-downloaded.
Expand All @@ -1087,22 +1132,51 @@ func TestUpdateNonRootRemoteCorruptedCannotUseLocalCache(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
for _, role := range append(data.BaseRoles, delegationsWithNonEmptyMetadata...) {
if role == data.CanonicalRootRole {
continue

for _, role := range data.BaseRoles {
switch role {
case data.CanonicalRootRole:
break
case data.CanonicalTimestampRole:
for _, testData := range waysToMessUpServer {
// in general the cached timsestamp will always succeed, but if the threshold has been
// increased, it fails because when we download the new timestamp, it validates as per our
// previous root. But the root hash doesn't match. So we download a new root and
// try the update again. In this case, both the old and new timestamps won't have enough
// signatures.
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, testData.desc == "insufficient signatures")
}
default:
for _, testData := range waysToMessUpServer {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, true)
}
}
for _, testData := range waysToMessUpServer {
// in general the cached timsestamp will always succeed, but if the threshold has been
// increased, it fails because when we download the new timestamp, it validates as per our
// previous root. But the root hash doesn't match. So we download a new root and
// try the update again. In this case, both the old and new timestamps won't have enough
// signatures.
shouldFail := role != data.CanonicalTimestampRole || testData.desc == "insufficient signatures"
}

for _, role := range delegationsWithNonEmptyMetadata {
for _, testData := range waysToMessUpServerBadMeta {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
}, testData, shouldFail)
}, testData, true)
}

for _, testData := range append(waysToMessUpServerBadSigs, wayToMessUpServerBadExpiry) {
testUpdateRemoteCorruptValidChecksum(t, updateOpts{
serverHasNewData: true,
localCache: true,
role: role,
checkRepo: checkBadDelegationRoleSkipped(t, role),
}, testData, false)
}
}

Expand Down Expand Up @@ -1203,6 +1277,10 @@ func testUpdateRemoteCorruptValidChecksum(t *testing.T, opts updateOpts, expt sw
} else {
require.NoError(t, err, "expected no failure updating when %s", msg)
}

if opts.checkRepo != nil {
opts.checkRepo(repo, serverSwizzler)
}
}

// If the local root is corrupt, and the remote root is corrupt, we should fail
Expand Down Expand Up @@ -1323,6 +1401,12 @@ func testUpdateRemoteKeyRotated(t *testing.T, role string) {
msg := fmt.Sprintf("swizzling %s remotely to rotate key (forWrite: false)", role)

err = repo.Update(false)
// invalid signatures are ok - the delegation is just skipped
if data.IsDelegation(role) {
require.NoError(t, err)
checkBadDelegationRoleSkipped(t, role)(repo, serverSwizzler)
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
Loading

0 comments on commit fa9c366

Please sign in to comment.