From fa788cb2a92ab3f6b74d8402ad0ca8bf8b324ce6 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Tue, 22 Dec 2015 14:49:43 -0800 Subject: [PATCH] make x509 certs viable as delegated public key object Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 2 +- client/client_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++ tuf/tuf.go | 14 +++++-- 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/client/client.go b/client/client.go index 537673a49e..9d511af31d 100644 --- a/client/client.go +++ b/client/client.go @@ -367,7 +367,7 @@ func (r *NotaryRepository) RemoveDelegation(name string) error { // AddTarget creates new changelist entries to add a target to the given roles // in the repository when the changelist gets appied at publish time. -// If roles are unspecified, the default role is "target". +// If roles are unspecified, the default role is "targets". func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error { cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) diff --git a/client/client_test.go b/client/client_test.go index 4aa032639a..5b82789118 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -15,6 +15,7 @@ import ( "sort" "strings" "testing" + "time" "github.com/Sirupsen/logrus" ctxu "github.com/docker/distribution/context" @@ -1581,6 +1582,93 @@ func TestPublishDelegations(t *testing.T) { } } +// Publishing delegations works so long as the delegation parent exists by the +// time that delegation addition change is applied. Most of the tests for +// applying delegation changes in in helpers_test.go (applyTargets tests), so +// this is just a sanity test to make sure Publish calls it correctly and +// no fallback happens. +func TestPublishDelegationsX509(t *testing.T) { + var tempDirs [2]string + for i := 0; i < 2; i++ { + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + assert.NoError(t, err, "failed to create a temporary directory: %s", err) + defer os.RemoveAll(tempBaseDir) + tempDirs[i] = tempBaseDir + } + + gun := "docker.com/notary" + ts := fullTestServer(t) + defer ts.Close() + + repo1, _ := initializeRepo(t, data.ECDSAKey, tempDirs[0], gun, ts.URL, false) + delgKey, err := repo1.CryptoService.Create("targets/a", data.ECDSAKey) + assert.NoError(t, err, "error creating delegation key") + + start := time.Now() + privKey, _, err := repo1.CryptoService.GetPrivateKey(delgKey.ID()) + assert.NoError(t, err) + cert, err := cryptoservice.GenerateCertificate( + privKey, "targets/a", start, start.AddDate(1, 0, 0), + ) + assert.NoError(t, err) + delgCert := data.NewECDSAx509PublicKey(trustmanager.CertToPEM(cert)) + + // This should publish fine, even though targets/a/b is dependent upon + // targets/a, because these should execute in order + for _, delgName := range []string{"targets/a", "targets/a/b", "targets/c"} { + assert.NoError(t, + repo1.AddDelegation(delgName, 1, []data.PublicKey{delgCert}, []string{""}), + "error creating delegation") + } + assert.Len(t, getChanges(t, repo1), 3, "wrong number of changelist files found") + assert.NoError(t, repo1.Publish()) + assert.Len(t, getChanges(t, repo1), 0, "wrong number of changelist files found") + + // this should not publish, because targets/z doesn't exist + assert.NoError(t, + repo1.AddDelegation("targets/z/y", 1, []data.PublicKey{delgCert}, []string{""}), + "error creating delegation") + assert.Len(t, getChanges(t, repo1), 1, "wrong number of changelist files found") + assert.Error(t, repo1.Publish()) + assert.Len(t, getChanges(t, repo1), 1, "wrong number of changelist files found") + + // Create a new repo and pull from the server + repo2, err := NewNotaryRepository(tempDirs[1], gun, ts.URL, + http.DefaultTransport, passphraseRetriever) + assert.NoError(t, err, "error creating repository: %s", err) + + // pull + _, err = repo2.ListTargets() + assert.NoError(t, err, "unable to pull repo") + + for _, repo := range []*NotaryRepository{repo1, repo2} { + // targets should have delegations targets/a and targets/c + targets := repo.tufRepo.Targets[data.CanonicalTargetsRole] + assert.Len(t, targets.Signed.Delegations.Roles, 2) + assert.Len(t, targets.Signed.Delegations.Keys, 1) + + _, ok := targets.Signed.Delegations.Keys[delgCert.ID()] + assert.True(t, ok) + + foundRoleNames := make(map[string]bool) + for _, r := range targets.Signed.Delegations.Roles { + foundRoleNames[r.Name] = true + } + assert.True(t, foundRoleNames["targets/a"]) + assert.True(t, foundRoleNames["targets/c"]) + + // targets/a should have delegation targets/a/b only + a := repo.tufRepo.Targets["targets/a"] + assert.Len(t, a.Signed.Delegations.Roles, 1) + assert.Len(t, a.Signed.Delegations.Keys, 1) + + _, ok = a.Signed.Delegations.Keys[delgCert.ID()] + assert.True(t, ok) + + assert.Equal(t, "targets/a/b", a.Signed.Delegations.Roles[0].Name) + } +} + // If a changelist specifies a particular role to push targets to, and there // is no such role, publish will try to publish to its parent. If the parent // doesn't work, it falls back on its parent, and so forth, and eventually diff --git a/tuf/tuf.go b/tuf/tuf.go index aed73621f0..6aa94cc032 100644 --- a/tuf/tuf.go +++ b/tuf/tuf.go @@ -530,9 +530,17 @@ func (tr *Repo) VerifyCanSign(roleName string) error { } for _, keyID := range role.KeyIDs { - p, _, err := tr.cryptoService.GetPrivateKey(keyID) - if err == nil && p != nil { - return nil + k := tr.keysDB.GetKey(keyID) + canonicalID, err := utils.CanonicalKeyID(k) + check := []string{keyID} + if err == nil { + check = append(check, canonicalID) + } + for _, id := range check { + p, _, err := tr.cryptoService.GetPrivateKey(id) + if err == nil && p != nil { + return nil + } } } return signed.ErrNoKeys{KeyIDs: role.KeyIDs}