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
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,
alessio marked this conversation as resolved.
Show resolved Hide resolved
}
)

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