Merge PR #5104: Remove keyring lazy implementation

This commit is contained in:
Alessio Treglia 2019-09-30 09:16:53 -07:00 committed by Alexander Bezobchuk
parent 3e6562ce45
commit 46cd6112e1
3 changed files with 129 additions and 398 deletions

View File

@ -1,16 +1,25 @@
package keys
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/99designs/keyring"
"github.com/pkg/errors"
"github.com/tendermint/crypto/bcrypt"
"github.com/tendermint/tendermint/crypto"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
@ -21,26 +30,32 @@ var _ Keybase = keyringKeybase{}
// keyringKeybase implements the Keybase interface by using the Keyring library
// for account key persistence.
//
// TODO: There is no need for keyringKeybase to implement the Keybase interface
// or even have any of its members be public as it currently cannot be used by
// the outside world. Any client uses this implementation through the
// lazyKeybaseKeyring implementation. Consider either:
//
// 1. Remove lazyKeybaseKeyring and make keyringKeybase useable by the outside
// world.
// 2. Mark all keyringKeybase methods private and remove the compile time
// Keybase interface assertion.
type keyringKeybase struct {
db keyring.Keyring
base baseKeybase
}
func newKeyringKeybase(db keyring.Keyring) Keybase {
return keyringKeybase{
db: db,
base: baseKeybase{},
var maxPassphraseEntryAttempts = 3
// NewKeyring creates a new instance of a keyring.
func NewKeyring(name string, dir string, userInput io.Reader) Keybase {
db, err := keyring.Open(lkbToKeyringConfig(name, dir, userInput, false))
if err != nil {
panic(err)
}
return newKeyringKeybase(db)
}
// 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 {
db, err := keyring.Open(lkbToKeyringConfig(name, dir, nil, true))
if err != nil {
panic(err)
}
return newKeyringKeybase(db)
}
// CreateMnemonic generates a new key and persists it to storage, encrypted
@ -437,3 +452,103 @@ func (kb keyringKeybase) writeInfo(name string, info Info) {
panic(err)
}
}
//nolint: funlen
func lkbToKeyringConfig(name, dir string, buf io.Reader, test bool) keyring.Config {
if test {
return keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: name,
FileDir: dir,
FilePasswordFunc: fakePrompt,
}
}
realPrompt := func(prompt string) (string, error) {
keyhashStored := false
keyhashFilePath := filepath.Join(dir, "keyhash")
var keyhash []byte
_, err := os.Stat(keyhashFilePath)
switch {
case err == nil:
keyhash, err = ioutil.ReadFile(keyhashFilePath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err)
}
keyhashStored = true
case os.IsNotExist(err):
keyhashStored = false
default:
return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err)
}
failureCounter := 0
for {
failureCounter++
if failureCounter > maxPassphraseEntryAttempts {
return "", fmt.Errorf("too many failed passphrase attempts")
}
buf := bufio.NewReader(buf)
pass, err := input.GetPassword("Enter keyring passphrase:", buf)
if err != nil {
continue
}
if keyhashStored {
if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
fmt.Fprintln(os.Stderr, "incorrect passphrase")
continue
}
return pass, nil
}
reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if pass != reEnteredPass {
fmt.Fprintln(os.Stderr, "passphrase do not match")
continue
}
saltBytes := crypto.CRandBytes(16)
passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if err := ioutil.WriteFile(dir+"/keyhash", passwordHash, 0555); err != nil {
return "", err
}
return pass, nil
}
}
return keyring.Config{
ServiceName: name,
FileDir: dir,
FilePasswordFunc: realPrompt,
}
}
func fakePrompt(prompt string) (string, error) {
fmt.Fprintln(os.Stderr, "Fake prompt for passphase. Testing only")
return "test", nil
}
func newKeyringKeybase(db keyring.Keyring) Keybase {
return keyringKeybase{
db: db,
base: baseKeybase{},
}
}

View File

