Skip to content

Commit

Permalink
Import only one root key, and add ability to import root cert
Browse files Browse the repository at this point in the history
Addresses notaryproject#731

Signed-off-by: David Wake <dwake@box.com>
  • Loading branch information
David Wake committed Jul 8, 2016
1 parent 414ee1d commit f039a08
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 118 deletions.
55 changes: 40 additions & 15 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,40 @@ func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) {
return x509PublicKey, nil
}

// Initialize creates a new repository by using rootKey as the root Key for the
// Initialize creates a new repository by using rootKeyID to identify the root Key for the
// TUF repository. The server must be reachable (and is asked to generate a
// timestamp key and possibly other serverManagedRoles), but the created repository
// result is only stored on local disk, not published to the server. To do that,
// use r.Publish() eventually.
func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...string) error {
privKeys := []data.PrivateKey{}
for _, keyID := range rootKeyIDs {
privKey, _, err := r.CryptoService.GetPrivateKey(keyID)
if err != nil {
return err
}
privKeys = append(privKeys, privKey)
func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error {
return r.InitializeWithPubKey(rootKeyID, (data.PublicKey)(nil), serverManagedRoles...)
}

// KeyPairVerifier is used to verify that a private key and public key form a matching keypair
type KeyPairVerifier func(privKey data.PrivateKey, pubKey data.PublicKey) error

func defaultKeyPairVerifier() KeyPairVerifier {
return signed.VerifyPublicKeyMatchesPrivateKey
}

// InitializeWithPubKey creates a new repository by using rootPrivKeyID to identify the root private key for the
// TUF repository.
// If rootPubKey is non-nil, it must form a valid keypair together with the private key identified by rootPrivKeyID,
// otherwise an error is thrown. The repository is then initialized with the given root keypair.
// If rootPubKey is nil, then the root public key is generated automatically.
// The server must be reachable (and is asked to generate a
// timestamp key and possibly other serverManagedRoles), but the created repository
// result is only stored on local disk, not published to the server. To do that,
// use r.Publish() eventually.
func (r *NotaryRepository) InitializeWithPubKey(rootPrivKeyID string, rootPubKey data.PublicKey, serverManagedRoles ...string) error {
return r.InitializeWithPubKeyAndVerifier(rootPrivKeyID, rootPubKey, defaultKeyPairVerifier(), serverManagedRoles...)
}

// InitializeWithPubKeyAndVerifier allows the keyPairVerifier to be overridden for testing
func (r *NotaryRepository) InitializeWithPubKeyAndVerifier(rootPrivKeyID string, rootPubKey data.PublicKey, keyPairVerifier KeyPairVerifier, serverManagedRoles ...string) error {
rootPrivKey, _, err := r.CryptoService.GetPrivateKey(rootPrivKeyID)
if err != nil {
return err
}

// currently we only support server managing timestamps and snapshots, and
Expand Down Expand Up @@ -210,20 +231,24 @@ func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ..
}
}

rootKeys := []data.PublicKey{}
for _, privKey := range privKeys {
rootKey, err := rootCertKey(r.gun, privKey)
if rootPubKey == nil {
logrus.Debug("No root public key specified; generating a new one.")
rootPubKey, err = rootCertKey(r.gun, rootPrivKey)
if err != nil {
return err
}
rootKeys = append(rootKeys, rootKey)
} else {
logrus.Debugf("Verifying user-specified root public key: %s", rootPubKey.ID())
if err := keyPairVerifier(rootPrivKey, rootPubKey); err != nil {
return err
}
}

var (
rootRole = data.NewBaseRole(
data.CanonicalRootRole,
notary.MinThreshold,
rootKeys...,
rootPubKey,
)
timestampRole data.BaseRole
snapshotRole data.BaseRole
Expand Down Expand Up @@ -279,7 +304,7 @@ func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ..

r.tufRepo = tuf.NewRepo(r.CryptoService)

err := r.tufRepo.InitRoot(
err = r.tufRepo.InitRoot(
rootRole,
timestampRole,
snapshotRole,
Expand Down
89 changes: 56 additions & 33 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func initializeRepo(t *testing.T, rootType, gun, url string,

repo, rec, rootPubKeyID := createRepoAndKey(t, rootType, tempBaseDir, gun, url)

err = repo.Initialize([]string{rootPubKeyID}, serverManagedRoles...)
err = repo.Initialize(rootPubKeyID, serverManagedRoles...)
if err != nil {
os.RemoveAll(tempBaseDir)
}
Expand Down Expand Up @@ -241,7 +241,7 @@ func TestInitRepositoryManagedRolesIncludingRoot(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalRootRole)
err = repo.Initialize(rootPubKeyID, data.CanonicalRootRole)
require.Error(t, err)
require.IsType(t, ErrInvalidRemoteRole{}, err)
// Just testing the error message here in this one case
Expand All @@ -261,7 +261,7 @@ func TestInitRepositoryManagedRolesInvalidRole(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
err = repo.Initialize([]string{rootPubKeyID}, "randomrole")
err = repo.Initialize(rootPubKeyID, "randomrole")
require.Error(t, err)
require.IsType(t, ErrInvalidRemoteRole{}, err)
// no key creation happened
Expand All @@ -278,7 +278,7 @@ func TestInitRepositoryManagedRolesIncludingTargets(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTargetsRole)
err = repo.Initialize(rootPubKeyID, data.CanonicalTargetsRole)
require.Error(t, err)
require.IsType(t, ErrInvalidRemoteRole{}, err)
// no key creation happened
Expand All @@ -298,27 +298,7 @@ func TestInitRepositoryManagedRolesIncludingTimestamp(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTimestampRole)
require.NoError(t, err)
// generates the target role, the snapshot role
rec.requireCreated(t, []string{data.CanonicalTargetsRole, data.CanonicalSnapshotRole})
}

func TestInitRepositoryMultipleRootKeys(t *testing.T) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err, "failed to create a temporary directory")
defer os.RemoveAll(tempBaseDir)

ts, _, _ := simpleTestServer(t)
defer ts.Close()

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
rootPubKey2, err := repo.CryptoService.Create("root", repo.gun, data.ECDSAKey)
require.NoError(t, err, "error generating second root key: %s", err)

err = repo.Initialize([]string{rootPubKeyID, rootPubKey2.ID()}, data.CanonicalTimestampRole)
err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole)
require.NoError(t, err)
// generates the target role, the snapshot role
rec.requireCreated(t, []string{data.CanonicalTargetsRole, data.CanonicalSnapshotRole})
Expand All @@ -337,7 +317,7 @@ func TestInitRepositoryNeedsRemoteTimestampKey(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTimestampRole)
err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)

Expand All @@ -359,7 +339,7 @@ func TestInitRepositoryNeedsRemoteSnapshotKey(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalSnapshotRole)
err = repo.Initialize(rootPubKeyID, data.CanonicalSnapshotRole)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)

Expand Down Expand Up @@ -536,9 +516,9 @@ func testInitRepoSigningKeys(t *testing.T, rootType string, serverManagesSnapsho
repo, rec := newRepoToTestRepo(t, repo, false)

if serverManagesSnapshot {
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalSnapshotRole)
err = repo.Initialize(rootPubKeyID, data.CanonicalSnapshotRole)
} else {
err = repo.Initialize([]string{rootPubKeyID})
err = repo.Initialize(rootPubKeyID)
}

require.NoError(t, err, "error initializing repository")
Expand Down Expand Up @@ -583,7 +563,7 @@ func testInitRepoAttemptsExceeded(t *testing.T, rootType string) {
// private key unlocking we need a new repo instance.
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, trustpinning.TrustPinConfig{})
require.NoError(t, err, "error creating repo: %s", err)
err = repo.Initialize([]string{rootPubKey.ID()})
err = repo.Initialize(rootPubKey.ID())
require.EqualError(t, err, trustmanager.ErrAttemptsExceeded{}.Error())
}

Expand Down Expand Up @@ -620,7 +600,7 @@ func testInitRepoPasswordInvalid(t *testing.T, rootType string) {
// private key unlocking we need a new repo instance.
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, giveUpPassphraseRetriever, trustpinning.TrustPinConfig{})
require.NoError(t, err, "error creating repo: %s", err)
err = repo.Initialize([]string{rootPubKey.ID()})
err = repo.Initialize(rootPubKey.ID())
require.EqualError(t, err, trustmanager.ErrPasswordInvalid{}.Error())
}

