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

Add keys migrate command #5097

Merged
merged 16 commits into from
Sep 30, 2019
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
85 changes: 85 additions & 0 deletions client/keys/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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"
)

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.NewTestKeybaseKeyring(types.GetConfig().GetKeyringServiceName(), rootDir)
} else {
keyring = keys.NewKeybaseKeyring(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
}

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

armoredPriv, err := legacykb.ExportPrivKey(keyName, passwd, "abc")
Copy link
Contributor

Choose a reason for hiding this comment

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

huh? Shouldn't we export with a user's encryption passphrase instead of hard-codec "abc"?

if err != nil {
return err
}

if err := keyring.ImportPrivKey(keyName, armoredPriv, "abc"); 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()))
}
18 changes: 16 additions & 2 deletions crypto/keys/lazy_keybase_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,29 @@ type lazyKeybaseKeyring struct {

// 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 {
func NewKeybaseKeyring(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}
}

// NewTestKeybaseKeyring creates a new instance of a keyring keybase
// for testing purposes that does not prompt users for password.
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 (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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("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 := NewTestKeybaseKeyring("keybasename", dir)

algo := Secp256k1
n1, n2 := "lost-key", "found-again"
Expand Down
20 changes: 18 additions & 2 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,6 +17,7 @@ type Config struct {
fullFundraiserPath string
txEncoder TxEncoder
addressVerifier func([]byte) error
keyringServiceName string
}

var (
Expand All @@ -31,6 +35,7 @@ var (
coinType: CoinType,
fullFundraiserPath: FullFundraiserPath,
txEncoder: nil,
keyringServiceName: DefaultKeyringServiceName,
alessio marked this conversation as resolved.
Show resolved Hide resolved
}
)

Expand Down Expand Up @@ -97,6 +102,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 +157,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
}