diff --git a/client/client.go b/client/client.go index eababee600..6887631159 100644 --- a/client/client.go +++ b/client/client.go @@ -173,10 +173,14 @@ func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) { // 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(rootKeyID string, serverManagedRoles ...string) error { - privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID) - if err != nil { - return err +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) } // currently we only support server managing timestamps and snapshots, and @@ -206,16 +210,20 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st } } - rootKey, err := rootCertKey(r.gun, privKey) - if err != nil { - return err + rootKeys := []data.PublicKey{} + for _, privKey := range privKeys { + rootKey, err := rootCertKey(r.gun, privKey) + if err != nil { + return err + } + rootKeys = append(rootKeys, rootKey) } var ( rootRole = data.NewBaseRole( data.CanonicalRootRole, notary.MinThreshold, - rootKey, + rootKeys..., ) timestampRole data.BaseRole snapshotRole data.BaseRole @@ -271,7 +279,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st r.tufRepo = tuf.NewRepo(r.CryptoService) - err = r.tufRepo.InitRoot( + err := r.tufRepo.InitRoot( rootRole, timestampRole, snapshotRole, diff --git a/client/client_test.go b/client/client_test.go index 6daffaafa2..051201b003 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -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(rootPubKeyID, serverManagedRoles...) + err = repo.Initialize([]string{rootPubKeyID}, serverManagedRoles...) if err != nil { os.RemoveAll(tempBaseDir) } @@ -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(rootPubKeyID, data.CanonicalRootRole) + err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalRootRole) require.Error(t, err) require.IsType(t, ErrInvalidRemoteRole{}, err) // Just testing the error message here in this one case @@ -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(rootPubKeyID, "randomrole") + err = repo.Initialize([]string{rootPubKeyID}, "randomrole") require.Error(t, err) require.IsType(t, ErrInvalidRemoteRole{}, err) // no key creation happened @@ -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(rootPubKeyID, data.CanonicalTargetsRole) + err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTargetsRole) require.Error(t, err) require.IsType(t, ErrInvalidRemoteRole{}, err) // no key creation happened @@ -298,7 +298,27 @@ func TestInitRepositoryManagedRolesIncludingTimestamp(t *testing.T) { repo, rec, rootPubKeyID := createRepoAndKey( t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL) - err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole) + 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) require.NoError(t, err) // generates the target role, the snapshot role rec.requireCreated(t, []string{data.CanonicalTargetsRole, data.CanonicalSnapshotRole}) @@ -317,7 +337,7 @@ func TestInitRepositoryNeedsRemoteTimestampKey(t *testing.T) { repo, rec, rootPubKeyID := createRepoAndKey( t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL) - err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole) + err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTimestampRole) require.Error(t, err) require.IsType(t, store.ErrMetaNotFound{}, err) @@ -339,7 +359,7 @@ func TestInitRepositoryNeedsRemoteSnapshotKey(t *testing.T) { repo, rec, rootPubKeyID := createRepoAndKey( t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL) - err = repo.Initialize(rootPubKeyID, data.CanonicalSnapshotRole) + err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalSnapshotRole) require.Error(t, err) require.IsType(t, store.ErrMetaNotFound{}, err) @@ -516,9 +536,9 @@ func testInitRepoSigningKeys(t *testing.T, rootType string, serverManagesSnapsho repo, rec := newRepoToTestRepo(t, repo, false) if serverManagesSnapshot { - err = repo.Initialize(rootPubKeyID, data.CanonicalSnapshotRole) + err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalSnapshotRole) } else { - err = repo.Initialize(rootPubKeyID) + err = repo.Initialize([]string{rootPubKeyID}) } require.NoError(t, err, "error initializing repository") @@ -563,7 +583,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(rootPubKey.ID()) + err = repo.Initialize([]string{rootPubKey.ID()}) require.EqualError(t, err, trustmanager.ErrAttemptsExceeded{}.Error()) } @@ -600,7 +620,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(rootPubKey.ID()) + err = repo.Initialize([]string{rootPubKey.ID()}) require.EqualError(t, err, trustmanager.ErrPasswordInvalid{}.Error()) } @@ -1650,7 +1670,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(rootPubKey.ID())) + require.NoError(t, repo.Initialize([]string{rootPubKey.ID()})) // now metadata is created requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true) @@ -2011,7 +2031,7 @@ func TestPublishSnapshotLocalKeysCreatedFirst(t *testing.T) { repo.CryptoService = cannotCreateKeys{CryptoService: cs} - err = repo.Initialize(rootPubKey.ID(), data.CanonicalSnapshotRole) + err = repo.Initialize([]string{rootPubKey.ID()}, data.CanonicalSnapshotRole) require.Error(t, err) require.Contains(t, err.Error(), "Oh no I cannot create keys") require.False(t, requestMade) diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go index 68271cc7b6..a06181e523 100644 --- a/cmd/notary/integration_test.go +++ b/cmd/notary/integration_test.go @@ -81,6 +81,43 @@ func setupServer() *httptest.Server { return httptest.NewServer(setupServerHandler(storage.NewMemStorage())) } +// Initializes a repo with existing key +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) + rootIDs, _ := assertNumKeys(t, tempDir, 1, 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", "--rootkey", rootFiles[0]) + 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) { diff --git a/cmd/notary/keys_test.go b/cmd/notary/keys_test.go index a232326e36..bc4eae7a09 100644 --- a/cmd/notary/keys_test.go +++ b/cmd/notary/keys_test.go @@ -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(rootPubKey.ID()) + err = repo.Initialize([]string{rootPubKey.ID()}) require.NoError(t, err) return ts, repo.CryptoService.ListAllKeys() diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index dc7cedbe1f..a38eb52506 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/hex" "fmt" + "io/ioutil" "net" "net/http" "net/url" @@ -19,6 +20,8 @@ import ( "github.com/docker/go-connections/tlsconfig" "github.com/docker/notary" notaryclient "github.com/docker/notary/client" + "github.com/docker/notary/cryptoservice" + "github.com/docker/notary/trustmanager" "github.com/docker/notary/trustpinning" "github.com/docker/notary/tuf/data" "github.com/docker/notary/utils" @@ -86,9 +89,10 @@ type tufCommander struct { retriever notary.PassRetriever // these are for command line parsing - no need to set - roles []string - sha256 string - sha512 string + roles []string + sha256 string + sha512 string + rootKey string input string output string @@ -96,7 +100,10 @@ type tufCommander struct { } func (t *tufCommander) AddToCommand(cmd *cobra.Command) { - cmd.AddCommand(cmdTUFInitTemplate.ToCommand(t.tufInit)) + cmdTUFInit := cmdTUFInitTemplate.ToCommand(t.tufInit) + cmdTUFInit.Flags().StringVar(&t.rootKey, "rootkey", "", "Root key to initialize the repository with") + cmd.AddCommand(cmdTUFInit) + cmd.AddCommand(cmdTUFStatusTemplate.ToCommand(t.tufStatus)) cmd.AddCommand(cmdTUFPublishTemplate.ToCommand(t.tufPublish)) cmd.AddCommand(cmdTUFLookupTemplate.ToCommand(t.tufLookup)) @@ -268,7 +275,38 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error { return err } - rootKeyList := nRepo.CryptoService.ListKeys(data.CanonicalRootRole) + var rootKeyList []string + + if t.rootKey != "" { + keyFile, err := os.Open(t.rootKey) + if err != nil { + return fmt.Errorf("Opening file for import: %v", err) + } + defer keyFile.Close() + + pemBytes, err := ioutil.ReadAll(keyFile) + if err != nil { + return fmt.Errorf("Error reading input file: %v", err) + } + if err = cryptoservice.CheckRootKeyIsEncrypted(pemBytes); err != nil { + return err + } + + privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") + if err != nil { + privKey, _, err = trustmanager.GetPasswdDecryptBytes(t.retriever, pemBytes, "", "imported root") + if err != nil { + return err + } + } + err = nRepo.CryptoService.AddKey(data.CanonicalRootRole, "", privKey) + if err != nil { + return fmt.Errorf("Error importing key: %v", err) + } + rootKeyList = []string{nRepo.CryptoService.GetKey(privKey.ID()).ID()} + } else { + rootKeyList = nRepo.CryptoService.ListKeys(data.CanonicalRootRole) + } var rootKeyID string if len(rootKeyList) < 1 { @@ -285,7 +323,7 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error { cmd.Printf("Root key found, using: %s\n", rootKeyID) } - if err = nRepo.Initialize(rootKeyID); err != nil { + if err = nRepo.Initialize([]string{rootKeyID}); err != nil { return err } return nil