Implement private keys export/import symmetric functionalities (#4436)
Add Keybase's ExportPrivKey()/ImportPrivKey() API calls to export/import ASCII-armored private keys. Relevant keys subcommands are provided as well. Closes: #2020
This commit is contained in:
parent
1db10b0033
commit
85ebf5f72e
|
@ -0,0 +1,2 @@
|
||||||
|
#2020 New keys export/import command line utilities to export/import private keys in ASCII format
|
||||||
|
that rely on Keybase's new underlying ExportPrivKey()/ImportPrivKey() API calls.
|
|
@ -0,0 +1,45 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
func exportKeyCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "export <name>",
|
||||||
|
Short: "Export private keys",
|
||||||
|
Long: `Export a private key from the local keybase in ASCII-armored encrypted format.`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: runExportCmd,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runExportCmd(_ *cobra.Command, args []string) error {
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := input.BufferStdin()
|
||||||
|
decryptPassword, err := input.GetPassword("Enter passphrase to decrypt your key:", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
encryptPassword, err := input.GetPassword("Enter passphrase to encrypt the exported key:", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
armored, err := kb.ExportPrivKey(args[0], decryptPassword, encryptPassword)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(armored)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/input"
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runExportCmd(t *testing.T) {
|
||||||
|
exportKeyCommand := exportKeyCommand()
|
||||||
|
|
||||||
|
// Now add a temporary keybase
|
||||||
|
kbHome, cleanUp := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp()
|
||||||
|
viper.Set(flags.FlagHome, kbHome)
|
||||||
|
|
||||||
|
// create a key
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = kb.CreateAccount("keyname1", tests.TestMnemonic, "", "123456789", 0, 0)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Now enter password
|
||||||
|
cleanUp1 := input.OverrideStdin(bufio.NewReader(strings.NewReader("123456789\n123456789\n")))
|
||||||
|
defer cleanUp1()
|
||||||
|
assert.NoError(t, runExportCmd(exportKeyCommand, []string{"keyname1"}))
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/input"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func importKeyCommand() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "import <name> <keyfile>",
|
||||||
|
Short: "Import private keys into the local keybase",
|
||||||
|
Long: "Import a ASCII armored private key into the local keybase.",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: runImportCmd,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runImportCmd(_ *cobra.Command, args []string) error {
|
||||||
|
kb, err := NewKeyBaseFromHomeFlag()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bz, err := ioutil.ReadFile(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := input.BufferStdin()
|
||||||
|
passphrase, err := input.GetPassword("Enter passphrase to decrypt your key:", buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return kb.ImportPrivKey(args[0], string(bz), passphrase)
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package keys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/input"
|
||||||
|
"github.com/cosmos/cosmos-sdk/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_runImportCmd(t *testing.T) {
|
||||||
|
importKeyCommand := importKeyCommand()
|
||||||
|
|
||||||
|
// Now add a temporary keybase
|
||||||
|
kbHome, cleanUp := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanUp()
|
||||||
|
viper.Set(flags.FlagHome, kbHome)
|
||||||
|
|
||||||
|
keyfile := filepath.Join(kbHome, "key.asc")
|
||||||
|
armoredKey := `-----BEGIN TENDERMINT PRIVATE KEY-----
|
||||||
|
salt: A790BB721D1C094260EA84F5E5B72289
|
||||||
|
kdf: bcrypt
|
||||||
|
|
||||||
|
HbP+c6JmeJy9JXe2rbbF1QtCX1gLqGcDQPBXiCtFvP7/8wTZtVOPj8vREzhZ9ElO
|
||||||
|
3P7YnrzPQThG0Q+ZnRSbl9MAS8uFAM4mqm5r/Ys=
|
||||||
|
=f3l4
|
||||||
|
-----END TENDERMINT PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
require.NoError(t, ioutil.WriteFile(keyfile, []byte(armoredKey), 0644))
|
||||||
|
|
||||||
|
// Now enter password
|
||||||
|
cleanUp1 := input.OverrideStdin(bufio.NewReader(strings.NewReader("123456789\n")))
|
||||||
|
defer cleanUp1()
|
||||||
|
assert.NoError(t, runImportCmd(importKeyCommand, []string{"keyname1", keyfile}))
|
||||||
|
}
|
|
@ -21,6 +21,8 @@ func Commands() *cobra.Command {
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
mnemonicKeyCommand(),
|
mnemonicKeyCommand(),
|
||||||
addKeyCommand(),
|
addKeyCommand(),
|
||||||
|
exportKeyCommand(),
|
||||||
|
importKeyCommand(),
|
||||||
listKeysCmd(),
|
listKeysCmd(),
|
||||||
showKeysCmd(),
|
showKeysCmd(),
|
||||||
flags.LineBreak,
|
flags.LineBreak,
|
||||||
|
|
|
@ -11,5 +11,5 @@ func TestCommands(t *testing.T) {
|
||||||
assert.NotNil(t, rootCommands)
|
assert.NotNil(t, rootCommands)
|
||||||
|
|
||||||
// Commands are registered
|
// Commands are registered
|
||||||
assert.Equal(t, 8, len(rootCommands.Commands()))
|
assert.Equal(t, 10, len(rootCommands.Commands()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,6 +339,35 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||||
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExportPrivKey returns a private key in ASCII armored format.
|
||||||
|
// It returns an error if the key does not exist or a wrong encryption passphrase is supplied.
|
||||||
|
func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||||
|
encryptPassphrase string) (armor string, err error) {
|
||||||
|
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPrivKey imports a private key in ASCII armor format.
|
||||||
|
// It returns an error if a key with the same name exists or a wrong encryption passphrase is
|
||||||
|
// supplied.
|
||||||
|
func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
|
||||||
|
if _, err := kb.Get(name); err == nil {
|
||||||
|
return errors.New("Cannot overwrite key " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "couldn't import private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
kb.writeLocalKey(name, privKey, passphrase)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||||
bz := kb.db.Get(infoKey(name))
|
bz := kb.db.Get(infoKey(name))
|
||||||
if len(bz) > 0 {
|
if len(bz) > 0 {
|
||||||
|
|
|
@ -156,6 +156,16 @@ func (lkb lazyKeybase) Import(name string, armor string) (err error) {
|
||||||
return newDbKeybase(db).Import(name, armor)
|
return newDbKeybase(db).Import(name, armor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error {
|
||||||
|
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
return newDbKeybase(db).ImportPrivKey(name, armor, passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
|
func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -196,4 +206,16 @@ func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (c
|
||||||
return newDbKeybase(db).ExportPrivateKeyObject(name, passphrase)
|
return newDbKeybase(db).ExportPrivateKeyObject(name, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string,
|
||||||
|
encryptPassphrase string) (armor string, err error) {
|
||||||
|
|
||||||
|
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
return newDbKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
func (lkb lazyKeybase) CloseDB() {}
|
func (lkb lazyKeybase) CloseDB() {}
|
||||||
|
|
|
@ -209,6 +209,35 @@ func TestLazyExportImport(t *testing.T) {
|
||||||
require.Equal(t, john, john2)
|
require.Equal(t, john, john2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLazyExportImportPrivKey(t *testing.T) {
|
||||||
|
dir, cleanup := tests.NewTestCaseDir(t)
|
||||||
|
defer cleanup()
|
||||||
|
kb := New("keybasename", dir)
|
||||||
|
|
||||||
|
info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, info.GetName(), "john")
|
||||||
|
priv1, err := kb.Get("john")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// decrypt local private key, and produce encrypted ASCII armored output
|
||||||
|
armored, err := kb.ExportPrivKey("john", "secretcpw", "new_secretcpw")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// delete exported key
|
||||||
|
require.NoError(t, kb.Delete("john", "", true))
|
||||||
|
_, err = kb.Get("john")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// import armored key
|
||||||
|
require.NoError(t, kb.ImportPrivKey("john", armored, "new_secretcpw"))
|
||||||
|
|
||||||
|
// ensure old and new keys match
|
||||||
|
priv2, err := kb.Get("john")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, priv1.GetPubKey().Equals(priv2.GetPubKey()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestLazyExportImportPubKey(t *testing.T) {
|
func TestLazyExportImportPubKey(t *testing.T) {
|
||||||
dir, cleanup := tests.NewTestCaseDir(t)
|
dir, cleanup := tests.NewTestCaseDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
|
@ -46,9 +46,11 @@ type Keybase interface {
|
||||||
// The following operations will *only* work on locally-stored keys
|
// The following operations will *only* work on locally-stored keys
|
||||||
Update(name, oldpass string, getNewpass func() (string, error)) error
|
Update(name, oldpass string, getNewpass func() (string, error)) error
|
||||||
Import(name string, armor string) (err error)
|
Import(name string, armor string) (err error)
|
||||||
|
ImportPrivKey(name, armor, passphrase string) error
|
||||||
ImportPubKey(name string, armor string) (err error)
|
ImportPubKey(name string, armor string) (err error)
|
||||||
Export(name string) (armor string, err error)
|
Export(name string) (armor string, err error)
|
||||||
ExportPubKey(name string) (armor string, err error)
|
ExportPubKey(name string) (armor string, err error)
|
||||||
|
ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error)
|
||||||
|
|
||||||
// ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
|
// ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
|
||||||
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
|
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
|
||||||
|
|
Loading…
Reference in New Issue