NotaryRepository.Publish supports server managing snapshot keys.

When publishing, do not sign and send the snapshot metadata if the
client does not have the snapshot key.  If the server sends back
an error, then it also does not have a snapshot key and the
client should propogate the no signing key error.

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2015-12-07 16:50:35 -08:00
parent 4b46a34524
commit 39d79d9844
4 changed files with 288 additions and 143 deletions

View File

@ -345,15 +345,12 @@ func (r *NotaryRepository) Publish() error {
if err != nil {
if _, ok := err.(store.ErrMetaNotFound); ok {
// if the remote store return a 404 (translated into ErrMetaNotFound),
// the repo hasn't been initialized yet. Attempt to load it from disk.
// there is no trust data for yet. Attempt to load it from disk.
err := r.bootstrapRepo()
if err != nil {
// Repo hasn't been initialized, It must be initialized before
// it can be published. Return an error and let caller determine
// what it wants to do.
logrus.Debug(err.Error())
logrus.Debug("Repository not initialized during Publish")
return &ErrRepoNotInitialized{}
// There are lots of reasons there might be an error, such as
// corrupt metadata. We need better errors from bootstrapRepo.
return err
}
// We had local data but the server doesn't know about the repo yet,
// ensure we will push the initial root file
@ -370,8 +367,8 @@ func (r *NotaryRepository) Publish() error {
}
} else {
// If we were successfully able to bootstrap the client (which only pulls
// root.json), update it the rest of the tuf metadata in preparation for
// applying the changelist.
// root.json), update it with the rest of the tuf metadata in
// preparation for applying the changelist.
err = c.Update()
if err != nil {
if err, ok := err.(signed.ErrExpired); ok {
@ -391,25 +388,50 @@ func (r *NotaryRepository) Publish() error {
return err
}
// these are the tuf files we will need to update, serialized as JSON before
// we send anything to remote
update := make(map[string][]byte)
// check if our root file is nearing expiry. Resign if it is.
if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty {
if err != nil {
return err
}
root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"))
if err != nil {
return err
}
updateRoot = true
}
// we will always resign targets and snapshots
targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"))
if updateRoot {
rootJSON, err := json.Marshal(root)
if err != nil {
return err
}
update[data.CanonicalRootRole] = rootJSON
}
// we will always resign targets, and snapshots if we have the snapshots key
targetsJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalTargetsRole)
if err != nil {
return err
}
snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"))
if err != nil {
return err
update[data.CanonicalTargetsRole] = targetsJSON
// do not update the snapshot role if we do not have the snapshot key or
// any snapshot data. There might not be any snapshot data the repo was
// initialized with the snapshot signing role delegated to the server.
// The repo might have snapshot data, because it was requested from
// the server by listing, but not have the snapshot key, so signing will
// fail.
clientCantSignSnapshot := true
if r.tufRepo.Snapshot != nil {
snapshotJSON, err := serializeCanonicalRole(
r.tufRepo, data.CanonicalSnapshotRole)
if err == nil { // we have the key - snapshot signed, let's update it
update[data.CanonicalSnapshotRole] = snapshotJSON
clientCantSignSnapshot = false
} else if _, ok := err.(signed.ErrNoKeys); ok {
logrus.Debugf("Client does not have the key to sign snapshot. " +
"Assuming that server should sign the snapshot.")
} else {
return err
}
}
remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
@ -417,28 +439,16 @@ func (r *NotaryRepository) Publish() error {
return err
}
// ensure we can marshal all the json before sending anything to remote
targetsJSON, err := json.Marshal(targets)
if err != nil {
return err
}
snapshotJSON, err := json.Marshal(snapshot)
if err != nil {
return err
}
update := make(map[string][]byte)
// if we need to update the root, marshal it and push the update to remote
if updateRoot {
rootJSON, err := json.Marshal(root)
if err != nil {
return err
}
update["root"] = rootJSON
}
update["targets"] = targetsJSON
update["snapshot"] = snapshotJSON
err = remote.SetMultiMeta(update)
if err != nil {
// TODO: this isn't exactly right, since there could be lots of
// reasons a request 400'ed. Need better error translation from HTTP
// status codes maybe back to the server errors?
if _, ok := err.(store.ErrInvalidOperation); ok && clientCantSignSnapshot {
return signed.ErrNoKeys{
KeyIDs: r.tufRepo.Root.Signed.Roles[data.CanonicalSnapshotRole].KeyIDs,
}
}
return err
}
err = cl.Clear("")
@ -451,6 +461,11 @@ func (r *NotaryRepository) Publish() error {
return nil
}
// bootstrapRepo loads the repository from the local file system. This attempts
// to load metadata for all roles. Since server snapshots are supported,
// if the snapshot metadata fails to load, that's ok.
// This can also be unified with some cache reading tools from tuf/client.
// This assumes that bootstrapRepo is only used by Publish()
func (r *NotaryRepository) bootstrapRepo() error {
kdb := keys.NewDB()
tufRepo := tuf.NewRepo(kdb, r.CryptoService)
@ -479,16 +494,16 @@ func (r *NotaryRepository) bootstrapRepo() error {
return err
}
tufRepo.SetTargets("targets", targets)
snapshotJSON, err := r.fileStore.GetMeta("snapshot", 0)
if err != nil {
return err
if err == nil {
snapshot := &data.SignedSnapshot{}
err = json.Unmarshal(snapshotJSON, snapshot)
if err != nil {
return err
}
tufRepo.SetSnapshot(snapshot)
}
snapshot := &data.SignedSnapshot{}
err = json.Unmarshal(snapshotJSON, snapshot)
if err != nil {
return err
}
tufRepo.SetSnapshot(snapshot)
r.tufRepo = tufRepo
@ -502,7 +517,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
if err != nil {
return err
}
err = r.fileStore.SetMeta("root", rootJSON)
err = r.fileStore.SetMeta(data.CanonicalRootRole, rootJSON)
if err != nil {
return err
}
@ -535,7 +550,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
return err
}
return r.fileStore.SetMeta("snapshot", snapshotJSON)
return r.fileStore.SetMeta(data.CanonicalSnapshotRole, snapshotJSON)
}
func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {

View File

@ -10,7 +10,6 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/Sirupsen/logrus"
@ -23,6 +22,7 @@ import (
"github.com/docker/notary/server/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/store"
"github.com/jfrazelle/go/canonical/json"
"github.com/stretchr/testify/assert"
@ -212,7 +212,7 @@ func TestInitRepoServerManagesTimestampAndSnapshotKeys(t *testing.T) {
// This creates a new KeyFileStore in the repo's base directory and makes sure
// the repo has the right number of keys
func assertRepoHasExpectedKeys(t *testing.T, repo *NotaryRepository,
rootKeyID string, serverManagesSnapshot bool) {
rootKeyID string, expectedSnapshotKey bool) {
// The repo should have a keyFileStore and have created keys using it,
// so create a new KeyFileStore, and check that the keys do exist and are
@ -241,11 +241,11 @@ func assertRepoHasExpectedKeys(t *testing.T, repo *NotaryRepository,
// there may be a snapshots key, depending on whether the server is managing
// the snapshots key
_, ok := roles[data.CanonicalSnapshotRole]
if serverManagesSnapshot {
if expectedSnapshotKey {
assert.True(t, ok, "missing snapshot key")
} else {
assert.False(t, ok,
"there should be no snapshot key because the server manages it")
} else {
assert.True(t, ok, "missing snapshot key")
}
// The server manages the timestamp key - there should not be a timestamp
@ -271,68 +271,62 @@ func assertRepoHasExpectedCerts(t *testing.T, repo *NotaryRepository) {
assert.NotEqual(t, certID, "")
}
// Sanity check the TUF metadata files. Verify that they exist, the JSON is
// well-formed, and the signatures exist. For the root.json file, also check
// that the root, snapshot, and targets key IDs are present.
// Sanity check the TUF metadata files. Verify that it exists for a particular
// role, the JSON is well-formed, and the signatures exist.
// For the root.json file, also check that the root, snapshot, and
// targets key IDs are present.
func assertRepoHasExpectedMetadata(t *testing.T, repo *NotaryRepository,
serverManagesSnapshot bool) {
role string, expected bool) {
expectedTUFMetadataFiles := []string{
filepath.Join(tufDir, filepath.FromSlash(repo.gun), "metadata", "root.json"),
filepath.Join(tufDir, filepath.FromSlash(repo.gun), "metadata", "snapshot.json"),
filepath.Join(tufDir, filepath.FromSlash(repo.gun), "metadata", "targets.json"),
filename := filepath.Join(tufDir, filepath.FromSlash(repo.gun),
"metadata", role+".json")
fullPath := filepath.Join(repo.baseDir, filename)
_, err := os.Stat(fullPath)
if expected {
assert.NoError(t, err, "missing TUF metadata file: %s", filename)
} else {
assert.Error(t, err,
"%s metadata should not exist, but does: %s", role, filename)
return
}
for _, filename := range expectedTUFMetadataFiles {
fullPath := filepath.Join(repo.baseDir, filename)
_, err := os.Stat(fullPath)
// special case snapshot metadata - if the server manages the snapshot key
// there should be no snapshot metadata file.
if strings.HasSuffix(filename, "snapshot.json") && serverManagesSnapshot {
assert.Error(t, err,
"snapshot metadata should not exist, but does: %s", filename)
continue
} else {
assert.NoError(t, err, "missing TUF metadata file: %s", filename)
}
jsonBytes, err := ioutil.ReadFile(fullPath)
assert.NoError(t, err, "error reading TUF metadata file %s: %s", filename, err)
jsonBytes, err := ioutil.ReadFile(fullPath)
assert.NoError(t, err, "error reading TUF metadata file %s: %s", filename, err)
var decoded data.Signed
err = json.Unmarshal(jsonBytes, &decoded)
assert.NoError(t, err, "error parsing TUF metadata file %s: %s", filename, err)
var decoded data.Signed
err = json.Unmarshal(jsonBytes, &decoded)
assert.NoError(t, err, "error parsing TUF metadata file %s: %s", filename, err)
assert.Len(t, decoded.Signatures, 1,
"incorrect number of signatures in TUF metadata file %s", filename)
assert.Len(t, decoded.Signatures, 1,
"incorrect number of signatures in TUF metadata file %s", filename)
assert.NotEmpty(t, decoded.Signatures[0].KeyID,
"empty key ID field in TUF metadata file %s", filename)
assert.NotEmpty(t, decoded.Signatures[0].Method,
"empty method field in TUF metadata file %s", filename)
assert.NotEmpty(t, decoded.Signatures[0].Signature,
"empty signature in TUF metadata file %s", filename)
assert.NotEmpty(t, decoded.Signatures[0].KeyID,
"empty key ID field in TUF metadata file %s", filename)
assert.NotEmpty(t, decoded.Signatures[0].Method,
"empty method field in TUF metadata file %s", filename)
assert.NotEmpty(t, decoded.Signatures[0].Signature,
"empty signature in TUF metadata file %s", filename)
// Special case for root.json: also check that the signed
// content for keys and roles
if role == data.CanonicalRootRole {
var decodedRoot data.Root
err := json.Unmarshal(decoded.Signed, &decodedRoot)
assert.NoError(t, err, "error parsing root.json signed section: %s", err)
// Special case for root.json: also check that the signed
// content for keys and roles
if strings.HasSuffix(filename, "root.json") {
var decodedRoot data.Root
err := json.Unmarshal(decoded.Signed, &decodedRoot)
assert.NoError(t, err, "error parsing root.json signed section: %s", err)
assert.Equal(t, "Root", decodedRoot.Type, "_type mismatch in root.json")
assert.Equal(t, "Root", decodedRoot.Type, "_type mismatch in root.json")
// Expect 1 key for each valid role in the Keys map - one for
// each of root, targets, snapshot, timestamp
assert.Len(t, decodedRoot.Keys, len(data.ValidRoles),
"wrong number of keys in root.json")
assert.Len(t, decodedRoot.Roles, len(data.ValidRoles),
"wrong number of roles in root.json")
// Expect 1 key for each valid role in the Keys map - one for
// each of root, targets, snapshot, timestamp
assert.Len(t, decodedRoot.Keys, len(data.ValidRoles),
"wrong number of keys in root.json")
assert.Len(t, decodedRoot.Roles, len(data.ValidRoles),
"wrong number of roles in root.json")
for role := range data.ValidRoles {
_, ok := decodedRoot.Roles[role]
assert.True(t, ok, "Missing role %s in root.json", role)
}
for role := range data.ValidRoles {
_, ok := decodedRoot.Roles[role]
assert.True(t, ok, "Missing role %s in root.json", role)
}
}
}
@ -351,9 +345,12 @@ func testInitRepo(t *testing.T, rootType string, serverManagesSnapshot bool) {
repo, rootKeyID := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL,
serverManagesSnapshot)
assertRepoHasExpectedKeys(t, repo, rootKeyID, serverManagesSnapshot)
assertRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot)
assertRepoHasExpectedCerts(t, repo)
assertRepoHasExpectedMetadata(t, repo, serverManagesSnapshot)
assertRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
assertRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole,
!serverManagesSnapshot)
}
// TestInitRepoAttemptsExceeded tests error handling when passphrase.Retriever
@ -533,10 +530,6 @@ func testListEmptyTargets(t *testing.T, rootType string) {
repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
// tests need to manually boostrap timestamp as client doesn't generate it
err = repo.tufRepo.InitTimestamp()
assert.NoError(t, err, "error creating repository: %s", err)
_, err = repo.ListTargets()
assert.Error(t, err) // no trust data
}
@ -766,19 +759,30 @@ func testGetChangelist(t *testing.T, rootType string) {
assert.Equal(t, "latest", latestChange.Path())
}
// TestPublish creates a repo, instantiates a notary server, and publishes
// the repo to the server.
// Create a repo, instantiate a notary server, and publish the repo to the
// server, signing all the non-timestamp metadata.
// We test this with both an RSA and ECDSA root key
func TestPublish(t *testing.T) {
testPublish(t, data.ECDSAKey)
func TestPublishClientHasSnapshotKey(t *testing.T) {
testPublish(t, data.ECDSAKey, false)
if !testing.Short() {
testPublish(t, data.RSAKey)
testPublish(t, data.RSAKey, false)
}
}
func testPublish(t *testing.T, rootType string) {
// Create a repo, instantiate a notary server (designating the server as the
// snapshot signer) , and publish the repo to the server, signing the root and
// targets metadata only. The server should sign just fine.
// We test this with both an RSA and ECDSA root key
func TestPublishAfterInitServerHasSnapshotKey(t *testing.T) {
testPublish(t, data.ECDSAKey, true)
if !testing.Short() {
testPublish(t, data.RSAKey, true)
}
}
func testPublish(t *testing.T, rootType string, serverManagesSnapshot bool) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
@ -786,46 +790,172 @@ func testPublish(t *testing.T, rootType string) {
gun := "docker.com/notary"
ts := fullTestServer(t)
defer ts.Close()
repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL,
serverManagesSnapshot)
assertPublishSucceeds(t, repo)
}
func assertPublishSucceeds(t *testing.T, repo1 *NotaryRepository) {
// Create 2 targets
latestTarget := addTarget(t, repo, "latest", "../fixtures/intermediate-ca.crt")
currentTarget := addTarget(t, repo, "current", "../fixtures/intermediate-ca.crt")
assert.Len(t, getChanges(t, repo), 2, "wrong number of changelist files found")
latestTarget := addTarget(t, repo1, "latest", "../fixtures/intermediate-ca.crt")
currentTarget := addTarget(t, repo1, "current", "../fixtures/intermediate-ca.crt")
assert.Len(t, getChanges(t, repo1), 2, "wrong number of changelist files found")
// Now test Publish
err = repo.Publish()
err := repo1.Publish()
assert.NoError(t, err)
assert.Len(t, getChanges(t, repo), 0, "wrong number of changelist files found")
assert.Len(t, getChanges(t, repo1), 0, "wrong number of changelist files found")
// Create a new repo and pull from the server
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir2)
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
repo2, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever)
repo2, err := NewNotaryRepository(tempBaseDir, repo1.gun, repo1.baseURL,
http.DefaultTransport, passphraseRetriever)
assert.NoError(t, err, "error creating repository: %s", err)
targets, err := repo2.ListTargets()
assert.NoError(t, err)
// Should be two targets
assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets")
for _, repo := range []*NotaryRepository{repo1, repo2} {
targets, err := repo.ListTargets()
assert.NoError(t, err)
sort.Stable(targetSorter(targets))
assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets")
assert.Equal(t, currentTarget, targets[0], "current target does not match")
assert.Equal(t, latestTarget, targets[1], "latest target does not match")
sort.Stable(targetSorter(targets))
// Also test GetTargetByName
newLatestTarget, err := repo2.GetTargetByName("latest")
assert.Equal(t, currentTarget, targets[0], "current target does not match")
assert.Equal(t, latestTarget, targets[1], "latest target does not match")
// Also test GetTargetByName
newLatestTarget, err := repo.GetTargetByName("latest")
assert.NoError(t, err)
assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
newCurrentTarget, err := repo.GetTargetByName("current")
assert.NoError(t, err)
assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match")
}
}
// After pulling a repo from the server, so there is a snapshots metadata file,
// push a different target to the server (the server is still the snapshot
// signer). The server should sign just fine.
// We test this with both an RSA and ECDSA root key
func TestPublishAfterPullServerHasSnapshotKey(t *testing.T) {
testPublishAfterPullServerHasSnapshotKey(t, data.ECDSAKey)
if !testing.Short() {
testPublishAfterPullServerHasSnapshotKey(t, data.RSAKey)
}
}
func testPublishAfterPullServerHasSnapshotKey(t *testing.T, rootType string) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
gun := "docker.com/notary"
ts := fullTestServer(t)
defer ts.Close()
repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, true)
// no timestamp metadata because that comes from the server
assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
// no snapshot metadata because that comes from the server
assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
// Publish something
published := addTarget(t, repo, "v1", "../fixtures/intermediate-ca.crt")
err = repo.Publish()
assert.NoError(t, err)
assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
// still no timestamp or snapshot metadata info
assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
newCurrentTarget, err := repo2.GetTargetByName("current")
// list, so that the snapshot metadata is pulled from server
targets, err := repo.ListTargets()
assert.NoError(t, err)
assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match")
assert.Equal(t, []*Target{published}, targets)
// listing downloaded the timestamp and snapshot metadata info
assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, true)
assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
// Publish again should succeed
addTarget(t, repo, "v2", "../fixtures/intermediate-ca.crt")
err = repo.Publish()
assert.NoError(t, err)
}
// If neither the client nor the server has the snapshot key, signing will fail
// with an ErrNoKeys error.
// We test this with both an RSA and ECDSA root key
func TestPublishNoOneHasSnapshotKey(t *testing.T) {
testPublishNoOneHasSnapshotKey(t, data.ECDSAKey)
if !testing.Short() {
testPublishNoOneHasSnapshotKey(t, data.RSAKey)
}
}
func testPublishNoOneHasSnapshotKey(t *testing.T, rootType string) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
gun := "docker.com/notary"
ts := fullTestServer(t)
defer ts.Close()
// create repo and delete the snapshot key and metadata
repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
snapshotRole, ok := repo.tufRepo.Root.Signed.Roles[data.CanonicalSnapshotRole]
assert.True(t, ok)
for _, keyID := range snapshotRole.KeyIDs {
repo.CryptoService.RemoveKey(keyID)
}
// Publish something
addTarget(t, repo, "v1", "../fixtures/intermediate-ca.crt")
err = repo.Publish()
assert.Error(t, err)
assert.IsType(t, signed.ErrNoKeys{}, err)
}
// If the snapshot metadata is corrupt, whether the client or server has the
// snapshot key, we can't publish.
// We test this with both an RSA and ECDSA root key
func TestPublishSnapshotCorrupt(t *testing.T) {
testPublishSnapshotCorrupt(t, data.ECDSAKey, true)
testPublishSnapshotCorrupt(t, data.ECDSAKey, false)
if !testing.Short() {
testPublishSnapshotCorrupt(t, data.RSAKey, true)
testPublishSnapshotCorrupt(t, data.RSAKey, false)
}
}
func testPublishSnapshotCorrupt(t *testing.T, rootType string, serverManagesSnapshot bool) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
gun := "docker.com/notary"
ts := fullTestServer(t)
defer ts.Close()
repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, serverManagesSnapshot)
// write a corrupt snapshots file
repo.fileStore.SetMeta(data.CanonicalSnapshotRole, []byte("this isn't JSON"))
addTarget(t, repo, "v1", "../fixtures/intermediate-ca.crt")
err = repo.Publish()
assert.Error(t, err)
}
func TestRotate(t *testing.T) {

View File

@ -63,10 +63,10 @@ func (e ErrInvalidKeyLength) Error() string {
// ErrNoKeys indicates no signing keys were found when trying to sign
type ErrNoKeys struct {
keyIDs []string
KeyIDs []string
}
func (e ErrNoKeys) Error() string {
return fmt.Sprintf("could not find necessary signing keys, at least one of these keys must be available: %s",
strings.Join(e.keyIDs, ", "))
strings.Join(e.KeyIDs, ", "))
}

View File

@ -46,7 +46,7 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
// Check to ensure we have at least one signing key
if len(privKeys) == 0 {
return ErrNoKeys{keyIDs: ids}
return ErrNoKeys{KeyIDs: ids}
}
// Do signing and generate list of signatures