Expand Down Expand Up @@ -1670,7 +1650,7 @@ func TestPublishUninitializedRepo(t *testing.T) {
rootPubKey, err := repo.CryptoService.Create("root", repo.gun, data.ECDSAKey)
require.NoError(t, err, "error generating root key: %s", err)

require.NoError(t, repo.Initialize([]string{rootPubKey.ID()}))
require.NoError(t, repo.Initialize(rootPubKey.ID()))

// now metadata is created
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
Expand Down Expand Up @@ -2031,12 +2011,55 @@ func TestPublishSnapshotLocalKeysCreatedFirst(t *testing.T) {

repo.CryptoService = cannotCreateKeys{CryptoService: cs}

err = repo.Initialize([]string{rootPubKey.ID()}, data.CanonicalSnapshotRole)
err = repo.Initialize(rootPubKey.ID(), data.CanonicalSnapshotRole)
require.Error(t, err)
require.Contains(t, err.Error(), "Oh no I cannot create keys")
require.False(t, requestMade)
}

// If there is an error creating the local keys, no call is made to get a
// remote key.
func TestInitializeWithPublicKeyVerifiesKeypairAndFailsIfKeysDontMatch(t *testing.T) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err, "failed to create a temporary directory")
defer os.RemoveAll(tempBaseDir)

