Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement private keys export/import symmetric functionalities #4436

Merged
merged 10 commits into from
May 30, 2019
Merged
2 changes: 2 additions & 0 deletions .pending/features/sdk/2020-New-keys-export
Original file line number Diff line number Diff line change
@@ -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 changes: 45 additions & 0 deletions client/keys/export.go
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed earlier, maybe we should open another issue for this, however, I think it would be good to improve input.GetPassword to ensure that something like 12345678 is not accepted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zmanian ping

if err != nil {
return err
}

armored, err := kb.ExportPrivKey(args[0], decryptPassword, encryptPassword)
if err != nil {
return err
}

fmt.Println(armored)
return nil
}
34 changes: 34 additions & 0 deletions client/keys/export_test.go
Original file line number Diff line number Diff line change
@@ -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"}))
}
jleni marked this conversation as resolved.
Show resolved Hide resolved
39 changes: 39 additions & 0 deletions client/keys/import.go
Original file line number Diff line number Diff line change
@@ -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)
}
43 changes: 43 additions & 0 deletions client/keys/import_test.go
Original file line number Diff line number Diff line change
@@ -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}))
}
2 changes: 2 additions & 0 deletions client/keys/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ func Commands() *cobra.Command {
cmd.AddCommand(
mnemonicKeyCommand(),
addKeyCommand(),
exportKeyCommand(),
importKeyCommand(),
listKeysCmd(),
showKeysCmd(),
flags.LineBreak,
Expand Down
2 changes: 1 addition & 1 deletion client/keys/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
}
29 changes: 29 additions & 0 deletions crypto/keys/keybase.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,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 {
Expand Down
22 changes: 22 additions & 0 deletions crypto/keys/lazy_keybase.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {}
29 changes: 29 additions & 0 deletions crypto/keys/lazy_keybase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions crypto/keys/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 string, armor string, passphrase string) error
alessio marked this conversation as resolved.
Show resolved Hide resolved
ImportPubKey(name string, armor string) (err error)
Export(name string) (armor string, err error)
ExportPubKey(name string) (armor string, err error)
ExportPrivKey(name string, decryptPassphrase string, encryptPassphrase string) (armor string, err error)
alessio marked this conversation as resolved.
Show resolved Hide resolved

// ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API
ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error)
Expand Down