Skip to content

Commit

Permalink
Merge PR #5097: Add keys migrate command
Browse files Browse the repository at this point in the history
Add new command to assist users migrate their keys from the legacy
on-disk keybase to the new OS keyring-based implementation.

Ref #4754
  • Loading branch information
Alessio Treglia authored and alexanderbez committed Sep 30, 2019
1 parent d010b68 commit 3e6562c
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 39 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ the following [issue](https://github.com/keybase/go-keychain/issues/47) with the
you encounter this issue, you must upgrade your xcode command line tools to version >= `10.2`. You can
upgrade via: `sudo rm -rf /Library/Developer/CommandLineTools; xcode-select --install`. Verify the
correct version via: `pkgutil --pkg-info=com.apple.pkg.CLTools_Executables`.
* (keys) [\#5097](https://github.com/cosmos/cosmos-sdk/pull/5097) New `keys migrate` command to assist users migrate their keys
to the new keyring.


### Improvements

Expand Down
91 changes: 91 additions & 0 deletions client/keys/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package keys

import (
"bufio"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/types"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// migratePassphrase is used as a no-op migration key passphrase as a passphrase
// is not needed for importing into the Keyring keystore.
const migratePassphrase = "NOOP_PASSPHRASE"

func migrateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "migrate",
Short: "Migrate key information from the lagacy key database to the OS secret store, or encrypted file store as a fall-back and save it",
Long: `Migrate keys from the legacy on-disk secret store to the OS keyring.
The command asks for every passphrase. If the passphrase is incorrect, it skips the respective key.
`,
Args: cobra.ExactArgs(0),
RunE: runMigrateCmd,
}

cmd.Flags().Bool(flags.FlagDryRun, false, "Do everything which is supposed to be done, but don't write any changes to the keyring.")
return cmd
}

func runMigrateCmd(cmd *cobra.Command, args []string) error {
// instantiate legacy keybase
rootDir := viper.GetString(flags.FlagHome)
legacykb, err := NewKeyBaseFromDir(rootDir)
if err != nil {
return err
}

// fetch list of keys from legacy keybase
oldKeys, err := legacykb.List()
if err != nil {
return err
}

// instantiate keyring
var keyring keys.Keybase
buf := bufio.NewReader(cmd.InOrStdin())
if viper.GetBool(flags.FlagDryRun) {
keyring = keys.NewTestKeyring(types.GetConfig().GetKeyringServiceName(), rootDir)
} else {
keyring = keys.NewKeyring(types.GetConfig().GetKeyringServiceName(), rootDir, buf)
}

for _, key := range oldKeys {
legKeyInfo, err := legacykb.Export(key.GetName())
if err != nil {
return err
}

keyName := key.GetName()
keyType := key.GetType()
cmd.PrintErrf("Migrating %s (%s) ...\n", key.GetName(), keyType)
if keyType != keys.TypeLocal {
if err := keyring.Import(keyName, legKeyInfo); err != nil {
return err
}
continue
}

password, err := input.GetPassword("Enter passphrase to decrypt your key:", buf)
if err != nil {
return err
}

// NOTE: A passphrase is not actually needed here as when the key information
// is imported into the Keyring keystore it only needs the password (see: writeLocalKey).
armoredPriv, err := legacykb.ExportPrivKey(keyName, password, migratePassphrase)
if err != nil {
return err
}

if err := keyring.ImportPrivKey(keyName, armoredPriv, migratePassphrase); err != nil {
return err
}
}

return err
}
36 changes: 36 additions & 0 deletions client/keys/migrate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package keys

import (
"testing"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/tests"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"

"github.com/tendermint/tendermint/libs/cli"
)

func Test_runMigrateCmd(t *testing.T) {
cmd := addKeyCommand()
assert.NotNil(t, cmd)
mockIn, _, _ := tests.ApplyMockIO(cmd)

kbHome, kbCleanUp := tests.NewTestCaseDir(t)
assert.NotNil(t, kbHome)
defer kbCleanUp()
viper.Set(flags.FlagHome, kbHome)

viper.Set(cli.OutputFlag, OutputFormatText)

mockIn.Reset("test1234\ntest1234\n")
err := runAddCmd(cmd, []string{"keyname1"})
assert.NoError(t, err)

viper.Set(flags.FlagDryRun, true)
cmd = migrateCommand()
mockIn, _, _ = tests.ApplyMockIO(cmd)
mockIn.Reset("test1234\n")
assert.NoError(t, runMigrateCmd(cmd, []string{}))
}
1 change: 1 addition & 0 deletions client/keys/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func Commands() *cobra.Command {
deleteKeyCommand(),
updateKeyCommand(),
parseKeyStringCommand(),
migrateCommand(),
)
return cmd
}
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, 10, len(rootCommands.Commands()))
assert.Equal(t, 11, len(rootCommands.Commands()))
}
7 changes: 4 additions & 3 deletions crypto/keys/keybase_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,11 @@ func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {

privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
if err != nil {
return errors.Wrap(err, "failed to import private key")
return errors.Wrap(err, "failed to decrypt private key")
}

kb.writeLocalKey(name, privKey, passphrase)
// NOTE: The keyring keystore has no need for a passphrase.
kb.writeLocalKey(name, privKey, "")
return nil
}

