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/keys/words" "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 words.Codec } func New(db dbm.DB, codec words.Codec) dbKeybase { return dbKeybase{ db: db, codec: codec, } } var _ Keybase = dbKeybase{} // Create generates a new key and persists it storage, encrypted using the passphrase. // It returns the generated seedphrase (mnemonic) and the key Info. // It returns an error 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 dbKeybase) Create(name, passphrase, algo string) (string, Info, error) { // NOTE: secret is SHA256 hashed by secp256k1 and ed25519. // 16 byte secret corresponds to 12 BIP39 words. // XXX: Ledgers use 24 words now - should we ? secret := crypto.CRandBytes(16) key, err := generate(algo, secret) if err != nil { return "", Info{}, err } // encrypt and persist the key public := kb.writeKey(key, name, passphrase) // return the mnemonic phrase words, err := kb.codec.BytesToWords(secret) seedphrase := strings.Join(words, " ") return seedphrase, public, err } // Recover converts a seedphrase to a private key and persists it, encrypted with the given passphrase. // Functions like Create, but seedphrase is input not output. func (kb dbKeybase) Recover(name, passphrase, algo string, seedphrase string) (Info, error) { key, err := kb.SeedToPrivKey(algo, seedphrase) if err != nil { return Info{}, err } // Valid seedphrase. Encrypt key and persist to disk. public := kb.writeKey(key, name, passphrase) return public, nil } // SeedToPrivKey returns the private key corresponding to a seedphrase // without persisting the private key. // TODO: enable the keybase to just hold these in memory so we can sign without persisting (?) func (kb dbKeybase) SeedToPrivKey(algo, seedphrase string) (crypto.PrivKey, error) { words := strings.Split(strings.TrimSpace(seedphrase), " ") secret, err := kb.codec.WordsToBytes(words) if err != nil { return crypto.PrivKey{}, err } key, err := generate(algo, secret) if err != nil { return crypto.PrivKey{}, err } return key, nil } // List returns the keys from storage in alphabetical order. func (kb dbKeybase) List() ([]Info, error) { var res []Info iter := kb.db.Iterator(nil, nil) defer iter.Close() for ; 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 signs the msg with the named key. // It returns an error if the key doesn't exist or the decryption fails. // TODO: what if leddger fails ? func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pk crypto.PubKey, err error) { var key crypto.PrivKey armorStr := kb.db.Get(privName(name)) key, err = unarmorDecryptPrivKey(string(armorStr), passphrase) if err != nil { return } sig = key.Sign(msg) pk = key.PubKey() return } // Export decodes the private key with the current password, encrypts // it with a secure one-time password and generates an armored private key // 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) { armorStr := kb.db.Get(privName(name)) key, err := unarmorDecryptPrivKey(string(armorStr), oldpass) if err != nil { return nil, err } if transferpass == "" { return key.Bytes(), nil } armorBytes := encryptArmorPrivKey(key, transferpass) return []byte(armorBytes), 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 an already stored key is encrypted. // // oldpass must be the current passphrase used for encryption, 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 } // Generate the public bytes and the encrypted privkey public := info(name, key) private := encryptArmorPrivKey(key, newpass) // We must delete first, as Putting over an existing name returns an error. // Must be done atomically with the write or we could lose the key. batch := kb.db.NewBatch() batch.Delete(pubName(name)) batch.Delete(privName(name)) batch.Set(pubName(name), public.bytes()) batch.Set(privName(name), []byte(private)) batch.Write() return nil } //--------------------------------------------------------------------------------------- func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info { // Generate the public bytes and the encrypted privkey public := info(name, priv) private := encryptArmorPrivKey(priv, passphrase) // Write them both kb.db.SetSync(pubName(name), public.bytes()) kb.db.SetSync(privName(name), []byte(private)) return public } // TODO: use a `type TypeKeyAlgo string` (?) 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.NewPrivKeyLedgerEd25519() default: err := errors.Errorf("Cannot generate keys for algorithm: %s", algo) 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") }