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:
parent
d010b68c61
commit
3e6562ce45
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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{}))
|
||||
}
|
|
@ -29,6 +29,7 @@ func Commands() *cobra.Command {
|
|||
deleteKeyCommand(),
|
||||
updateKeyCommand(),
|
||||
parseKeyStringCommand(),
|
||||
migrateCommand(),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue