2017-11-02 14:09:59 -05:00
|
|
|
package keys
|
2017-02-28 18:07:59 +01:00
|
|
|
|
2017-06-20 18:35:16 +02:00
|
|
|
import (
|
2017-11-02 18:32:12 -05:00
|
|
|
"fmt"
|
2017-06-20 18:35:16 +02:00
|
|
|
"strings"
|
|
|
|
|
2017-11-02 16:31:29 -05:00
|
|
|
"github.com/pkg/errors"
|
2017-06-20 18:35:16 +02:00
|
|
|
crypto "github.com/tendermint/go-crypto"
|
2017-11-01 16:38:11 -05:00
|
|
|
dbm "github.com/tendermint/tmlibs/db"
|
2017-11-02 16:31:29 -05:00
|
|
|
|
2017-12-30 16:46:58 -05:00
|
|
|
"github.com/tendermint/go-crypto/keys/words"
|
2017-11-02 16:31:29 -05:00
|
|
|
"github.com/tendermint/go-crypto/nano"
|
2017-06-20 18:35:16 +02:00
|
|
|
)
|
2017-02-28 18:07:59 +01:00
|
|
|
|
2017-11-01 16:38:11 -05:00
|
|
|
// 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
|
2017-02-28 18:07:59 +01:00
|
|
|
// a full-featured key manager
|
2017-11-01 16:38:11 -05:00
|
|
|
type dbKeybase struct {
|
|
|
|
db dbm.DB
|
2017-12-30 16:46:58 -05:00
|
|
|
codec words.Codec
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 16:46:58 -05:00
|
|
|
func New(db dbm.DB, codec words.Codec) dbKeybase {
|
2017-11-01 16:38:11 -05:00
|
|
|
return dbKeybase{
|
2017-11-02 16:46:10 -05:00
|
|
|
db: db,
|
2017-06-20 18:35:16 +02:00
|
|
|
codec: codec,
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 16:38:11 -05:00
|
|
|
var _ Keybase = dbKeybase{}
|
2017-10-04 18:16:48 -04:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// 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 ?
|
2017-10-23 16:35:26 +02:00
|
|
|
secret := crypto.CRandBytes(16)
|
2017-11-02 16:31:29 -05:00
|
|
|
key, err := generate(algo, secret)
|
2017-02-28 19:43:18 +01:00
|
|
|
if err != nil {
|
2017-12-30 17:02:18 -05:00
|
|
|
return "", Info{}, err
|
2017-02-28 19:43:18 +01:00
|
|
|
}
|
2017-07-22 05:44:09 -04:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// encrypt and persist the key
|
2017-11-02 18:32:12 -05:00
|
|
|
public := kb.writeKey(key, name, passphrase)
|
2017-07-22 05:44:09 -04:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// return the mnemonic phrase
|
|
|
|
words, err := kb.codec.BytesToWords(secret)
|
|
|
|
seedphrase := strings.Join(words, " ")
|
|
|
|
return seedphrase, public, err
|
2017-06-20 18:15:49 +02:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// 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)
|
2017-06-20 18:35:16 +02:00
|
|
|
if err != nil {
|
2017-11-01 16:38:11 -05:00
|
|
|
return Info{}, err
|
2017-06-20 18:35:16 +02:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// Valid seedphrase. Encrypt key and persist to disk.
|
|
|
|
public := kb.writeKey(key, name, passphrase)
|
|
|
|
return public, nil
|
|
|
|
}
|
2017-07-22 05:53:46 -04:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// 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)
|
2017-07-22 05:53:46 -04:00
|
|
|
if err != nil {
|
2017-12-30 17:02:18 -05:00
|
|
|
return crypto.PrivKey{}, err
|
2017-07-22 05:53:46 -04:00
|
|
|
}
|
2017-06-20 18:35:16 +02:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
key, err := generate(algo, secret)
|
|
|
|
if err != nil {
|
|
|
|
return crypto.PrivKey{}, err
|
|
|
|
}
|
|
|
|
return key, nil
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// List returns the keys from storage in alphabetical order.
|
2017-11-02 16:46:10 -05:00
|
|
|
func (kb dbKeybase) List() ([]Info, error) {
|
2017-11-02 18:32:12 -05:00
|
|
|
var res []Info
|
2017-12-27 14:37:37 -08:00
|
|
|
iter := kb.db.Iterator(nil, nil)
|
|
|
|
defer iter.Close()
|
|
|
|
for ; iter.Valid(); iter.Next() {
|
2017-11-02 18:32:12 -05:00
|
|
|
key := iter.Key()
|
|
|
|
if isPub(key) {
|
|
|
|
info, err := readInfo(iter.Value())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res = append(res, info)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res, nil
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// Get returns the public information about one key.
|
2017-11-02 16:46:10 -05:00
|
|
|
func (kb dbKeybase) Get(name string) (Info, error) {
|
2017-11-02 18:32:12 -05:00
|
|
|
bs := kb.db.Get(pubName(name))
|
|
|
|
return readInfo(bs)
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// 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 ?
|
2017-11-02 16:46:10 -05:00
|
|
|
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pk crypto.PubKey, err error) {
|
|
|
|
var key crypto.PrivKey
|
2017-12-30 17:02:18 -05:00
|
|
|
armorStr := kb.db.Get(privName(name))
|
|
|
|
key, err = unarmorDecryptPrivKey(string(armorStr), passphrase)
|
2017-02-28 18:07:59 +01:00
|
|
|
if err != nil {
|
2017-11-02 16:46:10 -05:00
|
|
|
return
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
2017-11-02 18:32:12 -05:00
|
|
|
|
2017-11-02 16:46:10 -05:00
|
|
|
sig = key.Sign(msg)
|
|
|
|
pk = key.PubKey()
|
|
|
|
return
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// 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.
|
2017-02-28 18:07:59 +01:00
|
|
|
//
|
|
|
|
// This is designed to copy from one device to another, or provide backups
|
|
|
|
// during version updates.
|
2017-11-02 16:46:10 -05:00
|
|
|
func (kb dbKeybase) Export(name, oldpass, transferpass string) ([]byte, error) {
|
2017-12-30 17:02:18 -05:00
|
|
|
armorStr := kb.db.Get(privName(name))
|
|
|
|
key, err := unarmorDecryptPrivKey(string(armorStr), oldpass)
|
2017-02-28 18:07:59 +01:00
|
|
|
if err != nil {
|
2017-10-27 22:02:44 -04:00
|
|
|
return nil, err
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-11-02 18:32:12 -05:00
|
|
|
if transferpass == "" {
|
|
|
|
return key.Bytes(), nil
|
|
|
|
}
|
2017-12-30 17:02:18 -05:00
|
|
|
armorBytes := encryptArmorPrivKey(key, transferpass)
|
|
|
|
return []byte(armorBytes), nil
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// Import accepts bytes generated by Export along with the same transferpass.
|
2017-10-27 22:02:44 -04:00
|
|
|
// If they are valid, it stores the password under the given name with the
|
2017-02-28 18:07:59 +01:00
|
|
|
// new passphrase.
|
2017-11-02 18:32:12 -05:00
|
|
|
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)
|
|
|
|
}
|
2017-02-28 18:07:59 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-02 18:32:12 -05:00
|
|
|
kb.writeKey(key, name, newpass)
|
|
|
|
return nil
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete removes key forever, but we must present the
|
2017-12-30 17:02:18 -05:00
|
|
|
// proper passphrase before deleting it (for security).
|
2017-11-02 16:46:10 -05:00
|
|
|
func (kb dbKeybase) Delete(name, passphrase string) error {
|
2017-02-28 18:07:59 +01:00
|
|
|
// verify we have the proper password before deleting
|
2017-11-02 18:32:12 -05:00
|
|
|
bs := kb.db.Get(privName(name))
|
|
|
|
_, err := unarmorDecryptPrivKey(string(bs), passphrase)
|
2017-02-28 18:07:59 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-11-02 18:32:12 -05:00
|
|
|
kb.db.DeleteSync(pubName(name))
|
|
|
|
kb.db.DeleteSync(privName(name))
|
|
|
|
return nil
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// Update changes the passphrase with which an already stored key is encrypted.
|
2017-02-28 18:07:59 +01:00
|
|
|
//
|
2017-12-30 17:02:18 -05:00
|
|
|
// oldpass must be the current passphrase used for encryption, newpass will be
|
|
|
|
// the only valid passphrase from this time forward.
|
2017-11-02 16:46:10 -05:00
|
|
|
func (kb dbKeybase) Update(name, oldpass, newpass string) error {
|
2017-11-02 18:32:12 -05:00
|
|
|
bs := kb.db.Get(privName(name))
|
|
|
|
key, err := unarmorDecryptPrivKey(string(bs), oldpass)
|
2017-02-28 18:07:59 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// 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()
|
|
|
|
|
2017-11-02 18:32:12 -05:00
|
|
|
return nil
|
|
|
|
}
|
2017-02-28 18:07:59 +01:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
//---------------------------------------------------------------------------------------
|
|
|
|
|
2017-11-02 18:32:12 -05:00
|
|
|
func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
|
2017-12-30 17:02:18 -05:00
|
|
|
// Generate the public bytes and the encrypted privkey
|
2017-11-02 18:32:12 -05:00
|
|
|
public := info(name, priv)
|
|
|
|
private := encryptArmorPrivKey(priv, passphrase)
|
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// Write them both
|
2017-11-02 18:32:12 -05:00
|
|
|
kb.db.SetSync(pubName(name), public.bytes())
|
|
|
|
kb.db.SetSync(privName(name), []byte(private))
|
|
|
|
|
|
|
|
return public
|
2017-02-28 18:07:59 +01:00
|
|
|
}
|
2017-11-02 16:31:29 -05:00
|
|
|
|
2017-12-30 17:02:18 -05:00
|
|
|
// TODO: use a `type TypeKeyAlgo string` (?)
|
2017-11-02 16:31:29 -05:00
|
|
|
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:
|
2017-12-30 16:46:58 -05:00
|
|
|
return nano.NewPrivKeyLedgerEd25519()
|
2017-11-02 16:31:29 -05:00
|
|
|
default:
|
|
|
|
err := errors.Errorf("Cannot generate keys for algorithm: %s", algo)
|
|
|
|
return crypto.PrivKey{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-02 18:32:12 -05:00
|
|
|
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")
|
2017-11-02 16:46:10 -05:00
|
|
|
}
|