cosmos-sdk/crypto/keys/keybase_keyring.go

440 lines
11 KiB
Go
Raw Normal View History

package keys
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/99designs/keyring"
"github.com/pkg/errors"
tmcrypto "github.com/tendermint/tendermint/crypto"
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
"github.com/cosmos/cosmos-sdk/crypto/keys/keyerror"
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
"github.com/cosmos/cosmos-sdk/types"
)
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{},
}
}
// 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 (kb keyringKeybase) CreateMnemonic(
name string, language Language, passwd string, algo SigningAlgo,
) (info Info, mnemonic string, err error) {
return kb.base.CreateMnemonic(kb, name, language, passwd, algo)
}
// CreateAccount converts a mnemonic to a private key and persists it, encrypted
// with the given password.
func (kb keyringKeybase) CreateAccount(
name, mnemonic, bip39Passwd, encryptPasswd string, account, index uint32,
) (Info, error) {
return kb.base.CreateAccount(kb, 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 (kb keyringKeybase) Derive(
name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params,
) (info Info, err error) {
return kb.base.Derive(kb, name, mnemonic, bip39Passphrase, 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 (kb keyringKeybase) CreateLedger(
name string, algo SigningAlgo, hrp string, account, index uint32,
) (Info, error) {
return kb.base.CreateLedger(kb, name, algo, hrp, account, index)
}
// CreateOffline creates a new reference to an offline keypair. It returns the
// created key info.
func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeOfflineKey(kb, name, pub), nil
}
// CreateMulti creates a new reference to a multisig (offline) keypair. It
// returns the created key Info object.
func (kb keyringKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) {
return kb.base.writeMultisigKey(kb, name, pub), nil
}
// List returns the keys from storage in alphabetical order.
func (kb keyringKeybase) List() ([]Info, error) {
var res []Info
keys, err := kb.db.Keys()
if err != nil {
return nil, err
}
sort.Strings(keys)
for _, key := range keys {
if strings.HasSuffix(key, infoSuffix) {
rawInfo, err := kb.db.Get(key)
if err != nil {
return nil, err
}
if len(rawInfo.Data) == 0 {
return nil, keyerror.NewErrKeyNotFound(key)
}
info, err := unmarshalInfo(rawInfo.Data)
if err != nil {
return nil, err
}
res = append(res, info)
}
}
return res, nil
}
// Get returns the public information about one key.
func (kb keyringKeybase) Get(name string) (Info, error) {
key := infoKey(name)
bs, err := kb.db.Get(string(key))
if err != nil {
return nil, err
}
if len(bs.Data) == 0 {
return nil, keyerror.NewErrKeyNotFound(name)
}
return unmarshalInfo(bs.Data)
}
// GetByAddress fetches a key by address and returns its public information.
func (kb keyringKeybase) GetByAddress(address types.AccAddress) (Info, error) {
ik, err := kb.db.Get(string(addrKey(address)))
if err != nil {
return nil, err
}
if len(ik.Data) == 0 {
return nil, fmt.Errorf("key with address %s not found", address)
}
bs, err := kb.db.Get(string(ik.Data))
if err != nil {
return nil, err
}
return unmarshalInfo(bs.Data)
}
// 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 (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
info, err := kb.Get(name)
if err != nil {
return
}
var priv tmcrypto.PrivKey
switch i := info.(type) {
case localInfo:
if i.PrivKeyArmor == "" {
return nil, nil, fmt.Errorf("private key not available")
}
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(i.PrivKeyArmor))
if err != nil {
return nil, nil, err
}
case ledgerInfo:
return kb.base.SignWithLedger(info, msg)
case offlineInfo, multiInfo:
return kb.base.DecodeSignature(info, msg)
}
sig, err = priv.Sign(msg)
if err != nil {
return nil, nil, err
}
return sig, priv.PubKey(), nil
}
// ExportPrivateKeyObject exports an armored private key object.
func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
info, err := kb.Get(name)
if err != nil {
return nil, err
}
var priv tmcrypto.PrivKey
switch linfo := info.(type) {
case localInfo:
if linfo.PrivKeyArmor == "" {
err = fmt.Errorf("private key not available")
return nil, err
}
priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor))
if err != nil {
return nil, err
}
case ledgerInfo, offlineInfo, multiInfo:
return nil, errors.New("only works on local private keys")
}
return priv, nil
}
// Export exports armored private key to the caller.
func (kb keyringKeybase) Export(name string) (armor string, err error) {
bz, err := kb.db.Get(string(infoKey(name)))
if err != nil {
return "", err
}
if bz.Data == nil {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return mintkey.ArmorInfoBytes(bz.Data), nil
}
// 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 (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) {
bz, err := kb.Get(name)
if err != nil {
return "", err
}
if bz == nil {
return "", fmt.Errorf("no key to export with name: %s", name)
}
return mintkey.ArmorPubKeyBytes(bz.GetPubKey().Bytes()), nil
}
// Import imports armored private key.
func (kb keyringKeybase) Import(name string, armor string) error {
bz, _ := kb.Get(name)
if bz != nil {
pubkey := bz.GetPubKey()
if len(pubkey.Bytes()) > 0 {
return fmt.Errorf("cannot overwrite data for name: %s", name)
}
}
infoBytes, err := mintkey.UnarmorInfoBytes(armor)
if err != nil {
return err
}
info, err := unmarshalInfo(infoBytes)
if err != nil {
return err
}
kb.writeInfo(name, info)
err = kb.db.Set(keyring.Item{
Key: string(addrKey(info.GetAddress())),
Data: infoKey(name),
})
if err != nil {
return err
}
return nil
}
// 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 (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) {
priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
if err != nil {
return "", err
}
return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil
}
// 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 (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {
if kb.HasKey(name) {
return fmt.Errorf("cannot overwrite key: %s", name)
}
privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
if err != nil {
return errors.Wrap(err, "failed to decrypt private key")
}
// NOTE: The keyring keystore has no need for a passphrase.
kb.writeLocalKey(name, privKey, "")
return nil
}
// HasKey returns whether the key exists in the keyring.
func (kb keyringKeybase) HasKey(name string) bool {
bz, _ := kb.Get(name)
return bz != nil
}
// 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 (kb keyringKeybase) ImportPubKey(name string, armor string) error {
bz, _ := kb.Get(name)
if bz != nil {
pubkey := bz.GetPubKey()
if len(pubkey.Bytes()) > 0 {
return fmt.Errorf("cannot overwrite data for name: %s", name)
}
}
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
if err != nil {
return err
}
pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
if err != nil {
return err
}
kb.base.writeOfflineKey(kb, name, pubKey)
return nil
}
// 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 (kb keyringKeybase) Delete(name, passphrase string, skipPass bool) error {
// verify we have the proper password before deleting
info, err := kb.Get(name)
if err != nil {
return err
}
err = kb.db.Remove(string(addrKey(info.GetAddress())))
if err != nil {
return err
}
err = kb.db.Remove(string(infoKey(name)))
if err != nil {
return err
}
return nil
}
// 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 (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
info, err := kb.Get(name)
if err != nil {
return err
}
switch linfo := info.(type) {
case localInfo:
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
if err != nil {
return err
}
newpass, err := getNewpass()
if err != nil {
return err
}
kb.writeLocalKey(name, key, newpass)
return nil
default:
return fmt.Errorf("locally stored key required; received: %v", reflect.TypeOf(info).String())
}
}
// CloseDB releases the lock and closes the storage backend.
func (kb keyringKeybase) CloseDB() {}
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()))
kb.writeInfo(name, info)
return info
}
func (kb keyringKeybase) writeInfo(name string, info Info) {
// write the info by key
key := infoKey(name)
serializedInfo := marshalInfo(info)
err := kb.db.Set(keyring.Item{
Key: string(key),
Data: serializedInfo,
})
if err != nil {
panic(err)
}
err = kb.db.Set(keyring.Item{
Key: string(addrKey(info.GetAddress())),
Data: key,
})
if err != nil {
panic(err)
}
}