Add a flag to change the password of the root key when exporting it

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2015-07-28 15:08:41 -07:00
parent 3af03daa42
commit c3cf6c4083
3 changed files with 124 additions and 5 deletions

View File

@ -22,9 +22,10 @@ func init() {
cmdKey.AddCommand(cmdKeyRemoveRootKey)
cmdKey.AddCommand(cmdKeyGenerateRootKey)
cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for. A new password will be set for all the keys. Output format is a zip archive.")
cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for.")
cmdKey.AddCommand(cmdKeyExport)
cmdKey.AddCommand(cmdKeyExportRoot)
cmdKeyExportRoot.Flags().BoolVarP(&keysExportRootChangePassphrase, "change-passphrase", "c", false, "set a new passphrase for the key being exported")
cmdKey.AddCommand(cmdKeyImport)
cmdKey.AddCommand(cmdKeyImportRoot)
}
@ -65,6 +66,8 @@ var cmdKeyExport = &cobra.Command{
Run: keysExport,
}
var keysExportRootChangePassphrase bool
var cmdKeyExportRoot = &cobra.Command{
Use: "export-root [ keyID ] [ filename ]",
Short: "Exports given root key to a file.",
@ -249,7 +252,14 @@ func keysExportRoot(cmd *cobra.Command, args []string) {
if err != nil {
fatalf("error creating output file: %v", err)
}
err = keyStoreManager.ExportRootKey(exportFile, keyID)
if keysExportRootChangePassphrase {
// Must use a different passphrase retriever to avoid caching the
// unlocking passphrase and reusing that.
exportRetriever := passphrase.PromptRetriever()
err = keyStoreManager.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
} else {
err = keyStoreManager.ExportRootKey(exportFile, keyID)
}
exportFile.Close()
if err != nil {
fatalf("error exporting root key: %v", err)

View File

@ -42,6 +42,39 @@ func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error {
return err
}
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
// PEM format. The key is reencrypted with a new passphrase.
func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
privateKey, alias, err := km.rootKeyStore.GetKey(keyID)
if err != nil {
return err
}
// Create temporary keystore to use as a staging area
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir)
privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir)
tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir)
tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever)
if err != nil {
return err
}
err = tempRootKeyStore.AddKey(keyID, alias, privateKey)
if err != nil {
return err
}
pemBytes, err := tempRootKeyStore.Get(keyID + "_" + alias)
if err != nil {
return err
}
_, err = dest.Write(pemBytes)
return err
}
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
// internal assumptions that depend on this.
func checkRootKeyIsEncrypted(pemBytes []byte) error {
@ -81,12 +114,12 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error {
func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error {
// List all files but no symlinks
for _, f := range oldKeyStore.ListKeys() {
pemBytes, alias, err := oldKeyStore.GetKey(f)
privateKey, alias, err := oldKeyStore.GetKey(f)
if err != nil {
return err
}
err = newKeyStore.AddKey(f, alias, pemBytes)
err = newKeyStore.AddKey(f, alias, privateKey)
if err != nil {
return err

View File

@ -350,7 +350,6 @@ func TestImportExportRootKey(t *testing.T) {
err = repo2.Initialize(rootCryptoService2)
assert.NoError(t, err, "error creating repository: %s", err)
// Check the contents of the zip file
keyReader, err := os.Open(tempKeyFilePath)
assert.NoError(t, err, "could not open key file")
@ -380,4 +379,81 @@ func TestImportExportRootKey(t *testing.T) {
// Try to import garbage and make sure it doesn't succeed
err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM"), rootKeyID)
assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error())
// Should be able to unlock the root key with the old password
key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID)
assert.NoError(t, err, "could not unlock root key")
assert.Equal(t, "root", alias)
assert.Equal(t, rootKeyID, key.ID())
}
func TestImportExportRootKeyReencrypt(t *testing.T) {
gun := "docker.com/notary"
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
ts, _ := createTestServer(t)
defer ts.Close()
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
assert.NoError(t, err, "error creating repo: %s", err)
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String())
assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID)
assert.NoError(t, err, "error retrieving root key: %s", err)
err = repo.Initialize(rootCryptoService)
assert.NoError(t, err, "error creating repository: %s", err)
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath)
err = repo.KeyStoreManager.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
assert.NoError(t, err)
tempKeyFile.Close()
// Create new repo to test import
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir2)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever)
assert.NoError(t, err, "error creating repo: %s", err)
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String())
assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2)
assert.NoError(t, err, "error retrieving root key: %s", err)
err = repo2.Initialize(rootCryptoService2)
assert.NoError(t, err, "error creating repository: %s", err)
keyReader, err := os.Open(tempKeyFilePath)
assert.NoError(t, err, "could not open key file")
err = repo2.KeyStoreManager.ImportRootKey(keyReader, rootKeyID)
assert.NoError(t, err)
keyReader.Close()
// Look for repo's root key in repo2
// There should be a file named after the key ID of the root key we
// imported.
rootKeyFilename := rootKeyID + "_root.key"
_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename))
assert.NoError(t, err, "missing root key")
// Should be able to unlock the root key with the new password
key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID)
assert.NoError(t, err, "could not unlock root key")
assert.Equal(t, "root", alias)
assert.Equal(t, rootKeyID, key.ID())
}