|
|
- package keys
-
- import (
- "fmt"
- "strings"
-
- "github.com/pkg/errors"
- crypto "github.com/tendermint/go-crypto"
- dbm "github.com/tendermint/tmlibs/db"
-
- "github.com/tendermint/go-crypto/nano"
- )
-
- // XXX Lets use go-crypto/bcrypt and ascii encoding directly in here without
- // further wrappers around a store or DB.
- // Copy functions from: https://github.com/tendermint/mintkey/blob/master/cmd/mintkey/common.go
- //
- // dbKeybase combines encyption and storage implementation to provide
- // a full-featured key manager
- type dbKeybase struct {
- db dbm.DB
- codec Codec
- }
-
- func New(db dbm.DB, codec Codec) dbKeybase {
- return dbKeybase{
- db: db,
- codec: codec,
- }
- }
-
- var _ Keybase = dbKeybase{}
-
- // Create adds a new key to the storage engine, returning error if
- // another key already stored under this name
- //
- // algo must be a supported go-crypto algorithm: ed25519, secp256k1
- func (kb dbKeybase) Create(name, passphrase, algo string) (Info, string, error) {
- // 128-bits are the all the randomness we can make use of
- secret := crypto.CRandBytes(16)
- key, err := generate(algo, secret)
- if err != nil {
- return Info{}, "", err
- }
-
- public := kb.writeKey(key, name, passphrase)
-
- // we append the type byte to the serialized secret to help with recovery
- // ie [secret] = [secret] + [type]
- typ := key.Bytes()[0]
- secret = append(secret, typ)
-
- seed, err := kb.codec.BytesToWords(secret)
- phrase := strings.Join(seed, " ")
- return public, phrase, err
- }
-
- // Recover takes a seed phrase and tries to recover the private key.
- //
- // If the seed phrase is valid, it will create the private key and store
- // it under name, protected by passphrase.
- //
- // Result similar to New(), except it doesn't return the seed again...
- func (kb dbKeybase) Recover(name, passphrase, seedphrase string) (Info, error) {
- words := strings.Split(strings.TrimSpace(seedphrase), " ")
- secret, err := kb.codec.WordsToBytes(words)
- if err != nil {
- return Info{}, err
- }
-
- // secret is comprised of the actual secret with the type appended
- // ie [secret] = [secret] + [type]
- l := len(secret)
- secret, typ := secret[:l-1], secret[l-1]
-
- key, err := generateByType(typ, secret)
- if err != nil {
- return Info{}, err
- }
-
- // d00d, it worked! create the bugger....
- public := kb.writeKey(key, name, passphrase)
- return public, err
- }
-
- // List loads the keys from the storage and enforces alphabetical order
- func (kb dbKeybase) List() ([]Info, error) {
- var res []Info
- for iter := kb.db.Iterator(); iter.Valid(); iter.Next() {
- key := iter.Key()
- if isPub(key) {
- info, err := readInfo(iter.Value())
- if err != nil {
- return nil, err
- }
- res = append(res, info)
- }
- }
- return res, nil
- }
-
- // Get returns the public information about one key
- func (kb dbKeybase) Get(name string) (Info, error) {
- bs := kb.db.Get(pubName(name))
- return readInfo(bs)
- }
-
- // Sign will modify the Signable in order to attach a valid signature with
- // this public key
- //
- // If no key for this name, or the passphrase doesn't match, returns an error
- func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pk crypto.PubKey, err error) {
- var key crypto.PrivKey
- bs := kb.db.Get(privName(name))
- key, err = unarmorDecryptPrivKey(string(bs), passphrase)
- if err != nil {
- return
- }
-
- sig = key.Sign(msg)
- pk = key.PubKey()
- return
- }
-
- // Export decodes the private key with the current password, encodes
- // it with a secure one-time password and generates a sequence that can be
- // Imported by another dbKeybase
- //
- // This is designed to copy from one device to another, or provide backups
- // during version updates.
- func (kb dbKeybase) Export(name, oldpass, transferpass string) ([]byte, error) {
- bs := kb.db.Get(privName(name))
- key, err := unarmorDecryptPrivKey(string(bs), oldpass)
- if err != nil {
- return nil, err
- }
-
- if transferpass == "" {
- return key.Bytes(), nil
- }
- res := encryptArmorPrivKey(key, transferpass)
- return []byte(res), nil
- }
-
- // Import accepts bytes generated by Export along with the same transferpass
- // If they are valid, it stores the password under the given name with the
- // new passphrase.
- func (kb dbKeybase) Import(name, newpass, transferpass string, data []byte) (err error) {
- var key crypto.PrivKey
- if transferpass == "" {
- key, err = crypto.PrivKeyFromBytes(data)
- } else {
- key, err = unarmorDecryptPrivKey(string(data), transferpass)
- }
- if err != nil {
- return err
- }
-
- kb.writeKey(key, name, newpass)
- return nil
- }
-
- // Delete removes key forever, but we must present the
- // proper passphrase before deleting it (for security)
- func (kb dbKeybase) Delete(name, passphrase string) error {
- // verify we have the proper password before deleting
- bs := kb.db.Get(privName(name))
- _, err := unarmorDecryptPrivKey(string(bs), passphrase)
- if err != nil {
- return err
- }
- kb.db.DeleteSync(pubName(name))
- kb.db.DeleteSync(privName(name))
- return nil
- }
-
- // Update changes the passphrase with which a already stored key is encoded.
- //
- // oldpass must be the current passphrase used for encoding, newpass will be
- // the only valid passphrase from this time forward
- func (kb dbKeybase) Update(name, oldpass, newpass string) error {
- bs := kb.db.Get(privName(name))
- key, err := unarmorDecryptPrivKey(string(bs), oldpass)
- if err != nil {
- return err
- }
-
- // we must delete first, as Putting over an existing name returns an error
- kb.db.DeleteSync(pubName(name))
- kb.db.DeleteSync(privName(name))
- kb.writeKey(key, name, newpass)
- return nil
- }
-
- func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
- // generate the public bytes
- public := info(name, priv)
- // generate the encrypted privkey
- private := encryptArmorPrivKey(priv, passphrase)
-
- // write them both
- kb.db.SetSync(pubName(name), public.bytes())
- kb.db.SetSync(privName(name), []byte(private))
-
- return public
- }
-
- func generate(algo string, secret []byte) (crypto.PrivKey, error) {
- switch algo {
- case crypto.NameEd25519:
- return crypto.GenPrivKeyEd25519FromSecret(secret).Wrap(), nil
- case crypto.NameSecp256k1:
- return crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap(), nil
- case nano.NameLedgerEd25519:
- return nano.NewPrivKeyLedgerEd25519Ed25519()
- default:
- err := errors.Errorf("Cannot generate keys for algorithm: %s", algo)
- return crypto.PrivKey{}, err
- }
- }
-
- func generateByType(typ byte, secret []byte) (crypto.PrivKey, error) {
- switch typ {
- case crypto.TypeEd25519:
- return generate(crypto.NameEd25519, secret)
- case crypto.TypeSecp256k1:
- return generate(crypto.NameSecp256k1, secret)
- case nano.TypeLedgerEd25519:
- return generate(nano.NameLedgerEd25519, secret)
- default:
- err := errors.Errorf("Cannot generate keys for algorithm: %X", typ)
- return crypto.PrivKey{}, err
- }
- }
-
- func pubName(name string) []byte {
- return []byte(fmt.Sprintf("%s.pub", name))
- }
-
- func privName(name string) []byte {
- return []byte(fmt.Sprintf("%s.priv", name))
- }
-
- func isPub(name []byte) bool {
- return strings.HasSuffix(string(name), ".pub")
- }
|