Merge PR #5097: Add keys migrate command

Add new command to assist users migrate their keys from the legacy
on-disk keybase to the new OS keyring-based implementation.

Ref #4754
This commit is contained in:
Alessio Treglia 2019-09-30 08:49:12 -07:00 committed by Alexander Bezobchuk
parent d010b68c61
commit 3e6562ce45
9 changed files with 187 additions and 39 deletions

View File

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

91
client/keys/migrate.go Normal file
View File

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

View File

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

View File

@ -29,6 +29,7 @@ func Commands() *cobra.Command {
deleteKeyCommand(),
updateKeyCommand(),
parseKeyStringCommand(),
migrateCommand(),
)
return cmd
}

View File

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

View File

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

View File

@ -32,9 +32,8 @@ 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,
})
@ -42,7 +41,21 @@ func NewKeybaseKeyring(name string, dir string, userInput io.Reader, test bool)
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 {

View File

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

View File

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