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:
Alessio Treglia 2019-05-30 16:44:28 +01:00 committed by GitHub
parent 1db10b0033
commit 85ebf5f72e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 1 deletions

View File

@ -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.

45
client/keys/export.go Normal file
View File

@ -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
}

View File

@ -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"}))
}

39
client/keys/import.go Normal file
View File

@ -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)
}

View File

@ -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}))
}

View File

@ -21,6 +21,8 @@ func Commands() *cobra.Command {
cmd.AddCommand(
mnemonicKeyCommand(),
addKeyCommand(),
exportKeyCommand(),
importKeyCommand(),
listKeysCmd(),
showKeysCmd(),
flags.LineBreak,

View File

@ -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()))
}

View File

@ -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 {

View File

@ -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() {}

View File

@ -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()

View File

@ -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)