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(
|
||||
mnemonicKeyCommand(),
|
||||
addKeyCommand(),
|
||||
exportKeyCommand(),
|
||||
importKeyCommand(),
|
||||
listKeysCmd(),
|
||||
showKeysCmd(),
|
||||
flags.LineBreak,
|
||||
|
|
|
@ -11,5 +11,5 @@ func TestCommands(t *testing.T) {
|
|||
assert.NotNil(t, rootCommands)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
|
|
|
@ -156,6 +156,16 @@ func (lkb lazyKeybase) Import(name string, armor string) (err error) {
|
|||
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) {
|
||||
db, err := sdk.NewLevelDB(lkb.name, lkb.dir)
|
||||
if err != nil {
|
||||
|
@ -196,4 +206,16 @@ func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (c
|
|||
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() {}
|
||||
|
|
|
@ -209,6 +209,35 @@ func TestLazyExportImport(t *testing.T) {
|
|||
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) {
|
||||
dir, cleanup := tests.NewTestCaseDir(t)
|
||||
defer cleanup()
|
||||
|
|
|
@ -46,9 +46,11 @@ type Keybase interface {
|
|||
// The following operations will *only* work on locally-stored keys
|
||||
Update(name, oldpass string, getNewpass func() (string, error)) error
|
||||
Import(name string, armor string) (err error)
|
||||
ImportPrivKey(name, armor, passphrase string) error
|
||||
ImportPubKey(name string, armor string) (err error)
|
||||
Export(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(name string, passphrase string) (crypto.PrivKey, error)
|
||||
|
|
Loading…
Reference in New Issue