Expand Down Expand Up @@ -406,7 +407,7 @@ func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string,
// CloseDB releases the lock and closes the storage backend.
func (kb keyringKeybase) CloseDB() {}

func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info {
func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, _ string) Info {
// encrypt private key using keyring
pub := priv.PubKey()
info := newLocalInfo(name, pub, string(priv.Bytes()))
Expand Down
21 changes: 17 additions & 4 deletions crypto/keys/lazy_keybase_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,30 @@ type lazyKeybaseKeyring struct {
userInput io.Reader
}

// NewKeybaseKeyring creates a new instance of a lazy keybase using a Keyring as
// the persistence layer.
func NewKeybaseKeyring(name string, dir string, userInput io.Reader, test bool) Keybase {
// NewKeyring creates a new instance of a keyring.
func NewKeyring(name string, dir string, userInput io.Reader) Keybase {
_, err := keyring.Open(keyring.Config{
ServiceName: name,
})
if err != nil {
panic(err)
}

return lazyKeybaseKeyring{name: name, dir: dir, userInput: userInput, test: test}
return lazyKeybaseKeyring{name: name, dir: dir, userInput: userInput, test: false}
}

// NewTestKeyring creates a new instance of a keyring for
// testing purposes that does not prompt users for password.
func NewTestKeyring(name string, dir string) Keybase {
if _, err := keyring.Open(keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: name,
FileDir: dir,
}); err != nil {
panic(err)
}

return lazyKeybaseKeyring{name: name, dir: dir, test: true}
}

func (lkb lazyKeybaseKeyring) lkbToKeyringConfig() keyring.Config {
Expand Down
31 changes: 8 additions & 23 deletions crypto/keys/lazy_keybase_keyring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,15 @@ import (
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"

"github.com/99designs/keyring"

"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/tests"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// New creates a new instance of a lazy keybase.
func newTestKeybaseKeyring(name string, dir string) Keybase {
if _, err := keyring.Open(keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: name,
FileDir: dir,
}); err != nil {
panic(err)
}

return lazyKeybaseKeyring{name: name, dir: dir, test: true}
}

func TestNewTestKeybaseKeyring(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)
lazykb, ok := kb.(lazyKeybaseKeyring)
require.True(t, ok)
require.Equal(t, lazykb.name, "keybasename")
Expand All @@ -41,7 +26,7 @@ func TestNewTestKeybaseKeyring(t *testing.T) {
func TestLazyKeyManagementKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)

algo := Secp256k1
n1, n2, n3 := "personal", "business", "other"
Expand Down Expand Up @@ -124,7 +109,7 @@ func TestLazyKeyManagementKeyRing(t *testing.T) {
func TestLazySignVerifyKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)
algo := Secp256k1

n1, n2, n3 := "some dude", "a dudette", "dude-ish"
Expand Down Expand Up @@ -199,7 +184,7 @@ func TestLazySignVerifyKeyRing(t *testing.T) {
func TestLazyExportImportKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)

info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
require.NoError(t, err)
Expand Down Expand Up @@ -227,7 +212,7 @@ func TestLazyExportImportKeyRing(t *testing.T) {
func TestLazyExportImportPubKeyKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)

// CreateMnemonic a private-public key pair and ensure consistency
notPasswd := "n9y25ah7"
Expand Down Expand Up @@ -266,7 +251,7 @@ func TestLazyExportImportPubKeyKeyRing(t *testing.T) {
func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)

info, _, err := kb.CreateMnemonic("john", English, "secretcpw", Secp256k1)
require.NoError(t, err)
Expand All @@ -281,7 +266,7 @@ func TestLazyExportPrivateKeyObjectKeyRing(t *testing.T) {
func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)

algo := Secp256k1
n1, n2 := "old-name", "new name"
Expand Down Expand Up @@ -314,7 +299,7 @@ func TestLazyAdvancedKeyManagementKeyRing(t *testing.T) {
func TestLazySeedPhraseKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := newTestKeybaseKeyring("keybasename", dir)
kb := NewTestKeyring("keybasename", dir)

algo := Secp256k1
n1, n2 := "lost-key", "found-again"
Expand Down
34 changes: 26 additions & 8 deletions types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"sync"
)

// DefaultKeyringServiceName defines a default service name for the keyring.
const DefaultKeyringServiceName = "cosmos"

// Config is the structure that holds the SDK configuration parameters.
// This could be used to initialize certain configuration parameters for the SDK.
type Config struct {
Expand All @@ -14,10 +17,17 @@ type Config struct {
fullFundraiserPath string
txEncoder TxEncoder
addressVerifier func([]byte) error
keyringServiceName string
}

var (
// Initializing an instance of Config
// cosmos-sdk wide global singleton
var sdkConfig *Config

// GetConfig returns the config instance for the SDK.
func GetConfig() *Config {
if sdkConfig != nil {
return sdkConfig
}
sdkConfig = &Config{
sealed: false,
bech32AddressPrefix: map[string]string{
Expand All @@ -31,11 +41,8 @@ var (
coinType: CoinType,
fullFundraiserPath: FullFundraiserPath,
txEncoder: nil,
keyringServiceName: DefaultKeyringServiceName,
}
)

// GetConfig returns the config instance for the SDK.
func GetConfig() *Config {
return sdkConfig
}

Expand Down Expand Up @@ -97,6 +104,12 @@ func (config *Config) SetFullFundraiserPath(fullFundraiserPath string) {
config.fullFundraiserPath = fullFundraiserPath
}

// Set the keyringServiceName (BIP44Prefix) on the config
func (config *Config) SetKeyringServiceName(keyringServiceName string) {
config.assertNotSealed()
config.keyringServiceName = keyringServiceName
}

// Seal seals the config such that the config state could not be modified further
func (config *Config) Seal() *Config {
config.mtx.Lock()
Expand Down Expand Up @@ -146,12 +159,17 @@ func (config *Config) GetAddressVerifier() func([]byte) error {
return config.addressVerifier
}

// Get the BIP-0044 CoinType code on the config
// GetCoinType returns the BIP-0044 CoinType code on the config.
func (config *Config) GetCoinType() uint32 {
return config.coinType
}

// Get the FullFundraiserPath (BIP44Prefix) on the config
// GetFullFundraiserPath returns the BIP44Prefix.
func (config *Config) GetFullFundraiserPath() string {
return config.fullFundraiserPath
}

// GetKeyringServiceName returns the keyring service name from the config.
func (config *Config) GetKeyringServiceName() string {
return config.keyringServiceName
}

0 comments on commit 3e6562c

Please sign in to comment.