diff --git a/client/changelist/change.go b/client/changelist/change.go index 403c66cb1..3e872e681 100644 --- a/client/changelist/change.go +++ b/client/changelist/change.go @@ -23,6 +23,7 @@ const ( TypeRootRole = "role" TypeTargetsTarget = "target" TypeTargetsDelegation = "delegation" + TypeWitness = "witness" ) // TUFChange represents a change to a TUF repo diff --git a/client/client.go b/client/client.go index a1fbcdde5..3287ac365 100644 --- a/client/client.go +++ b/client/client.go @@ -20,7 +20,6 @@ import ( "github.com/docker/notary/trustmanager" "github.com/docker/notary/trustpinning" "github.com/docker/notary/tuf" - tufclient "github.com/docker/notary/tuf/client" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/utils" @@ -85,6 +84,7 @@ type NotaryRepository struct { fileStore store.MetadataStore CryptoService signed.CryptoService tufRepo *tuf.Repo + invalid *tuf.Repo // known data that was parsable but deemed invalid roundTrip http.RoundTripper trustPinning trustpinning.TrustPinConfig } @@ -616,7 +616,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error { } } // apply the changelist to the repo - if err := applyChangelist(r.tufRepo, cl); err != nil { + if err := applyChangelist(r.tufRepo, r.invalid, cl); err != nil { logrus.Debug("Error applying changelist") return err } @@ -714,7 +714,7 @@ func (r *NotaryRepository) bootstrapRepo() error { } } - tufRepo, err := b.Finish() + tufRepo, _, err := b.Finish() if err == nil { r.tufRepo = tufRepo } @@ -787,7 +787,7 @@ func (r *NotaryRepository) Update(forWrite bool) error { } return err } - repo, err := c.Update() + repo, invalid, err := c.Update() if err != nil { // notFound.Resource may include a checksum so when the role is root, // it will be root or root.. Therefore best we can @@ -800,6 +800,7 @@ func (r *NotaryRepository) Update(forWrite bool) error { // we can be assured if we are at this stage that the repo we built is good // no need to test the following function call for an error as it will always be fine should the repo be good- it is! r.tufRepo = repo + r.invalid = invalid warnRolesNearExpiry(repo) return nil } @@ -811,16 +812,16 @@ func (r *NotaryRepository) Update(forWrite bool) error { // and return an error if the remote repository errors. // // Populates a tuf.RepoBuilder with this root metadata (only use -// tufclient.Client.Update to load the rest). +// TUFClient.Update to load the rest). // // Fails if the remote server is reachable and does not know the repo // (i.e. before the first r.Publish()), in which case the error is // store.ErrMetaNotFound, or if the root metadata (from whichever source is used) // is not trusted. // -// Returns a tufclient.Client for the remote server, which may not be actually +// Returns a TUFClient for the remote server, which may not be actually // operational (if the URL is invalid but a root.json is cached). -func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) { +func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*TUFClient, error) { minVersion := 1 // the old root on disk should not be validated against any trust pinning configuration // because if we have an old root, it itself is the thing that pins trust @@ -887,7 +888,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl return nil, ErrRepoNotInitialized{} } - return tufclient.NewClient(oldBuilder, newBuilder, remote, r.fileStore), nil + return NewTUFClient(oldBuilder, newBuilder, remote, r.fileStore), nil } // RotateKey removes all existing keys associated with the role, and either diff --git a/client/client_test.go b/client/client_test.go index 678746af7..7e543e3fa 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1202,7 +1202,7 @@ func testListTarget(t *testing.T, rootType string) { require.NoError(t, err, "could not open changelist") // apply the changelist to the repo - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") fakeServerData(t, repo, mux, keys) @@ -1280,7 +1280,7 @@ func testListTargetWithDelegates(t *testing.T, rootType string) { require.NoError(t, err, "could not open changelist") // apply the changelist to the repo, then clear it - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") require.NoError(t, cl.Clear("")) @@ -1305,7 +1305,7 @@ func testListTargetWithDelegates(t *testing.T, rootType string) { filepath.Join(repo.baseDir, "tuf", filepath.FromSlash(repo.gun), "changelist")) require.NoError(t, err, "could not open changelist") // apply the changelist to the repo - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") // check the changelist was applied _, ok = repo.tufRepo.Targets["targets/level1/level2"].Signed.Targets["level2"] @@ -1430,7 +1430,7 @@ func TestListTargetRestrictsDelegationPaths(t *testing.T) { require.NoError(t, err, "could not open changelist") // apply the changelist to the repo - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") require.NoError(t, cl.Clear("")) @@ -1452,7 +1452,7 @@ func TestListTargetRestrictsDelegationPaths(t *testing.T) { require.NoError(t, err, "could not open changelist") // apply the changelist to the repo - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") fakeServerData(t, repo, mux, keys) @@ -2948,7 +2948,7 @@ func TestAddDelegationChangefileApplicable(t *testing.T) { require.Len(t, changes, 2) // ensure that it can be applied correctly - err = applyTargetsChange(repo.tufRepo, changes[0]) + err = applyTargetsChange(repo.tufRepo, nil, changes[0]) require.NoError(t, err) targetRole := repo.tufRepo.Targets[data.CanonicalTargetsRole] @@ -3025,8 +3025,8 @@ func TestRemoveDelegationChangefileApplicable(t *testing.T) { require.NoError(t, repo.AddDelegation("targets/a", []data.PublicKey{rootPubKey}, []string{""})) changes := getChanges(t, repo) require.Len(t, changes, 2) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[0])) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[1])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[0])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[1])) targetRole := repo.tufRepo.Targets[data.CanonicalTargetsRole] require.Len(t, targetRole.Signed.Delegations.Roles, 1) @@ -3038,7 +3038,7 @@ func TestRemoveDelegationChangefileApplicable(t *testing.T) { require.NoError(t, repo.RemoveDelegationKeys("targets/a", []string{rootKeyCanonicalID})) changes = getChanges(t, repo) require.Len(t, changes, 3) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[2])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[2])) targetRole = repo.tufRepo.Targets[data.CanonicalTargetsRole] require.Len(t, targetRole.Signed.Delegations.Roles, 1) @@ -3061,14 +3061,14 @@ func TestClearAllPathsDelegationChangefileApplicable(t *testing.T) { require.NoError(t, repo.AddDelegation("targets/a", []data.PublicKey{rootPubKey}, []string{"abc,123,xyz,path"})) changes := getChanges(t, repo) require.Len(t, changes, 2) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[0])) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[1])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[0])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[1])) // now clear paths it require.NoError(t, repo.ClearDelegationPaths("targets/a")) changes = getChanges(t, repo) require.Len(t, changes, 3) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[2])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[2])) delgRoles := repo.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles require.Len(t, delgRoles, 1) @@ -3105,7 +3105,7 @@ func TestFullAddDelegationChangefileApplicable(t *testing.T) { changes := getChanges(t, repo) require.Len(t, changes, 1) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[0])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[0])) delgRoles := repo.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles require.Len(t, delgRoles, 1) @@ -3136,8 +3136,8 @@ func TestFullRemoveDelegationChangefileApplicable(t *testing.T) { require.NoError(t, repo.AddDelegation(delegationName, []data.PublicKey{rootPubKey, key2}, []string{"abc", "123"})) changes := getChanges(t, repo) require.Len(t, changes, 2) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[0])) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[1])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[0])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[1])) targetRole := repo.tufRepo.Targets[data.CanonicalTargetsRole] require.Len(t, targetRole.Signed.Delegations.Roles, 1) @@ -3155,7 +3155,7 @@ func TestFullRemoveDelegationChangefileApplicable(t *testing.T) { changes = getChanges(t, repo) require.Len(t, changes, 3) - require.NoError(t, applyTargetsChange(repo.tufRepo, changes[2])) + require.NoError(t, applyTargetsChange(repo.tufRepo, nil, changes[2])) delgRoles := repo.tufRepo.Targets[data.CanonicalTargetsRole].Signed.Delegations.Roles require.Len(t, delgRoles, 1) @@ -3577,7 +3577,7 @@ func TestGetAllTargetInfo(t *testing.T) { require.NoError(t, err, "could not open changelist") // apply the changelist to the repo, then clear it - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") require.NoError(t, cl.Clear("")) @@ -3602,7 +3602,7 @@ func TestGetAllTargetInfo(t *testing.T) { filepath.Join(repo.baseDir, "tuf", filepath.FromSlash(repo.gun), "changelist")) require.NoError(t, err, "could not open changelist") // apply the changelist to the repo - err = applyChangelist(repo.tufRepo, cl) + err = applyChangelist(repo.tufRepo, nil, cl) require.NoError(t, err, "could not apply changelist") // check the changelist was applied _, ok = repo.tufRepo.Targets["targets/level1/level2"].Signed.Targets["level2"] diff --git a/client/helpers.go b/client/helpers.go index f048aa1bb..76e2b12df 100644 --- a/client/helpers.go +++ b/client/helpers.go @@ -29,7 +29,7 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor return s, err } -func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error { +func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error { it, err := cl.NewIterator() if err != nil { return err @@ -43,11 +43,11 @@ func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error { isDel := data.IsDelegation(c.Scope()) || data.IsWildDelegation(c.Scope()) switch { case c.Scope() == changelist.ScopeTargets || isDel: - err = applyTargetsChange(repo, c) + err = applyTargetsChange(repo, invalid, c) case c.Scope() == changelist.ScopeRoot: err = applyRootChange(repo, c) default: - logrus.Debug("scope not supported: ", c.Scope()) + return fmt.Errorf("scope not supported: %s", c.Scope()) } if err != nil { logrus.Debugf("error attempting to apply change #%d: %s, on scope: %s path: %s type: %s", index, c.Action(), c.Scope(), c.Path(), c.Type()) @@ -59,12 +59,14 @@ func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error { return nil } -func applyTargetsChange(repo *tuf.Repo, c changelist.Change) error { +func applyTargetsChange(repo *tuf.Repo, invalid *tuf.Repo, c changelist.Change) error { switch c.Type() { case changelist.TypeTargetsTarget: return changeTargetMeta(repo, c) case changelist.TypeTargetsDelegation: return changeTargetsDelegation(repo, c) + case changelist.TypeWitness: + return witnessTargets(repo, invalid, c.Scope()) default: return fmt.Errorf("only target meta and delegations changes supported") } @@ -155,7 +157,7 @@ func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error { } default: - logrus.Debug("action not yet supported: ", c.Action()) + err = fmt.Errorf("action not yet supported: %s", c.Action()) } return err } @@ -166,7 +168,7 @@ func applyRootChange(repo *tuf.Repo, c changelist.Change) error { case changelist.TypeRootRole: err = applyRootRoleChange(repo, c) default: - logrus.Debug("type of root change not yet supported: ", c.Type()) + err = fmt.Errorf("type of root change not yet supported: %s", c.Type()) } return err // might be nil } @@ -185,7 +187,7 @@ func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error { return err } default: - logrus.Debug("action not yet supported for root: ", c.Action()) + return fmt.Errorf("action not yet supported for root: %s", c.Action()) } return nil } diff --git a/client/helpers_test.go b/client/helpers_test.go index b54d62c2b..5c6552098 100644 --- a/client/helpers_test.go +++ b/client/helpers_test.go @@ -36,7 +36,7 @@ func TestApplyTargetsChange(t *testing.T) { ChangePath: "latest", Data: fjson, } - err = applyTargetsChange(repo, addChange) + err = applyTargetsChange(repo, nil, addChange) require.NoError(t, err) require.NotNil(t, repo.Targets["targets"].Signed.Targets["latest"]) @@ -47,7 +47,7 @@ func TestApplyTargetsChange(t *testing.T) { ChangePath: "latest", Data: nil, } - err = applyTargetsChange(repo, removeChange) + err = applyTargetsChange(repo, nil, removeChange) require.NoError(t, err) _, ok := repo.Targets["targets"].Signed.Targets["latest"] require.False(t, ok) @@ -85,11 +85,11 @@ func TestApplyAddTargetTwice(t *testing.T) { Data: fjson, })) - require.NoError(t, applyChangelist(repo, cl)) + require.NoError(t, applyChangelist(repo, nil, cl)) require.Len(t, repo.Targets["targets"].Signed.Targets, 1) require.NotEmpty(t, repo.Targets["targets"].Signed.Targets["latest"]) - require.NoError(t, applyTargetsChange(repo, &changelist.TUFChange{ + require.NoError(t, applyTargetsChange(repo, nil, &changelist.TUFChange{ Actn: changelist.ActionCreate, Role: changelist.ScopeTargets, ChangeType: "target", @@ -124,7 +124,7 @@ func TestApplyChangelist(t *testing.T) { Data: fjson, } cl.Add(addChange) - err = applyChangelist(repo, cl) + err = applyChangelist(repo, nil, cl) require.NoError(t, err) require.NotNil(t, repo.Targets["targets"].Signed.Targets["latest"]) @@ -138,7 +138,7 @@ func TestApplyChangelist(t *testing.T) { Data: nil, } cl.Add(removeChange) - err = applyChangelist(repo, cl) + err = applyChangelist(repo, nil, cl) require.NoError(t, err) _, ok := repo.Targets["targets"].Signed.Targets["latest"] require.False(t, ok) @@ -179,7 +179,7 @@ func TestApplyChangelistMulti(t *testing.T) { cl.Add(addChange) cl.Add(removeChange) - err = applyChangelist(repo, cl) + err = applyChangelist(repo, nil, cl) require.NoError(t, err) _, ok := repo.Targets["targets"].Signed.Targets["latest"] require.False(t, ok) @@ -211,7 +211,7 @@ func TestApplyTargetsDelegationCreateDelete(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) tgts := repo.Targets[data.CanonicalTargetsRole] @@ -242,7 +242,7 @@ func TestApplyTargetsDelegationCreateDelete(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) require.Len(t, tgts.Signed.Delegations.Roles, 0) @@ -275,7 +275,7 @@ func TestApplyTargetsDelegationCreate2SharedKey(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) // create second delegation @@ -297,7 +297,7 @@ func TestApplyTargetsDelegationCreate2SharedKey(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) tgts := repo.Targets[data.CanonicalTargetsRole] @@ -330,7 +330,7 @@ func TestApplyTargetsDelegationCreate2SharedKey(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) require.Len(t, tgts.Signed.Delegations.Roles, 1) @@ -345,7 +345,7 @@ func TestApplyTargetsDelegationCreate2SharedKey(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) require.Len(t, tgts.Signed.Delegations.Roles, 0) @@ -378,7 +378,7 @@ func TestApplyTargetsDelegationCreateEdit(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) // edit delegation @@ -403,7 +403,7 @@ func TestApplyTargetsDelegationCreateEdit(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) tgts := repo.Targets[data.CanonicalTargetsRole] @@ -446,7 +446,7 @@ func TestApplyTargetsDelegationEditNonExisting(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.Error(t, err) require.IsType(t, data.ErrInvalidRole{}, err) } @@ -477,7 +477,7 @@ func TestApplyTargetsDelegationCreateAlreadyExisting(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) // we have sufficient checks elsewhere we don't need to confirm that // creating fresh works here via more requires. @@ -505,7 +505,7 @@ func TestApplyTargetsDelegationCreateAlreadyExisting(t *testing.T) { ) // when attempting to create the same role again, check that we added a key - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) delegation, err := repo.GetDelegationRole("targets/level1") require.NoError(t, err) @@ -539,7 +539,7 @@ func TestApplyTargetsDelegationAlreadyExistingMergePaths(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) // we have sufficient checks elsewhere we don't need to confirm that // creating fresh works here via more requires. @@ -560,7 +560,7 @@ func TestApplyTargetsDelegationAlreadyExistingMergePaths(t *testing.T) { // when attempting to create the same role again, check that we // merged with previous details - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) delegation, err := repo.GetDelegationRole("targets/level1") require.NoError(t, err) @@ -595,7 +595,7 @@ func TestApplyTargetsDelegationInvalidRole(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.Error(t, err) } @@ -625,7 +625,7 @@ func TestApplyTargetsDelegationInvalidJSONContent(t *testing.T) { tdJSON[1:], ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.Error(t, err) } @@ -641,7 +641,7 @@ func TestApplyTargetsDelegationInvalidAction(t *testing.T) { nil, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.Error(t, err) } @@ -657,7 +657,7 @@ func TestApplyTargetsChangeInvalidType(t *testing.T) { nil, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.Error(t, err) } @@ -687,7 +687,7 @@ func TestApplyTargetsDelegationCreate2Deep(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) tgts := repo.Targets[data.CanonicalTargetsRole] @@ -724,7 +724,7 @@ func TestApplyTargetsDelegationCreate2Deep(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.NoError(t, err) tgts = repo.Targets["targets/level1"] @@ -772,7 +772,7 @@ func TestApplyTargetsDelegationParentDoesntExist(t *testing.T) { tdJSON, ) - err = applyTargetsChange(repo, ch) + err = applyTargetsChange(repo, nil, ch) require.Error(t, err) require.IsType(t, data.ErrInvalidRole{}, err) } @@ -809,7 +809,7 @@ func TestApplyChangelistCreatesDelegation(t *testing.T) { Data: fjson, })) - require.NoError(t, applyChangelist(repo, cl)) + require.NoError(t, applyChangelist(repo, nil, cl)) _, ok := repo.Targets["targets/level1"] require.True(t, ok, "Failed to create the delegation target") _, ok = repo.Targets["targets/level1"].Signed.Targets["latest"] @@ -860,7 +860,7 @@ func TestApplyChangelistTargetsToMultipleRoles(t *testing.T) { Data: nil, })) - require.NoError(t, applyChangelist(repo, cl)) + require.NoError(t, applyChangelist(repo, nil, cl)) _, ok := repo.Targets["targets/level1"].Signed.Targets["latest"] require.True(t, ok) _, ok = repo.Targets["targets/level2"] @@ -890,7 +890,7 @@ func TestApplyChangelistTargetsFailsNonexistentRole(t *testing.T) { ChangePath: "latest", Data: fjson, })) - err = applyChangelist(repo, cl) + err = applyChangelist(repo, nil, cl) require.Error(t, err) require.IsType(t, data.ErrInvalidRole{}, err) @@ -904,7 +904,7 @@ func TestApplyChangelistTargetsFailsNonexistentRole(t *testing.T) { Data: nil, })) - err = applyChangelist(repo, cl) + err = applyChangelist(repo, nil, cl) require.Error(t, err) require.IsType(t, data.ErrInvalidRole{}, err) } diff --git a/tuf/client/client.go b/client/tufclient.go similarity index 87% rename from tuf/client/client.go rename to client/tufclient.go index fde4b2ebc..1f7464dc5 100644 --- a/tuf/client/client.go +++ b/client/tufclient.go @@ -11,17 +11,17 @@ import ( "github.com/docker/notary/tuf/signed" ) -// Client is a usability wrapper around a raw TUF repo -type Client struct { +// TUFClient is a usability wrapper around a raw TUF repo +type TUFClient struct { remote store.RemoteStore cache store.MetadataStore oldBuilder tuf.RepoBuilder newBuilder tuf.RepoBuilder } -// NewClient initialized a Client with the given repo, remote source of content, and cache -func NewClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *Client { - return &Client{ +// NewTUFClient initialized a TUFClient with the given repo, remote source of content, and cache +func NewTUFClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *TUFClient { + return &TUFClient{ oldBuilder: oldBuilder, newBuilder: newBuilder, remote: remote, @@ -30,7 +30,7 @@ func NewClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, } // Update performs an update to the TUF repo as defined by the TUF spec -func (c *Client) Update() (*tuf.Repo, error) { +func (c *TUFClient) Update() (*tuf.Repo, *tuf.Repo, error) { // 1. Get timestamp // a. If timestamp error (verification, expired, etc...) download new root and return to 1. // 2. Check if local snapshot is up to date @@ -49,19 +49,19 @@ func (c *Client) Update() (*tuf.Repo, error) { if err := c.downloadRoot(); err != nil { logrus.Debug("Client Update (Root):", err) - return nil, err + return nil, nil, err } // If we error again, we now have the latest root and just want to fail // out as there's no expectation the problem can be resolved automatically logrus.Debug("retrying TUF client update") if err := c.update(); err != nil { - return nil, err + return nil, nil, err } } return c.newBuilder.Finish() } -func (c *Client) update() error { +func (c *TUFClient) update() error { if err := c.downloadTimestamp(); err != nil { logrus.Debugf("Client Update (Timestamp): %s", err.Error()) return err @@ -79,7 +79,7 @@ func (c *Client) update() error { } // downloadRoot is responsible for downloading the root.json -func (c *Client) downloadRoot() error { +func (c *TUFClient) downloadRoot() error { role := data.CanonicalRootRole consistentInfo := c.newBuilder.GetConsistentInfo(role) @@ -102,7 +102,7 @@ func (c *Client) downloadRoot() error { // downloadTimestamp is responsible for downloading the timestamp.json // Timestamps are special in that we ALWAYS attempt to download and only // use cache if the download fails (and the cache is still valid). -func (c *Client) downloadTimestamp() error { +func (c *TUFClient) downloadTimestamp() error { logrus.Debug("Loading timestamp...") role := data.CanonicalTimestampRole consistentInfo := c.newBuilder.GetConsistentInfo(role) @@ -131,7 +131,7 @@ func (c *Client) downloadTimestamp() error { } // downloadSnapshot is responsible for downloading the snapshot.json -func (c *Client) downloadSnapshot() error { +func (c *TUFClient) downloadSnapshot() error { logrus.Debug("Loading snapshot...") role := data.CanonicalSnapshotRole consistentInfo := c.newBuilder.GetConsistentInfo(role) @@ -143,11 +143,12 @@ func (c *Client) downloadSnapshot() error { // downloadTargets downloads all targets and delegated targets for the repository. // It uses a pre-order tree traversal as it's necessary to download parents first // to obtain the keys to validate children. -func (c *Client) downloadTargets() error { +func (c *TUFClient) downloadTargets() error { toDownload := []data.DelegationRole{{ BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole}, Paths: []string{""}, }} + for len(toDownload) > 0 { role := toDownload[0] toDownload = toDownload[1:] @@ -175,7 +176,7 @@ func (c *Client) downloadTargets() error { return nil } -func (c Client) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) { +func (c TUFClient) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) { logrus.Debugf("Loading %s...", role.Name) tgs := &data.SignedTargets{} @@ -190,7 +191,7 @@ func (c Client) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) return tgs.GetValidDelegations(role), nil } -func (c *Client) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) { +func (c *TUFClient) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) { cachedTS, err := c.cache.GetSized(consistentInfo.RoleName, consistentInfo.Length()) if err != nil { logrus.Debugf("no %s in cache, must download", consistentInfo.RoleName) @@ -206,7 +207,7 @@ func (c *Client) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]by return c.tryLoadRemote(consistentInfo, cachedTS) } -func (c *Client) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) { +func (c *TUFClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) { consistentName := consistentInfo.ConsistentName() raw, err := c.remote.GetSized(consistentName, consistentInfo.Length()) if err != nil { diff --git a/client/witness.go b/client/witness.go new file mode 100644 index 000000000..6600b60fc --- /dev/null +++ b/client/witness.go @@ -0,0 +1,57 @@ +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. 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")) + if err != nil { + return nil, err + } + defer cl.Close() + + successful := make([]string, 0, len(roles)) + for _, role := range roles { + // scope is role + c := changelist.NewTUFChange( + changelist.ActionUpdate, + role, + changelist.TypeWitness, + "", + nil, + ) + err = cl.Add(c) + if err != nil { + break + } + successful = append(successful, role) + } + return successful, err +} + +func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role string) error { + if r, ok := repo.Targets[role]; ok { + // role is already valid, mark for re-signing/updating + r.Dirty = true + return nil + } + 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 + } + } + // role isn't recognized, even as invalid + return data.ErrInvalidRole{ + Role: role, + Reason: "this role is not known", + } +} diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go index 8c58e9a09..06a6780ae 100644 --- a/cmd/notary/integration_test.go +++ b/cmd/notary/integration_test.go @@ -1366,3 +1366,163 @@ 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) + + // 12. check non-targets base roles all fail + for _, role := range []string{data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTimestampRole} { + // clear any pending changes to ensure errors are only related to the specific role we're trying to witness + _, err = runCommand(t, tempDir, "status", "gun", "--reset") + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "witness", "gun", role) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.Error(t, err) + } +} diff --git a/cmd/notary/main_test.go b/cmd/notary/main_test.go index c07ca3680..42e768e46 100644 --- a/cmd/notary/main_test.go +++ b/cmd/notary/main_test.go @@ -165,6 +165,7 @@ var exampleValidCommands = []string{ "delegation list repo", "delegation add repo targets/releases path/to/pem/file.pem", "delegation remove repo targets/releases", + "witness gun targets/releases", } // config parsing bugs are propagated in all commands diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index 7ef3e7f7d..671f73227 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -83,6 +83,12 @@ var cmdTUFVerifyTemplate = usageTemplate{ Long: "Verifies if the data passed in STDIN is included in the remote trusted collection identified by the Globally Unique Name.", } +var cmdWitnessTemplate = usageTemplate{ + Use: "witness [ GUN ] ...", + Short: "Marks roles to be re-signed the next time they're published", + Long: "Marks roles to be re-signed the next time they're published. Currently will always bump version and expiry for role. N.B. behaviour may change when thresholding is introduced.", +} + type tufCommander struct { // these need to be set configGetter func() (*viper.Viper, error) @@ -140,6 +146,46 @@ func (t *tufCommander) AddToCommand(cmd *cobra.Command) { cmdTUFVerify.Flags().StringVarP(&t.output, "output", "o", "", "Write to a file, instead of STDOUT") cmdTUFVerify.Flags().BoolVarP(&t.quiet, "quiet", "q", false, "No output except for errors") cmd.AddCommand(cmdTUFVerify) + + cmdWitness := cmdWitnessTemplate.ToCommand(t.tufWitness) + cmd.AddCommand(cmdWitness) +} + +func (t *tufCommander) tufWitness(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + cmd.Usage() + return fmt.Errorf("Please provide a GUN and at least one role to witness") + } + config, err := t.configGetter() + if err != nil { + return err + } + trustPin, err := getTrustPinning(config) + if err != nil { + return err + } + gun := args[0] + roles := args[1:] + + // no online operations are performed by add so the transport argument + // should be nil + nRepo, err := notaryclient.NewNotaryRepository( + config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever, trustPin) + if err != nil { + return err + } + + success, err := nRepo.Witness(roles...) + if err != nil { + cmd.Printf("Some roles have failed to be marked for witnessing: %s", err.Error()) + } + + cmd.Printf( + "The following roles were successfully marked for witnessing on the next publish:\n\t- %s\n", + strings.Join(success, "\n\t- "), + ) + + return nil } func (t *tufCommander) tufAddByHash(cmd *cobra.Command, args []string) error { diff --git a/tuf/builder.go b/tuf/builder.go index 46fd02505..9e1d3efb6 100644 --- a/tuf/builder.go +++ b/tuf/builder.go @@ -59,7 +59,7 @@ type RepoBuilder interface { Load(roleName string, content []byte, minVersion int, allowExpired bool) error GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) - Finish() (*Repo, error) + Finish() (*Repo, *Repo, error) BootstrapNewBuilder() RepoBuilder // informative functions @@ -80,7 +80,7 @@ func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, in func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) { return nil, 0, ErrBuildDone } -func (f finishedBuilder) Finish() (*Repo, error) { return nil, ErrBuildDone } +func (f finishedBuilder) Finish() (*Repo, *Repo, error) { return nil, nil, ErrBuildDone } func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f } func (f finishedBuilder) IsLoaded(roleName string) bool { return false } func (f finishedBuilder) GetLoadedVersion(roleName string) int { return 0 } @@ -90,12 +90,21 @@ 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 &repoBuilderWrapper{RepoBuilder: &repoBuilder{ - repo: NewRepo(cs), - gun: gun, - trustpin: trustpin, - loadedNotChecksummed: make(map[string][]byte), - }} + 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: repo, + invalidRoles: NewRepo(nil), + gun: gun, + trustpin: trustpin, + loadedNotChecksummed: make(map[string][]byte), + }, + } } // repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps @@ -104,7 +113,7 @@ type repoBuilderWrapper struct { RepoBuilder } -func (rbw *repoBuilderWrapper) Finish() (*Repo, error) { +func (rbw *repoBuilderWrapper) Finish() (*Repo, *Repo, error) { switch rbw.RepoBuilder.(type) { case finishedBuilder: return rbw.RepoBuilder.Finish() @@ -117,7 +126,8 @@ func (rbw *repoBuilderWrapper) Finish() (*Repo, error) { // repoBuilder actually builds a tuf.Repo type repoBuilder struct { - repo *Repo + repo *Repo + invalidRoles *Repo // needed for root trust pininng verification gun string @@ -136,13 +146,14 @@ type repoBuilder struct { nextRootChecksum *data.FileMeta } -func (rb *repoBuilder) Finish() (*Repo, error) { - return rb.repo, nil +func (rb *repoBuilder) Finish() (*Repo, *Repo, error) { + return rb.repo, rb.invalidRoles, nil } func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder { return &repoBuilderWrapper{RepoBuilder: &repoBuilder{ repo: NewRepo(rb.repo.cryptoService), + invalidRoles: NewRepo(nil), gun: rb.gun, loadedNotChecksummed: make(map[string][]byte), trustpin: rb.trustpin, @@ -534,7 +545,8 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio return err } - signedObj, err := rb.bytesToSignedAndValidateSigs(delegationRole.BaseRole, content) + // bytesToSigned checks checksum + signedObj, err := rb.bytesToSigned(content, roleName) if err != nil { return err } @@ -545,11 +557,19 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio } 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 + } + + // verify signature + if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil { + rb.invalidRoles.Targets[roleName] = signedTargets return err } if !allowExpired { // check must go at the end because all other validation should pass if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil { + rb.invalidRoles.Targets[roleName] = signedTargets return err } } diff --git a/tuf/builder_test.go b/tuf/builder_test.go index 28bee4296..f88c98358 100644 --- a/tuf/builder_test.go +++ b/tuf/builder_test.go @@ -98,6 +98,84 @@ 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 TestBuilderLoadInvalidDelegationsOldVersion(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{}) + delete(tufRepo.Targets, "targets/a") + + // load targets/a with high min-version so this one is too old + err = builder.Load( + "targets/a", + meta["targets/a"], + 10, + false, + ) + require.Error(t, err) + require.IsType(t, signed.ErrLowVersion{}, err) + + _, invalid, err := builder.Finish() + require.NoError(t, err) + _, ok := invalid.Targets["targets/a"] + require.False(t, ok) +} + func TestBuilderAcceptRoleOnce(t *testing.T) { meta, gun := getSampleMeta(t) builder := tuf.NewRepoBuilder(gun, nil, trustpinning.TrustPinConfig{}) @@ -128,7 +206,7 @@ func TestBuilderStopsAcceptingOrProducingDataOnceDone(t *testing.T) { require.True(t, builder.IsLoaded(roleName)) } - _, err := builder.Finish() + _, _, err := builder.Finish() require.NoError(t, err) err = builder.Load("targets/a", meta["targets/a"], 1, false) @@ -143,7 +221,7 @@ func TestBuilderStopsAcceptingOrProducingDataOnceDone(t *testing.T) { require.Equal(t, tuf.ErrBuildDone, err) for _, b := range []tuf.RepoBuilder{builder, bootstrapped} { - _, err = b.Finish() + _, _, err = b.Finish() require.Error(t, err) require.Equal(t, tuf.ErrBuildDone, err) diff --git a/tuf/client/errors.go b/tuf/client/errors.go deleted file mode 100644 index 28a6836af..000000000 --- a/tuf/client/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package client - -import ( - "fmt" -) - -// ErrCorruptedCache - local data is incorrect -type ErrCorruptedCache struct { - file string -} - -func (e ErrCorruptedCache) Error() string { - return fmt.Sprintf("cache is corrupted: %s", e.file) -} diff --git a/tuf/signed/verify.go b/tuf/signed/verify.go index fadcbd034..2a497b707 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 diff --git a/tuf/tuf.go b/tuf/tuf.go index 34621b37f..fc87ba447 100644 --- a/tuf/tuf.go +++ b/tuf/tuf.go @@ -77,11 +77,10 @@ type Repo struct { // If the Repo will only be used for reading, the CryptoService // can be nil. func NewRepo(cryptoService signed.CryptoService) *Repo { - repo := &Repo{ + return &Repo{ Targets: make(map[string]*data.SignedTargets), cryptoService: cryptoService, } - return repo } // AddBaseKeys is used to add keys to the role in root.json