rework export key

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-02-02 14:21:19 -08:00
parent 3b3026c121
commit 12fd5aa246
3 changed files with 51 additions and 27 deletions

View File

@ -52,10 +52,10 @@ var cmdKeysBackupTemplate = usageTemplate{
Long: "Backs up all of your accessible of keys. The keys are reencrypted with a new passphrase. The output is a ZIP file. If the --gun option is passed, only signing keys and no root keys will be backed up. Does not work on keys that are only in hardware (e.g. Yubikeys).", Long: "Backs up all of your accessible of keys. The keys are reencrypted with a new passphrase. The output is a ZIP file. If the --gun option is passed, only signing keys and no root keys will be backed up. Does not work on keys that are only in hardware (e.g. Yubikeys).",
} }
var cmdKeyExportRootTemplate = usageTemplate{ var cmdKeyExportTemplate = usageTemplate{
Use: "export [ keyID ] [ pemfilename ]", Use: "export [ keyID ] [ pemfilename ]",
Short: "Export a root key on disk to a PEM file.", Short: "Export a private key on disk to a PEM file.",
Long: "Exports a single root key on disk, without reencrypting. The output is a PEM file. Does not work on keys that are only in hardware (e.g. Yubikeys).", Long: "Exports a single private key on disk, without reencrypting. The output is a PEM file. Does not work on keys that are only in hardware (e.g. Yubikeys).",
} }
var cmdKeysRestoreTemplate = usageTemplate{ var cmdKeysRestoreTemplate = usageTemplate{
@ -64,10 +64,10 @@ var cmdKeysRestoreTemplate = usageTemplate{
Long: "Restores one or more keys from a ZIP file. If hardware key storage (e.g. a Yubikey) is available, root keys will be imported into the hardware, but not backed up to disk in the same location as the other, non-root keys.", Long: "Restores one or more keys from a ZIP file. If hardware key storage (e.g. a Yubikey) is available, root keys will be imported into the hardware, but not backed up to disk in the same location as the other, non-root keys.",
} }
var cmdKeyImportRootTemplate = usageTemplate{ var cmdKeyImportTemplate = usageTemplate{
Use: "import [ pemfilename ]", Use: "import [ pemfilename ]",
Short: "Imports a root key from a PEM file.", Short: "Imports a key from a PEM file.",
Long: "Imports a single root key from a PEM file. If a hardware key storage (e.g. Yubikey) is available, the root key will be imported into the hardware but not backed up on disk again.", Long: "Imports a single key from a PEM file. If a hardware key storage (e.g. Yubikey) is available, the root key will be imported into the hardware but not backed up on disk again.",
} }
var cmdKeyRemoveTemplate = usageTemplate{ var cmdKeyRemoveTemplate = usageTemplate{
@ -88,7 +88,7 @@ type keyCommander struct {
getRetriever func() passphrase.Retriever getRetriever func() passphrase.Retriever
// these are for command line parsing - no need to set // these are for command line parsing - no need to set
keysExportRootChangePassphrase bool keysExportChangePassphrase bool
keysExportGUN string keysExportGUN string
rotateKeyRole string rotateKeyRole string
rotateKeyServerManaged bool rotateKeyServerManaged bool
@ -99,7 +99,8 @@ func (k *keyCommander) GetCommand() *cobra.Command {
cmd.AddCommand(cmdKeyListTemplate.ToCommand(k.keysList)) cmd.AddCommand(cmdKeyListTemplate.ToCommand(k.keysList))
cmd.AddCommand(cmdKeyGenerateRootKeyTemplate.ToCommand(k.keysGenerateRootKey)) cmd.AddCommand(cmdKeyGenerateRootKeyTemplate.ToCommand(k.keysGenerateRootKey))
cmd.AddCommand(cmdKeysRestoreTemplate.ToCommand(k.keysRestore)) cmd.AddCommand(cmdKeysRestoreTemplate.ToCommand(k.keysRestore))
cmd.AddCommand(cmdKeyImportRootTemplate.ToCommand(k.keysImportRoot)) cmd.AddCommand(cmdKeyImportTemplate.ToCommand(k.keysImportRoot))
cmd.AddCommand(cmdKeyRemoveTemplate.ToCommand(k.keyRemove)) cmd.AddCommand(cmdKeyRemoveTemplate.ToCommand(k.keyRemove))
cmd.AddCommand(cmdKeyPasswdTemplate.ToCommand(k.keyPassphraseChange)) cmd.AddCommand(cmdKeyPasswdTemplate.ToCommand(k.keyPassphraseChange))
@ -108,11 +109,11 @@ func (k *keyCommander) GetCommand() *cobra.Command {
&k.keysExportGUN, "gun", "g", "", "Globally Unique Name to export keys for") &k.keysExportGUN, "gun", "g", "", "Globally Unique Name to export keys for")
cmd.AddCommand(cmdKeysBackup) cmd.AddCommand(cmdKeysBackup)
cmdKeyExportRoot := cmdKeyExportRootTemplate.ToCommand(k.keysExportRoot) cmdKeyExport := cmdKeyExportTemplate.ToCommand(k.keysExport)
cmdKeyExportRoot.Flags().BoolVarP( cmdKeyExport.Flags().BoolVarP(
&k.keysExportRootChangePassphrase, "change-passphrase", "p", false, &k.keysExportChangePassphrase, "change-passphrase", "p", false,
"Set a new passphrase for the key being exported") "Set a new passphrase for the key being exported")
cmd.AddCommand(cmdKeyExportRoot) cmd.AddCommand(cmdKeyExport)
cmdRotateKey := cmdRotateKeyTemplate.ToCommand(k.keysRotate) cmdRotateKey := cmdRotateKeyTemplate.ToCommand(k.keysRotate)
cmdRotateKey.Flags().BoolVarP(&k.rotateKeyServerManaged, "server-managed", "r", cmdRotateKey.Flags().BoolVarP(&k.rotateKeyServerManaged, "server-managed", "r",
@ -236,8 +237,8 @@ func (k *keyCommander) keysBackup(cmd *cobra.Command, args []string) error {
return nil return nil
} }
// keysExportRoot exports a root key by ID to a PEM file // keysExport exports a key by ID to a PEM file
func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error { func (k *keyCommander) keysExport(cmd *cobra.Command, args []string) error {
if len(args) < 2 { if len(args) < 2 {
cmd.Usage() cmd.Usage()
return fmt.Errorf("Must specify key ID and output filename for export") return fmt.Errorf("Must specify key ID and output filename for export")
@ -247,7 +248,7 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
exportFilename := args[1] exportFilename := args[1]
if len(keyID) != notary.Sha256HexSize { if len(keyID) != notary.Sha256HexSize {
return fmt.Errorf("Please specify a valid root key ID") return fmt.Errorf("Please specify a valid key ID")
} }
config, err := k.configGetter() config, err := k.configGetter()
@ -258,24 +259,43 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
cs := cryptoservice.NewCryptoService("", ks...)
// Search for this key, determine whether this key has a GUN
keyGun := ""
keyRole := ""
for _, store := range ks {
for keypath, role := range store.ListKeys() {
if filepath.Base(keypath) == keyID {
keyRole = role
if role != data.CanonicalRootRole {
dirPath := filepath.Dir(keypath)
if dirPath != "." { // no gun
keyGun = dirPath
}
}
break
}
}
}
cs := cryptoservice.NewCryptoService(keyGun, ks...)
exportFile, err := os.Create(exportFilename) exportFile, err := os.Create(exportFilename)
if err != nil { if err != nil {
return fmt.Errorf("Error creating output file: %v", err) return fmt.Errorf("Error creating output file: %v", err)
} }
if k.keysExportRootChangePassphrase { if k.keysExportChangePassphrase {
// Must use a different passphrase retriever to avoid caching the // Must use a different passphrase retriever to avoid caching the
// unlocking passphrase and reusing that. // unlocking passphrase and reusing that.
exportRetriever := k.getRetriever() exportRetriever := k.getRetriever()
err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever) err = cs.ExportKeyReencrypt(exportFile, keyID, exportRetriever)
} else { } else {
err = cs.ExportRootKey(exportFile, keyID) err = cs.ExportKey(exportFile, keyID, keyRole)
} }
exportFile.Close() exportFile.Close()
if err != nil { if err != nil {
os.Remove(exportFilename) os.Remove(exportFilename)
return fmt.Errorf("Error exporting root key: %v", err) return fmt.Errorf("Error exporting %s key: %v", keyRole, err)
} }
return nil return nil
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/docker/notary/passphrase" "github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
) )
const zipMadeByUNIX = 3 << 8 const zipMadeByUNIX = 3 << 8
@ -31,15 +32,18 @@ var (
ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN") ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
) )
// ExportRootKey exports the specified root key to an io.Writer in PEM format. // ExportKey exports the specified key to an io.Writer in PEM format.
// The key's existing encryption is preserved. // The key's existing encryption is preserved.
func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error { func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
var ( var (
pemBytes []byte pemBytes []byte
err error err error
) )
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
if role != data.CanonicalRootRole {
keyID = filepath.Join(cs.gun, keyID)
}
pemBytes, err = ks.ExportKey(keyID) pemBytes, err = ks.ExportKey(keyID)
if err != nil { if err != nil {
continue continue
@ -59,9 +63,9 @@ func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
return nil return nil
} }
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in // ExportRootKeyReencrypt exports the specified private key to an io.Writer in
// PEM format. The key is reencrypted with a new passphrase. // PEM format. The key is reencrypted with a new passphrase.
func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
privateKey, role, err := cs.GetPrivateKey(keyID) privateKey, role, err := cs.GetPrivateKey(keyID)
if err != nil { if err != nil {
return err return err
@ -110,7 +114,7 @@ func (cs *CryptoService) ImportRootKey(source io.Reader) error {
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
// don't redeclare err, we want the value carried out of the loop // don't redeclare err, we want the value carried out of the loop
if err = ks.ImportKey(pemBytes, "root"); err == nil { if err = ks.ImportKey(pemBytes, data.CanonicalRootRole); err == nil {
return nil //bail on the first keystore we import to return nil //bail on the first keystore we import to
} }
} }

View File

@ -284,7 +284,7 @@ func TestImportExportRootKey(t *testing.T) {
tempKeyFilePath := tempKeyFile.Name() tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath) defer os.Remove(tempKeyFilePath)
err = cs.ExportRootKey(tempKeyFile, rootKeyID) err = cs.ExportKey(tempKeyFile, rootKeyID, data.CanonicalRootRole)
assert.NoError(t, err) assert.NoError(t, err)
tempKeyFile.Close() tempKeyFile.Close()
@ -352,7 +352,7 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
tempKeyFilePath := tempKeyFile.Name() tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath) defer os.Remove(tempKeyFilePath)
err = cs.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever) err = cs.ExportKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
assert.NoError(t, err) assert.NoError(t, err)
tempKeyFile.Close() tempKeyFile.Close()