repo, rec, privKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
require.NotNil(t, rec, "")
rootPubKey := repo.CryptoService.GetKey(privKeyID)
require.NotNil(t, rootPubKey, "Couldn't get the root public key")

keyPairVerifierFail := func(privKey data.PrivateKey, pubKey data.PublicKey) error {
return fmt.Errorf("Private key %s does not match public key %s", privKey.ID(), pubKey.ID())
}
err = repo.InitializeWithPubKeyAndVerifier(privKeyID, rootPubKey, keyPairVerifierFail, data.CanonicalSnapshotRole)
require.Error(t, err)
require.Equal(t, err.Error(), fmt.Sprintf("Private key %s does not match public key %s", privKeyID, rootPubKey.ID()))
}

func TestInitializeWithPublicKeyVerifiesKeypairAndSucceedsIfKeysMatch(t *testing.T) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err, "failed to create a temporary directory")
defer os.RemoveAll(tempBaseDir)

ts, _, _ := simpleTestServer(t)
defer ts.Close()

repo, rec, privKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
require.NotNil(t, rec, "")
rootPubKey := repo.CryptoService.GetKey(privKeyID)
require.NotNil(t, rootPubKey, "Couldn't get the root public key")

keyPairVerifierSucceed := func(privKey data.PrivateKey, pubKey data.PublicKey) error { return nil }
err = repo.InitializeWithPubKeyAndVerifier(privKeyID, rootPubKey, keyPairVerifierSucceed, data.CanonicalSnapshotRole)
require.NoError(t, err)
require.Equal(t, rootPubKey.Public(), repo.tufRepo.Root.Signed.Keys[rootPubKey.ID()].Public())
}

func createKey(t *testing.T, repo *NotaryRepository, role string, x509 bool) data.PublicKey {
key, err := repo.CryptoService.Create(role, repo.gun, data.ECDSAKey)
require.NoError(t, err, "error creating key")
Expand Down
40 changes: 0 additions & 40 deletions cmd/notary/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,46 +81,6 @@ func setupServer() *httptest.Server {
return httptest.NewServer(setupServerHandler(storage.NewMemStorage()))
}

// Initializes a repo with multiple existing keys
func TestInitWithMultipleRootKeys(t *testing.T) {
// -- setup --
setUp(t)

tempDir := tempDirWithConfig(t, "{}")
defer os.RemoveAll(tempDir)

server := setupServer()
defer server.Close()

tempFile, err := ioutil.TempFile("", "targetfile")
require.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())

// -- tests --

// generate root key produces a single root key and no other keys
_, err = runCommand(t, tempDir, "key", "generate", data.ECDSAKey)
require.NoError(t, err)
assertNumKeys(t, tempDir, 1, 0, true)
_, err = runCommand(t, tempDir, "key", "generate", data.ECDSAKey)
require.NoError(t, err)
rootIDs, _ := assertNumKeys(t, tempDir, 2, 0, true)
var rootFiles []string
for _, rID := range rootIDs {
rootFiles = append(rootFiles, filepath.Join(
tempDir, "private", "root_keys", rID+".key"))
}

// init repo
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun", "--rootkeys", strings.Join(rootFiles, ","))
require.NoError(t, err)

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

// Initializes a repo, adds a target, publishes the target, lists the target,
// verifies the target, and then removes the target.
func TestClientTUFInteraction(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/notary/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func setUpRepo(t *testing.T, tempBaseDir, gun string, ret notary.PassRetriever)
rootPubKey, err := repo.CryptoService.Create("root", "", data.ECDSAKey)
require.NoError(t, err, "error generating root key: %s", err)

err = repo.Initialize([]string{rootPubKey.ID()})
err = repo.Initialize(rootPubKey.ID())
require.NoError(t, err)

return ts, repo.CryptoService.ListAllKeys()
Expand Down
Loading

0 comments on commit f039a08

Please sign in to comment.