github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/crypto/keys/keyring.go (about)

     1  package keys
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/99designs/keyring"
    15  	"github.com/pkg/errors"
    16  
    17  	tmcrypto "github.com/fibonacci-chain/fbc/libs/tendermint/crypto"
    18  	cryptoAmino "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/encoding/amino"
    19  	"github.com/tendermint/crypto/bcrypt"
    20  
    21  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/client/input"
    22  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/keyerror"
    23  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/mintkey"
    24  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    25  )
    26  
    27  const (
    28  	BackendFile       = "file"
    29  	BackendFileForRPC = "file4rpc"
    30  	BackendOS         = "os"
    31  	BackendKWallet    = "kwallet"
    32  	BackendPass       = "pass"
    33  	BackendTest       = "test"
    34  	BackendMemory     = "memory"
    35  )
    36  
    37  const (
    38  	keyringDirNameFmt     = "keyring-%s"
    39  	testKeyringDirNameFmt = "keyring-test-%s"
    40  )
    41  
    42  var _ Keybase = keyringKeybase{}
    43  
    44  // keyringKeybase implements the Keybase interface by using the Keyring library
    45  // for account key persistence.
    46  type keyringKeybase struct {
    47  	base    baseKeybase
    48  	db      keyring.Keyring
    49  	fileDir string
    50  }
    51  
    52  var maxPassphraseEntryAttempts = 3
    53  
    54  func newKeyringKeybase(db keyring.Keyring, path string, opts ...KeybaseOption) Keybase {
    55  	return keyringKeybase{
    56  		db:      db,
    57  		fileDir: path,
    58  		base:    newBaseKeybase(opts...),
    59  	}
    60  }
    61  
    62  // NewKeyring creates a new instance of a keyring. Keybase
    63  // options can be applied when generating this new Keybase.
    64  // Available backends are "os", "file", "test".
    65  func NewKeyring(
    66  	appName, backend, rootDir string, userInput io.Reader, opts ...KeybaseOption,
    67  ) (Keybase, error) {
    68  
    69  	var db keyring.Keyring
    70  	var err error
    71  	var config keyring.Config
    72  
    73  	switch backend {
    74  	case BackendTest:
    75  		config = lkbToKeyringConfig(appName, rootDir, nil, true)
    76  	case BackendFile:
    77  		config = newFileBackendKeyringConfig(appName, rootDir, userInput)
    78  	case BackendFileForRPC:
    79  		config = newFileBackendKeyringConfigForRPC(appName, rootDir, userInput)
    80  	case BackendOS:
    81  		config = lkbToKeyringConfig(appName, rootDir, userInput, false)
    82  	case BackendKWallet:
    83  		config = newKWalletBackendKeyringConfig(appName, rootDir, userInput)
    84  	case BackendPass:
    85  		config = newPassBackendKeyringConfig(appName, rootDir, userInput)
    86  	case BackendMemory:
    87  		return NewInMemory(opts...), err
    88  	default:
    89  		return nil, fmt.Errorf("unknown keyring backend %v", backend)
    90  	}
    91  	db, err = keyring.Open(config)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	return newKeyringKeybase(db, config.FileDir, opts...), nil
    97  }
    98  
    99  // CreateMnemonic generates a new key and persists it to storage, encrypted
   100  // using the provided password. It returns the generated mnemonic and the key Info.
   101  // An error is returned if it fails to generate a key for the given algo type,
   102  // or if another key is already stored under the same name.
   103  func (kb keyringKeybase) CreateMnemonic(
   104  	name string, language Language, passwd string, algo SigningAlgo, mnemonicInput string,
   105  ) (info Info, mnemonic string, err error) {
   106  
   107  	return kb.base.CreateMnemonic(kb, name, language, passwd, algo, mnemonicInput)
   108  }
   109  
   110  // CreateMnemonicWithHDPath generates a new key and persists it to storage, encrypted
   111  // using the provided password. It returns the generated mnemonic and the key Info.
   112  // An error is returned if it fails to generate a key for the given algo type,
   113  // or if another key is already stored under the same name.
   114  func (kb keyringKeybase) CreateMnemonicWithHDPath(
   115  	name string, language Language, passwd string, algo SigningAlgo, mnemonicInput string, hdPath string,
   116  ) (info Info, mnemonic string, err error) {
   117  
   118  	return kb.base.CreateMnemonicWithHDPath(kb, name, language, passwd, algo, mnemonicInput, hdPath)
   119  }
   120  
   121  // CreateAccount converts a mnemonic to a private key and persists it, encrypted
   122  // with the given password.
   123  func (kb keyringKeybase) CreateAccount(
   124  	name, mnemonic, bip39Passwd, encryptPasswd, hdPath string, algo SigningAlgo,
   125  ) (Info, error) {
   126  
   127  	return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, hdPath, algo)
   128  }
   129  
   130  // CreateLedger creates a new locally-stored reference to a Ledger keypair.
   131  // It returns the created key info and an error if the Ledger could not be queried.
   132  func (kb keyringKeybase) CreateLedger(
   133  	name string, algo SigningAlgo, hrp string, account, index uint32,
   134  ) (Info, error) {
   135  
   136  	return kb.base.CreateLedger(kb, name, algo, hrp, account, index)
   137  }
   138  
   139  // CreateOffline creates a new reference to an offline keypair. It returns the
   140  // created key info.
   141  func (kb keyringKeybase) CreateOffline(name string, pub tmcrypto.PubKey, algo SigningAlgo) (Info, error) {
   142  	return kb.base.writeOfflineKey(kb, name, pub, algo), nil
   143  }
   144  
   145  // CreateMulti creates a new reference to a multisig (offline) keypair. It
   146  // returns the created key Info object.
   147  func (kb keyringKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) {
   148  	return kb.base.writeMultisigKey(kb, name, pub), nil
   149  }
   150  
   151  // List returns the keys from storage in alphabetical order.
   152  func (kb keyringKeybase) List() ([]Info, error) {
   153  	var res []Info
   154  	keys, err := kb.db.Keys()
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	sort.Strings(keys)
   160  
   161  	for _, key := range keys {
   162  		if strings.HasSuffix(key, infoSuffix) {
   163  			rawInfo, err := kb.db.Get(key)
   164  			if err != nil {
   165  				return nil, err
   166  			}
   167  
   168  			if len(rawInfo.Data) == 0 {
   169  				return nil, keyerror.NewErrKeyNotFound(key)
   170  			}
   171  
   172  			info, err := unmarshalInfo(rawInfo.Data)
   173  			if err != nil {
   174  				return nil, err
   175  			}
   176  
   177  			res = append(res, info)
   178  		}
   179  	}
   180  
   181  	return res, nil
   182  }
   183  
   184  // Get returns the public information about one key.
   185  func (kb keyringKeybase) Get(name string) (Info, error) {
   186  	key := infoKey(name)
   187  
   188  	bs, err := kb.db.Get(string(key))
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	if len(bs.Data) == 0 {
   194  		return nil, keyerror.NewErrKeyNotFound(name)
   195  	}
   196  
   197  	return unmarshalInfo(bs.Data)
   198  }
   199  
   200  // GetByAddress fetches a key by address and returns its public information.
   201  func (kb keyringKeybase) GetByAddress(address types.AccAddress) (Info, error) {
   202  	ik, err := kb.db.Get(string(addrKey(address)))
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	if len(ik.Data) == 0 {
   208  		return nil, fmt.Errorf("key with address %s not found", address)
   209  	}
   210  
   211  	bs, err := kb.db.Get(string(ik.Data))
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	return unmarshalInfo(bs.Data)
   217  }
   218  
   219  // Sign signs an arbitrary set of bytes with the named key. It returns an error
   220  // if the key doesn't exist or the decryption fails.
   221  func (kb keyringKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) {
   222  	info, err := kb.Get(name)
   223  	if err != nil {
   224  		return
   225  	}
   226  
   227  	var priv tmcrypto.PrivKey
   228  
   229  	switch i := info.(type) {
   230  	case localInfo:
   231  		if i.PrivKeyArmor == "" {
   232  			return nil, nil, fmt.Errorf("private key not available")
   233  		}
   234  
   235  		priv, err = cryptoAmino.PrivKeyFromBytes([]byte(i.PrivKeyArmor))
   236  		if err != nil {
   237  			return nil, nil, err
   238  		}
   239  
   240  	case ledgerInfo:
   241  		return kb.base.SignWithLedger(info, msg)
   242  
   243  	case offlineInfo, multiInfo:
   244  		return kb.base.DecodeSignature(info, msg)
   245  	}
   246  
   247  	sig, err = priv.Sign(msg)
   248  	if err != nil {
   249  		return nil, nil, err
   250  	}
   251  
   252  	return sig, priv.PubKey(), nil
   253  }
   254  
   255  // ExportPrivateKeyObject exports an armored private key object.
   256  func (kb keyringKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) {
   257  	info, err := kb.Get(name)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	var priv tmcrypto.PrivKey
   263  
   264  	switch linfo := info.(type) {
   265  	case localInfo:
   266  		if linfo.PrivKeyArmor == "" {
   267  			err = fmt.Errorf("private key not available")
   268  			return nil, err
   269  		}
   270  
   271  		priv, err = cryptoAmino.PrivKeyFromBytes([]byte(linfo.PrivKeyArmor))
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  
   276  	case ledgerInfo, offlineInfo, multiInfo:
   277  		return nil, errors.New("only works on local private keys")
   278  	}
   279  
   280  	return priv, nil
   281  }
   282  
   283  // Export exports armored private key to the caller.
   284  func (kb keyringKeybase) Export(name string) (armor string, err error) {
   285  	bz, err := kb.db.Get(string(infoKey(name)))
   286  	if err != nil {
   287  		return "", err
   288  	}
   289  
   290  	if bz.Data == nil {
   291  		return "", fmt.Errorf("no key to export with name: %s", name)
   292  	}
   293  
   294  	return mintkey.ArmorInfoBytes(bz.Data), nil
   295  }
   296  
   297  // ExportPubKey returns public keys in ASCII armored format. It retrieves an Info
   298  // object by its name and return the public key in a portable format.
   299  func (kb keyringKeybase) ExportPubKey(name string) (armor string, err error) {
   300  	bz, err := kb.Get(name)
   301  	if err != nil {
   302  		return "", err
   303  	}
   304  
   305  	if bz == nil {
   306  		return "", fmt.Errorf("no key to export with name: %s", name)
   307  	}
   308  
   309  	return mintkey.ArmorPubKeyBytes(bz.GetPubKey().Bytes(), string(bz.GetAlgo())), nil
   310  }
   311  
   312  // Import imports armored private key.
   313  func (kb keyringKeybase) Import(name string, armor string) error {
   314  	bz, _ := kb.Get(name)
   315  
   316  	if bz != nil {
   317  		pubkey := bz.GetPubKey()
   318  
   319  		if len(pubkey.Bytes()) > 0 {
   320  			return fmt.Errorf("cannot overwrite data for name: %s", name)
   321  		}
   322  	}
   323  
   324  	infoBytes, err := mintkey.UnarmorInfoBytes(armor)
   325  	if err != nil {
   326  		return err
   327  	}
   328  
   329  	info, err := unmarshalInfo(infoBytes)
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	kb.writeInfo(name, info)
   335  
   336  	err = kb.db.Set(keyring.Item{
   337  		Key:  string(addrKey(info.GetAddress())),
   338  		Data: infoKey(name),
   339  	})
   340  	if err != nil {
   341  		return err
   342  	}
   343  
   344  	return nil
   345  }
   346  
   347  // ExportPrivKey returns a private key in ASCII armored format. An error is returned
   348  // if the key does not exist or a wrong encryption passphrase is supplied.
   349  func (kb keyringKeybase) ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) {
   350  	priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase)
   351  	if err != nil {
   352  		return "", err
   353  	}
   354  
   355  	info, err := kb.Get(name)
   356  	if err != nil {
   357  		return "", err
   358  	}
   359  
   360  	return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase, string(info.GetAlgo())), nil
   361  }
   362  
   363  // ImportPrivKey imports a private key in ASCII armor format. An error is returned
   364  // if a key with the same name exists or a wrong encryption passphrase is
   365  // supplied.
   366  func (kb keyringKeybase) ImportPrivKey(name, armor, passphrase string) error {
   367  	if kb.HasKey(name) {
   368  		return fmt.Errorf("cannot overwrite key: %s", name)
   369  	}
   370  
   371  	privKey, algo, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase)
   372  	if err != nil {
   373  		return errors.Wrap(err, "failed to decrypt private key")
   374  	}
   375  
   376  	// NOTE: The keyring keystore has no need for a passphrase.
   377  	kb.writeLocalKey(name, privKey, "", SigningAlgo(algo))
   378  	return nil
   379  }
   380  
   381  // HasKey returns whether the key exists in the keyring.
   382  func (kb keyringKeybase) HasKey(name string) bool {
   383  	bz, _ := kb.Get(name)
   384  	return bz != nil
   385  }
   386  
   387  // ImportPubKey imports an ASCII-armored public key. It will store a new Info
   388  // object holding a public key only, i.e. it will not be possible to sign with
   389  // it as it lacks the secret key.
   390  func (kb keyringKeybase) ImportPubKey(name string, armor string) error {
   391  	bz, _ := kb.Get(name)
   392  	if bz != nil {
   393  		pubkey := bz.GetPubKey()
   394  
   395  		if len(pubkey.Bytes()) > 0 {
   396  			return fmt.Errorf("cannot overwrite data for name: %s", name)
   397  		}
   398  	}
   399  
   400  	pubBytes, algo, err := mintkey.UnarmorPubKeyBytes(armor)
   401  	if err != nil {
   402  		return err
   403  	}
   404  
   405  	pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes)
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	kb.base.writeOfflineKey(kb, name, pubKey, SigningAlgo(algo))
   411  	return nil
   412  }
   413  
   414  // Delete removes key forever, but we must present the proper passphrase before
   415  // deleting it (for security). It returns an error if the key doesn't exist or
   416  // passphrases don't match. The passphrase is ignored when deleting references to
   417  // offline and Ledger / HW wallet keys.
   418  func (kb keyringKeybase) Delete(name, _ string, _ bool) error {
   419  	// verify we have the proper password before deleting
   420  	info, err := kb.Get(name)
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	err = kb.db.Remove(string(addrKey(info.GetAddress())))
   426  	if err != nil {
   427  		return err
   428  	}
   429  
   430  	err = kb.db.Remove(string(infoKey(name)))
   431  	if err != nil {
   432  		return err
   433  	}
   434  
   435  	return nil
   436  }
   437  
   438  // Update changes the passphrase with which an already stored key is encrypted.
   439  // The oldpass must be the current passphrase used for encryption, getNewpass is
   440  // a function to get the passphrase to permanently replace the current passphrase.
   441  func (kb keyringKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error {
   442  	info, err := kb.Get(name)
   443  	if err != nil {
   444  		return err
   445  	}
   446  
   447  	switch linfo := info.(type) {
   448  	case localInfo:
   449  		key, _, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
   450  		if err != nil {
   451  			return err
   452  		}
   453  
   454  		newpass, err := getNewpass()
   455  		if err != nil {
   456  			return err
   457  		}
   458  
   459  		kb.writeLocalKey(name, key, newpass, linfo.GetAlgo())
   460  		return nil
   461  
   462  	default:
   463  		return fmt.Errorf("locally stored key required; received: %v", reflect.TypeOf(info).String())
   464  	}
   465  }
   466  
   467  // SupportedAlgos returns a list of supported signing algorithms.
   468  func (kb keyringKeybase) SupportedAlgos() []SigningAlgo {
   469  	return kb.base.SupportedAlgos()
   470  }
   471  
   472  // SupportedAlgosLedger returns a list of supported ledger signing algorithms.
   473  func (kb keyringKeybase) SupportedAlgosLedger() []SigningAlgo {
   474  	return kb.base.SupportedAlgosLedger()
   475  }
   476  
   477  // CloseDB releases the lock and closes the storage backend.
   478  func (kb keyringKeybase) CloseDB() {}
   479  
   480  func (kb keyringKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, _ string, algo SigningAlgo) Info {
   481  	// encrypt private key using keyring
   482  	pub := priv.PubKey()
   483  	info := newLocalInfo(name, pub, string(priv.Bytes()), algo)
   484  
   485  	kb.writeInfo(name, info)
   486  	return info
   487  }
   488  
   489  func (kb keyringKeybase) writeInfo(name string, info Info) {
   490  	// write the info by key
   491  	key := infoKey(name)
   492  	serializedInfo := marshalInfo(info)
   493  
   494  	err := kb.db.Set(keyring.Item{
   495  		Key:  string(key),
   496  		Data: serializedInfo,
   497  	})
   498  	if err != nil {
   499  		panic(err)
   500  	}
   501  
   502  	err = kb.db.Set(keyring.Item{
   503  		Key:  string(addrKey(info.GetAddress())),
   504  		Data: key,
   505  	})
   506  	if err != nil {
   507  		panic(err)
   508  	}
   509  }
   510  
   511  // FileDir show keyringKeybase absolute position
   512  func (kb keyringKeybase) FileDir() (string, error) {
   513  	return resolvePath(kb.fileDir)
   514  }
   515  
   516  func lkbToKeyringConfig(appName, dir string, buf io.Reader, test bool) keyring.Config {
   517  	if test {
   518  		return keyring.Config{
   519  			AllowedBackends: []keyring.BackendType{keyring.FileBackend},
   520  			ServiceName:     appName,
   521  			FileDir:         filepath.Join(dir, fmt.Sprintf(testKeyringDirNameFmt, appName)),
   522  			FilePasswordFunc: func(_ string) (string, error) {
   523  				return "test", nil
   524  			},
   525  		}
   526  	}
   527  
   528  	return keyring.Config{
   529  		ServiceName:      appName,
   530  		FileDir:          dir,
   531  		FilePasswordFunc: newRealPrompt(dir, buf),
   532  	}
   533  }
   534  
   535  func newKWalletBackendKeyringConfig(appName, _ string, _ io.Reader) keyring.Config {
   536  	return keyring.Config{
   537  		AllowedBackends: []keyring.BackendType{keyring.KWalletBackend},
   538  		ServiceName:     "kdewallet",
   539  		KWalletAppID:    appName,
   540  		KWalletFolder:   "",
   541  	}
   542  }
   543  
   544  func newPassBackendKeyringConfig(appName, dir string, _ io.Reader) keyring.Config {
   545  	prefix := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, appName))
   546  	return keyring.Config{
   547  		AllowedBackends: []keyring.BackendType{keyring.PassBackend},
   548  		ServiceName:     appName,
   549  		PassPrefix:      prefix,
   550  	}
   551  }
   552  
   553  func newFileBackendKeyringConfig(name, dir string, buf io.Reader) keyring.Config {
   554  	fileDir := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, name))
   555  	return keyring.Config{
   556  		AllowedBackends:  []keyring.BackendType{keyring.FileBackend},
   557  		ServiceName:      name,
   558  		FileDir:          fileDir,
   559  		FilePasswordFunc: newRealPrompt(fileDir, buf),
   560  	}
   561  }
   562  
   563  func newFileBackendKeyringConfigForRPC(name, dir string, buf io.Reader) keyring.Config {
   564  	fileDir := filepath.Join(dir, fmt.Sprintf(keyringDirNameFmt, name))
   565  	return keyring.Config{
   566  		AllowedBackends: []keyring.BackendType{keyring.FileBackend},
   567  		ServiceName:     name,
   568  		FileDir:         fileDir,
   569  		FilePasswordFunc: func(_ string) (string, error) {
   570  			return "test", nil
   571  		},
   572  	}
   573  }
   574  
   575  func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
   576  	return func(prompt string) (string, error) {
   577  		keyhashStored := false
   578  		keyhashFilePath := filepath.Join(dir, "keyhash")
   579  		var keyhash []byte
   580  
   581  		// read hashfile from file to check input password
   582  		_, err := os.Stat(keyhashFilePath)
   583  		switch {
   584  		case err == nil:
   585  			keyhash, err = ioutil.ReadFile(keyhashFilePath)
   586  			if err != nil {
   587  				return "", fmt.Errorf("failed to read %s: %v", keyhashFilePath, err)
   588  			}
   589  
   590  			keyhashStored = true
   591  
   592  		case os.IsNotExist(err):
   593  			keyhashStored = false
   594  
   595  		default:
   596  			return "", fmt.Errorf("failed to open %s: %v", keyhashFilePath, err)
   597  		}
   598  
   599  		failureCounter := 0
   600  		for {
   601  			failureCounter++
   602  			if failureCounter > maxPassphraseEntryAttempts {
   603  				return "", fmt.Errorf("too many failed passphrase attempts")
   604  			}
   605  
   606  			buf := bufio.NewReader(buf)
   607  			pass, err := input.GetPassword("Enter keyring passphrase:", buf)
   608  			if err != nil {
   609  				fmt.Fprintln(os.Stderr, err)
   610  				continue
   611  			}
   612  
   613  			if keyhashStored {
   614  				if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
   615  					fmt.Fprintln(os.Stderr, "incorrect passphrase")
   616  					continue
   617  				}
   618  				return pass, nil
   619  			}
   620  
   621  			reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf)
   622  			if err != nil {
   623  				fmt.Fprintln(os.Stderr, err)
   624  				continue
   625  			}
   626  
   627  			if pass != reEnteredPass {
   628  				fmt.Fprintln(os.Stderr, "passphrase do not match")
   629  				continue
   630  			}
   631  
   632  			saltBytes := tmcrypto.CRandBytes(16)
   633  			passwordHash, err := bcrypt.GenerateFromPassword(saltBytes, []byte(pass), 2)
   634  			if err != nil {
   635  				fmt.Fprintln(os.Stderr, err)
   636  				continue
   637  			}
   638  
   639  			if err := ioutil.WriteFile(dir+"/keyhash", passwordHash, 0555); err != nil {
   640  				return "", err
   641  			}
   642  			return pass, nil
   643  		}
   644  	}
   645  }