@ -14,15 +14,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestNewTestKeybaseKeyring(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()
kb := NewTestKeyring("keybasename", dir)
lazykb, ok := kb.(lazyKeybaseKeyring)
require.True(t, ok)
require.Equal(t, lazykb.name, "keybasename")
}
func TestLazyKeyManagementKeyRing(t *testing.T) {
dir, cleanup := tests.NewTestCaseDir(t)
defer cleanup()

View File

@ -1,375 +0,0 @@
package keys
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/99designs/keyring"
"github.com/tendermint/crypto/bcrypt"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var (
_ Keybase = lazyKeybaseKeyring{}
maxPassphraseEntryAttempts = 3
)
// lazyKeybaseKeyring implements a public wrapper around the keyringKeybase type
// and implements the Keybase interface.
type lazyKeybaseKeyring struct {
name string
dir string
test bool
userInput io.Reader
}
// 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: 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 {
if lkb.test {
return keyring.Config{
AllowedBackends: []keyring.BackendType{"file"},
ServiceName: lkb.name,
FileDir: lkb.dir,
FilePasswordFunc: fakePrompt,
}
}
realPrompt := func(prompt string) (string, error) {
keyhashStored := false
keyhashFilePath := filepath.Join(lkb.dir, "keyhash")
var keyhash []byte
_, err := os.Stat(keyhashFilePath)
switch {
case err == nil:
keyhash, err = ioutil.ReadFile(keyhashFilePath)
if err != nil {
return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err)
}
keyhashStored = true
case os.IsNotExist(err):
keyhashStored = false
default:
return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err)
}
failureCounter := 0
for {
failureCounter++
if failureCounter > maxPassphraseEntryAttempts {
return "", fmt.Errorf("too many failed passphrase attempts")
}
buf := bufio.NewReader(lkb.userInput)
pass, err := input.GetPassword("Enter keyring passphrase:", buf)
if err != nil {
continue
}
if keyhashStored {
if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
fmt.Fprintln(os.Stderr, "incorrect passphrase")
continue
}
return pass, nil
}
reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if pass != reEnteredPass {
fmt.Fprintln(os.Stderr, "passphrase do not match")
continue
}
saltBytes := crypto.CRandBytes(16)
passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2)
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if err := ioutil.WriteFile(lkb.dir+"/keyhash", passwordHash, 0555); err != nil {
return "", err
}
return pass, nil
}
}
return keyring.Config{
ServiceName: lkb.name,
FileDir: lkb.dir,
FilePasswordFunc: realPrompt,
}
}
func fakePrompt(prompt string) (string, error) {
fmt.Fprintln(os.Stderr, "Fake Prompt for passphase. Testing only")
return "test", nil
}
// List returns the keys from storage in alphabetical order.
func (lkb lazyKeybaseKeyring) List() ([]Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).List()
}
// Get returns the public information about one key.
func (lkb lazyKeybaseKeyring) Get(name string) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).Get(name)
}
// GetByAddress fetches a key by address and returns its public information.
func (lkb lazyKeybaseKeyring) GetByAddress(address sdk.AccAddress) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).GetByAddress(address)
}
// Delete removes key forever, but we must present the proper passphrase before
// deleting it (for security). It returns an error if the key doesn't exist or
// passphrases don't match. The passphrase is ignored when deleting references to
// offline and Ledger / HW wallet keys.
func (lkb lazyKeybaseKeyring) Delete(name, passphrase string, skipPass bool) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).Delete(name, passphrase, skipPass)
}
// Sign signs an arbitrary set of bytes with the named key. It returns an error
// if the key doesn't exist or the decryption fails.
func (lkb lazyKeybaseKeyring) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, nil, err
}
return newKeyringKeybase(db).Sign(name, passphrase, msg)
}
// CreateMnemonic generates a new key and persists it to storage, encrypted
// using the provided password. It returns the generated mnemonic and the key Info.
// An error is returned if it fails to generate a key for the given algo type,
// or if another key is already stored under the same name.
func (lkb lazyKeybaseKeyring) CreateMnemonic(
name string, language Language, passwd string, algo SigningAlgo,
) (info Info, seed string, err error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, "", err
}
return newKeyringKeybase(db).CreateMnemonic(name, language, passwd, algo)
}
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (lkb lazyKeybaseKeyring) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index)
}
// Derive computes a BIP39 seed from th mnemonic and bip39Passphrase. It creates
// a private key from the seed using the BIP44 params.
func (lkb lazyKeybaseKeyring) Derive(
name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params,
) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params)
}
// CreateLedger creates a new locally-stored reference to a Ledger keypair.
// It returns the created key info and an error if the Ledger could not be queried.
func (lkb lazyKeybaseKeyring) CreateLedger(
name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateLedger(name, algo, hrp, account, index)
}
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
func (lkb lazyKeybaseKeyring) CreateOffline(name string, pubkey crypto.PubKey) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateOffline(name, pubkey)
}
// CreateMulti creates a new reference to a multisig (offline) keypair. It
// returns the created key Info object.
func (lkb lazyKeybaseKeyring) CreateMulti(name string, pubkey crypto.PubKey) (Info, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).CreateMulti(name, pubkey)
}
// Update changes the passphrase with which an already stored key is encrypted.
// The oldpass must be the current passphrase used for encryption, getNewpass is
// a function to get the passphrase to permanently replace the current passphrase.
func (lkb lazyKeybaseKeyring) Update(name, oldpass string, getNewpass func() (string, error)) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).Update(name, oldpass, getNewpass)
}
// Import imports armored private key.
func (lkb lazyKeybaseKeyring) Import(name, armor string) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).Import(name, armor)
}
// ImportPrivKey imports a private key in ASCII armor format. An error is returned
// if a key with the same name exists or a wrong encryption passphrase is
// supplied.
func (lkb lazyKeybaseKeyring) ImportPrivKey(name, armor, passphrase string) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).ImportPrivKey(name, armor, passphrase)
}
// ImportPubKey imports an ASCII-armored public key. It will store a new Info
// object holding a public key only, i.e. it will not be possible to sign with
// it as it lacks the secret key.
func (lkb lazyKeybaseKeyring) ImportPubKey(name, armor string) error {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return err
}
return newKeyringKeybase(db).ImportPubKey(name, armor)
}
// Export exports armored a private key for the given name.
func (lkb lazyKeybaseKeyring) Export(name string) (armor string, err error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return "", err
}
return newKeyringKeybase(db).Export(name)
}
// ExportPubKey returns public keys in ASCII armored format. It retrieves an Info
// object by its name and return the public key in a portable format.
func (lkb lazyKeybaseKeyring) ExportPubKey(name string) (armor string, err error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return "", err
}
return newKeyringKeybase(db).ExportPubKey(name)
}
// ExportPrivateKeyObject exports an armored private key object.
func (lkb lazyKeybaseKeyring) ExportPrivateKeyObject(name, passphrase string) (crypto.PrivKey, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return nil, err
}
return newKeyringKeybase(db).ExportPrivateKeyObject(name, passphrase)
}
// ExportPrivKey returns a private key in ASCII armored format. An error is returned
// if the key does not exist or a wrong encryption passphrase is supplied.
func (lkb lazyKeybaseKeyring) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (string, error) {
db, err := keyring.Open(lkb.lkbToKeyringConfig())
if err != nil {
return "", err
}
return newKeyringKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase)
}
func (lkb lazyKeybaseKeyring) CloseDB() {}