From 6c6d01b51c56f8b155cf9712e79de8fb12a82803 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 2 Mar 2018 03:08:48 -0500 Subject: [PATCH] Keybase refactor Same as 788cc0a79256d4fe0448e17a90217acf3d61346b but without the new go-wire --- {nano => _nano}/keys.go | 0 {nano => _nano}/keys_test.go | 0 {nano => _nano}/sign.go | 0 {nano => _nano}/sign_test.go | 0 glide.lock | 32 +++-- glide.yaml | 4 +- keys/keybase.go | 221 +++++++++++++------------------- keys/keybase_test.go | 242 ++++++++++++++++++----------------- keys/keys.go | 32 +++++ keys/mintkey.go | 26 ++++ keys/types.go | 64 +++++---- 11 files changed, 325 insertions(+), 296 deletions(-) rename {nano => _nano}/keys.go (100%) rename {nano => _nano}/keys_test.go (100%) rename {nano => _nano}/sign.go (100%) rename {nano => _nano}/sign_test.go (100%) create mode 100644 keys/keys.go diff --git a/nano/keys.go b/_nano/keys.go similarity index 100% rename from nano/keys.go rename to _nano/keys.go diff --git a/nano/keys_test.go b/_nano/keys_test.go similarity index 100% rename from nano/keys_test.go rename to _nano/keys_test.go diff --git a/nano/sign.go b/_nano/sign.go similarity index 100% rename from nano/sign.go rename to _nano/sign.go diff --git a/nano/sign_test.go b/_nano/sign_test.go similarity index 100% rename from nano/sign_test.go rename to _nano/sign_test.go diff --git a/glide.lock b/glide.lock index 5032685c2..add082c5b 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,8 @@ -hash: a21061afc44c148eb6bfeb91478b520661f3d086234383a0208d915b0cb058b8 -updated: 2017-12-27T14:29:49.534901894-08:00 +hash: 575a7b42282ded36e66490c44105140387d305b07e8e5a9e8e4b9eeb6f995e66 +updated: 2018-03-02T03:06:22.122862726-05:00 imports: - name: github.com/btcsuite/btcd - version: c7588cbf7690cd9f047a28efa2dcd8f2435a4e5e + version: 50de9da05b50eb15658bb350f6ea24368a111ab7 subpackages: - btcec - name: github.com/btcsuite/btcutil @@ -10,11 +10,9 @@ imports: subpackages: - base58 - name: github.com/ethanfrey/ledger - version: 5e432577be582bd18a3b4a9cd75dae7a317ade36 -- name: github.com/flynn/hid - version: ed06a31c6245d4552e8dbba7e32e5b010b875d65 + version: 23a7bb9d74bc83a862fcb4bddde24215b2295ad9 - name: github.com/go-kit/kit - version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c + version: 4dc7be5d2d12881735283bcab7352178e190fc71 subpackages: - log - log/level @@ -22,9 +20,9 @@ imports: - name: github.com/go-logfmt/logfmt version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 - name: github.com/go-stack/stack - version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf + version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc - name: github.com/gogo/protobuf - version: 342cbe0a04158f6dcb03ca0079991a51a4248c02 + version: 1adfc126b41513cc696b209667c8656ea7aac67c subpackages: - gogoproto - proto @@ -40,7 +38,7 @@ imports: - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/syndtr/goleveldb - version: b89cc31ef7977104127d34c1bd31ebd1a9db2199 + version: 34011bf325bce385408353a30b101fe5e923eb6e subpackages: - leveldb - leveldb/cache @@ -55,23 +53,23 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/ed25519 - version: 1f52c6f8b8a5c7908aff4497c186af344b428925 + version: d8387025d2b9d158cf4efb07e7ebf814bcce2057 subpackages: - edwards25519 - extra25519 - name: github.com/tendermint/go-wire - version: 27be46e25124ddf775e23317a83647ce62a93f6b + version: 67ee274c5f9da166622f3b6e6747003b563e3742 subpackages: - data - data/base58 - name: github.com/tendermint/tmlibs - version: 93c05aa8c06ef38f2b15fcdd1d91eafefda2732d + version: 1b9b5652a199ab0be2e781393fb275b66377309d subpackages: - common - db - log - name: golang.org/x/crypto - version: edd5e9b0879d13ee6970a50153d85b8fec9f7686 + version: 1875d0a70c90e57f11972aefd42276df65e895b9 subpackages: - bcrypt - blowfish @@ -84,17 +82,17 @@ imports: - salsa20/salsa testImports: - name: github.com/davecgh/go-spew - version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 + version: 346938d642f2ec3594ed81d874461961cd0faa76 subpackages: - spew - name: github.com/mndrix/btcutil version: d3a63a5752ecf3fbc06bd97365da752111c263df - name: github.com/pmezard/go-difflib - version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + version: 792786c7400a136282c1664665ae0a8db921c6c2 subpackages: - difflib - name: github.com/stretchr/testify - version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 + version: 12b6f73e6084dad08a7c6e575284b177ecafbc71 subpackages: - assert - require diff --git a/glide.yaml b/glide.yaml index f9df5c18d..c5ac4992b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -17,9 +17,9 @@ import: subpackages: - extra25519 - package: github.com/tendermint/tmlibs - version: sdk2 + version: master - package: github.com/tendermint/go-wire - version: develop + version: bucky/new-go-wire-api subpackages: - data - data/base58 diff --git a/keys/keybase.go b/keys/keybase.go index c72518748..2c1d64a27 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -6,16 +6,10 @@ import ( "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" + dbm "github.com/tendermint/tmlibs/db" ) -// 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 { @@ -32,58 +26,59 @@ func New(db dbm.DB, codec words.Codec) dbKeybase { 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) { +// Create generates a new key and persists it to 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 string, algo CryptoAlgo) (Info, string, 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) + priv, err := generate(algo, secret) if err != nil { - return "", Info{}, err + return Info{}, "", err } // encrypt and persist the key - public := kb.writeKey(key, name, passphrase) + info := kb.writeKey(priv, name, passphrase) + + // we append the type byte to the serialized secret to help with + // recovery + // ie [secret] = [type] + [secret] + typ := cryptoAlgoToByte(algo) + secret = append([]byte{typ}, secret...) // 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 + seed := strings.Join(words, " ") + return info, seed, err } -// 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) { +// 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, seedphrase string) (Info, error) { words := strings.Split(strings.TrimSpace(seedphrase), " ") secret, err := kb.codec.WordsToBytes(words) if err != nil { - return crypto.PrivKey{}, err + return Info{}, err } - key, err := generate(algo, secret) + // secret is comprised of the actual secret with the type + // appended. + // ie [secret] = [type] + [secret] + typ, secret := secret[0], secret[1:] + algo := byteToCryptoAlgo(typ) + priv, err := generate(algo, secret) if err != nil { - return crypto.PrivKey{}, err + return Info{}, err } - return key, nil + + // encrypt and persist key. + public := kb.writeKey(priv, name, passphrase) + return public, err } // List returns the keys from storage in alphabetical order. @@ -92,75 +87,56 @@ func (kb dbKeybase) List() ([]Info, error) { 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) + // key := iter.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)) + bs := kb.db.Get(infoKey(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) +func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pub crypto.PubKey, err error) { + info, err := kb.Get(name) 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) + priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase) if err != nil { - return nil, err + return } + sig = priv.Sign(msg) + pub = priv.PubKey() + return +} - if transferpass == "" { - return key.Bytes(), nil +func (kb dbKeybase) Export(name string) (armor string, err error) { + bz := kb.db.Get(infoKey(name)) + if bz == nil { + return "", errors.New("No key to export with name " + name) } - armorBytes := encryptArmorPrivKey(key, transferpass) - return []byte(armorBytes), nil + return armorInfoBytes(bz), 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) +func (kb dbKeybase) Import(name string, armor string) (err error) { + bz := kb.db.Get(infoKey(name)) + if len(bz) > 0 { + return errors.New("Cannot overwrite data for name " + name) } + infoBytes, err := unarmorInfoBytes(armor) if err != nil { - return err + return } - - kb.writeKey(key, name, newpass) + kb.db.Set(infoKey(name), infoBytes) return nil } @@ -168,80 +144,59 @@ func (kb dbKeybase) Import(name, newpass, transferpass string, data []byte) (err // 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) + info, err := kb.Get(name) if err != nil { return err } - kb.db.DeleteSync(pubName(name)) - kb.db.DeleteSync(privName(name)) + _, err = unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase) + if err != nil { + return err + } + kb.db.DeleteSync(infoKey(name)) return nil } -// Update changes the passphrase with which an already stored key is encrypted. +// 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. +// 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) + info, err := kb.Get(name) + if err != nil { + return err + } + key, err := unarmorDecryptPrivKey(info.PrivKeyArmor, 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() - + kb.writeKey(key, name, newpass) 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) + // generate the encrypted privkey + privArmor := encryptArmorPrivKey(priv, passphrase) + // make Info + info := newInfo(name, priv.PubKey(), privArmor) - // Write them both - kb.db.SetSync(pubName(name), public.bytes()) - kb.db.SetSync(privName(name), []byte(private)) - - return public + // write them both + kb.db.SetSync(infoKey(name), info.bytes()) + return info } -// TODO: use a `type TypeKeyAlgo string` (?) -func generate(algo string, secret []byte) (crypto.PrivKey, error) { +func generate(algo CryptoAlgo, secret []byte) (crypto.PrivKey, error) { switch algo { - case crypto.NameEd25519: + case AlgoEd25519: return crypto.GenPrivKeyEd25519FromSecret(secret).Wrap(), nil - case crypto.NameSecp256k1: + case AlgoSecp256k1: 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") +func infoKey(name string) []byte { + return []byte(fmt.Sprintf("%s.info", name)) } diff --git a/keys/keybase_test.go b/keys/keybase_test.go index 12e5205b0..84c81c81f 100644 --- a/keys/keybase_test.go +++ b/keys/keybase_test.go @@ -2,24 +2,20 @@ package keys_test import ( "fmt" - "os" "testing" - asrt "github.com/stretchr/testify/assert" - rqr "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" - crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto/keys" "github.com/tendermint/go-crypto/keys/words" - "github.com/tendermint/go-crypto/nano" ) // TestKeyManagement makes sure we can manipulate these keys well func TestKeyManagement(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) // make the storage with reasonable defaults cstore := keys.New( @@ -27,84 +23,83 @@ func TestKeyManagement(t *testing.T) { words.MustLoadCodec("english"), ) - algo := crypto.NameEd25519 + algo := keys.AlgoEd25519 n1, n2, n3 := "personal", "business", "other" p1, p2 := "1234", "really-secure!@#$" // Check empty state l, err := cstore.List() - require.Nil(err) - assert.Empty(l) + require.Nil(t, err) + assert.Empty(t, l) // create some keys _, err = cstore.Get(n1) - assert.NotNil(err) - _, i, err := cstore.Create(n1, p1, algo) - require.Equal(n1, i.Name) - require.Nil(err) + assert.NotNil(t, err) + i, _, err := cstore.Create(n1, p1, algo) + require.Equal(t, n1, i.Name) + require.Nil(t, err) _, _, err = cstore.Create(n2, p2, algo) - require.Nil(err) + require.Nil(t, err) // we can get these keys i2, err := cstore.Get(n2) - assert.Nil(err) + assert.Nil(t, err) _, err = cstore.Get(n3) - assert.NotNil(err) + assert.NotNil(t, err) // list shows them in order keyS, err := cstore.List() - require.Nil(err) - require.Equal(2, len(keyS)) + require.Nil(t, err) + require.Equal(t, 2, len(keyS)) // note these are in alphabetical order - assert.Equal(n2, keyS[0].Name) - assert.Equal(n1, keyS[1].Name) - assert.Equal(i2.PubKey, keyS[0].PubKey) + assert.Equal(t, n2, keyS[0].Name) + assert.Equal(t, n1, keyS[1].Name) + assert.Equal(t, i2.PubKey, keyS[0].PubKey) // deleting a key removes it err = cstore.Delete("bad name", "foo") - require.NotNil(err) + require.NotNil(t, err) err = cstore.Delete(n1, p1) - require.Nil(err) + require.Nil(t, err) keyS, err = cstore.List() - require.Nil(err) - assert.Equal(1, len(keyS)) + require.Nil(t, err) + assert.Equal(t, 1, len(keyS)) _, err = cstore.Get(n1) - assert.NotNil(err) + assert.NotNil(t, err) // make sure that it only signs with the right password // tx := mock.NewSig([]byte("mytransactiondata")) // err = cstore.Sign(n2, p1, tx) - // assert.NotNil(err) + // assert.NotNil(t, err) // err = cstore.Sign(n2, p2, tx) - // assert.Nil(err, "%+v", err) + // assert.Nil(t, err, "%+v", err) // sigs, err := tx.Signers() - // assert.Nil(err, "%+v", err) - // if assert.Equal(1, len(sigs)) { - // assert.Equal(i2.PubKey, sigs[0]) + // assert.Nil(t, err, "%+v", err) + // if assert.Equal(t, 1, len(sigs)) { + // assert.Equal(t, i2.PubKey, sigs[0]) // } } // TestSignVerify does some detailed checks on how we sign and validate // signatures func TestSignVerify(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) // make the storage with reasonable defaults cstore := keys.New( dbm.NewMemDB(), words.MustLoadCodec("english"), ) - algo := crypto.NameSecp256k1 + algo := keys.AlgoSecp256k1 n1, n2 := "some dude", "a dudette" p1, p2 := "1234", "foobar" // create two users and get their info - _, i1, err := cstore.Create(n1, p1, algo) - require.Nil(err) + i1, _, err := cstore.Create(n1, p1, algo) + require.Nil(t, err) - _, i2, err := cstore.Create(n2, p2, algo) - require.Nil(err) + i2, _, err := cstore.Create(n2, p2, algo) + require.Nil(t, err) // let's try to sign some messages d1 := []byte("my first message") @@ -112,20 +107,20 @@ func TestSignVerify(t *testing.T) { // try signing both data with both keys... s11, pub1, err := cstore.Sign(n1, p1, d1) - require.Nil(err) - require.Equal(i1.PubKey, pub1) + require.Nil(t, err) + require.Equal(t, i1.PubKey, pub1) s12, pub1, err := cstore.Sign(n1, p1, d2) - require.Nil(err) - require.Equal(i1.PubKey, pub1) + require.Nil(t, err) + require.Equal(t, i1.PubKey, pub1) s21, pub2, err := cstore.Sign(n2, p2, d1) - require.Nil(err) - require.Equal(i2.PubKey, pub2) + require.Nil(t, err) + require.Equal(t, i2.PubKey, pub2) s22, pub2, err := cstore.Sign(n2, p2, d2) - require.Nil(err) - require.Equal(i2.PubKey, pub2) + require.Nil(t, err) + require.Equal(t, i2.PubKey, pub2) // let's try to validate and make sure it only works when everything is proper cases := []struct { @@ -148,17 +143,17 @@ func TestSignVerify(t *testing.T) { for i, tc := range cases { valid := tc.key.VerifyBytes(tc.data, tc.sig) - assert.Equal(tc.valid, valid, "%d", i) + assert.Equal(t, tc.valid, valid, "%d", i) } } +/* // TestSignWithLedger makes sure we have ledger compatibility with // the crypto store. // // This test will only succeed with a ledger attached to the computer // and the cosmos app open func TestSignWithLedger(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) if os.Getenv("WITH_LEDGER") == "" { t.Skip("Set WITH_LEDGER to run code on real ledger") } @@ -172,19 +167,19 @@ func TestSignWithLedger(t *testing.T) { p := "hard2hack" // create a nano user - _, c, err := cstore.Create(n, p, nano.NameLedgerEd25519) - require.Nil(err, "%+v", err) - assert.Equal(c.Name, n) + c, _, err := cstore.Create(n, p, nano.KeyLedgerEd25519) + require.Nil(t, err, "%+v", err) + assert.Equal(t, c.Key, n) _, ok := c.PubKey.Unwrap().(nano.PubKeyLedgerEd25519) - require.True(ok) + require.True(t, ok) // make sure we can get it back info, err := cstore.Get(n) - require.Nil(err, "%+v", err) - assert.Equal(info.Name, n) + require.Nil(t, err, "%+v", err) + assert.Equal(t, info.Key, n) key := info.PubKey - require.False(key.Empty()) - require.True(key.Equals(c.PubKey)) + require.False(t ,key.Empty()) + require.True(t, key.Equals(c.PubKey)) // let's try to sign some messages d1 := []byte("welcome to cosmos") @@ -192,56 +187,64 @@ func TestSignWithLedger(t *testing.T) { // try signing both data with the ledger... s1, pub, err := cstore.Sign(n, p, d1) - require.Nil(err) - require.Equal(info.PubKey, pub) + require.Nil(t, err) + require.Equal(t, info.PubKey, pub) s2, pub, err := cstore.Sign(n, p, d2) - require.Nil(err) - require.Equal(info.PubKey, pub) + require.Nil(t, err) + require.Equal(t, info.PubKey, pub) // now, let's check those signatures work - assert.True(key.VerifyBytes(d1, s1)) - assert.True(key.VerifyBytes(d2, s2)) + assert.True(t, key.VerifyBytes(d1, s1)) + assert.True(t, key.VerifyBytes(d2, s2)) // and mismatched signatures don't - assert.False(key.VerifyBytes(d1, s2)) + assert.False(t, key.VerifyBytes(d1, s2)) } +*/ -func assertPassword(assert *asrt.Assertions, cstore keys.Keybase, name, pass, badpass string) { +func assertPassword(t *testing.T, cstore keys.Keybase, name, pass, badpass string) { err := cstore.Update(name, badpass, pass) - assert.NotNil(err) + assert.NotNil(t, err) err = cstore.Update(name, pass, pass) - assert.Nil(err, "%+v", err) + assert.Nil(t, err, "%+v", err) } -// TestImportUnencrypted tests accepting raw priv keys bytes as input -func TestImportUnencrypted(t *testing.T) { - require := rqr.New(t) +// TestExportImport tests exporting and importing keys. +func TestExportImport(t *testing.T) { // make the storage with reasonable defaults + db := dbm.NewMemDB() cstore := keys.New( - dbm.NewMemDB(), + db, words.MustLoadCodec("english"), ) - key := crypto.GenPrivKeyEd25519FromSecret(cmn.RandBytes(16)).Wrap() + info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519) + assert.Nil(t, err) + assert.Equal(t, info.Name, "john") + addr := info.PubKey.Address() + + john, err := cstore.Get("john") + assert.Nil(t, err) + assert.Equal(t, john.Name, "john") + assert.Equal(t, john.PubKey.Address(), addr) - addr := key.PubKey().Address() - name := "john" - pass := "top-secret" + armor, err := cstore.Export("john") + assert.Nil(t, err) - // import raw bytes - err := cstore.Import(name, pass, "", key.Bytes()) - require.Nil(err, "%+v", err) + err = cstore.Import("john2", armor) + assert.Nil(t, err) - // make sure the address matches - info, err := cstore.Get(name) - require.Nil(err, "%+v", err) - require.EqualValues(addr, info.Address()) + john2, err := cstore.Get("john2") + assert.Nil(t, err) + + assert.Equal(t, john.PubKey.Address(), addr) + assert.Equal(t, john.Name, "john") + assert.Equal(t, john, john2) } // TestAdvancedKeyManagement verifies update, import, export functionality func TestAdvancedKeyManagement(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) // make the storage with reasonable defaults cstore := keys.New( @@ -249,42 +252,49 @@ func TestAdvancedKeyManagement(t *testing.T) { words.MustLoadCodec("english"), ) - algo := crypto.NameSecp256k1 + algo := keys.AlgoSecp256k1 n1, n2 := "old-name", "new name" - p1, p2, p3, pt := "1234", "foobar", "ding booms!", "really-secure!@#$" + p1, p2 := "1234", "foobar" // make sure key works with initial password _, _, err := cstore.Create(n1, p1, algo) - require.Nil(err, "%+v", err) - assertPassword(assert, cstore, n1, p1, p2) + require.Nil(t, err, "%+v", err) + assertPassword(t, cstore, n1, p1, p2) // update password requires the existing password err = cstore.Update(n1, "jkkgkg", p2) - assert.NotNil(err) - assertPassword(assert, cstore, n1, p1, p2) + assert.NotNil(t, err) + assertPassword(t, cstore, n1, p1, p2) // then it changes the password when correct err = cstore.Update(n1, p1, p2) - assert.Nil(err) + assert.Nil(t, err) // p2 is now the proper one! - assertPassword(assert, cstore, n1, p2, p1) + assertPassword(t, cstore, n1, p2, p1) // exporting requires the proper name and passphrase - _, err = cstore.Export(n2, p2, pt) - assert.NotNil(err) - _, err = cstore.Export(n1, p1, pt) - assert.NotNil(err) - exported, err := cstore.Export(n1, p2, pt) - require.Nil(err, "%+v", err) - - // import fails on bad transfer pass - err = cstore.Import(n2, p3, p2, exported) - assert.NotNil(err) + _, err = cstore.Export(n1 + ".notreal") + assert.NotNil(t, err) + _, err = cstore.Export(" " + n1) + assert.NotNil(t, err) + _, err = cstore.Export(n1 + " ") + assert.NotNil(t, err) + _, err = cstore.Export("") + assert.NotNil(t, err) + exported, err := cstore.Export(n1) + require.Nil(t, err, "%+v", err) + + // import succeeds + err = cstore.Import(n2, exported) + assert.Nil(t, err) + + // second import fails + err = cstore.Import(n2, exported) + assert.NotNil(t, err) } // TestSeedPhrase verifies restoring from a seed phrase func TestSeedPhrase(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) // make the storage with reasonable defaults cstore := keys.New( @@ -292,28 +302,28 @@ func TestSeedPhrase(t *testing.T) { words.MustLoadCodec("english"), ) - algo := crypto.NameEd25519 + algo := keys.AlgoEd25519 n1, n2 := "lost-key", "found-again" p1, p2 := "1234", "foobar" // make sure key works with initial password - seed, info, err := cstore.Create(n1, p1, algo) - require.Nil(err, "%+v", err) - assert.Equal(n1, info.Name) - assert.NotEmpty(seed) + info, seed, err := cstore.Create(n1, p1, algo) + require.Nil(t, err, "%+v", err) + assert.Equal(t, n1, info.Name) + assert.NotEmpty(t, seed) // now, let us delete this key err = cstore.Delete(n1, p1) - require.Nil(err, "%+v", err) + require.Nil(t, err, "%+v", err) _, err = cstore.Get(n1) - require.NotNil(err) + require.NotNil(t, err) // let us re-create it from the seed-phrase - newInfo, err := cstore.Recover(n2, p2, algo, seed) - require.Nil(err, "%+v", err) - assert.Equal(n2, newInfo.Name) - assert.Equal(info.Address(), newInfo.Address()) - assert.Equal(info.PubKey, newInfo.PubKey) + newInfo, err := cstore.Recover(n2, p2, seed) + require.Nil(t, err, "%+v", err) + assert.Equal(t, n2, newInfo.Name) + assert.Equal(t, info.Address(), newInfo.Address()) + assert.Equal(t, info.PubKey, newInfo.PubKey) } func ExampleNew() { @@ -322,11 +332,11 @@ func ExampleNew() { dbm.NewMemDB(), words.MustLoadCodec("english"), ) - ed := crypto.NameEd25519 - sec := crypto.NameSecp256k1 + ed := keys.AlgoEd25519 + sec := keys.AlgoSecp256k1 // Add keys and see they return in alphabetical order - _, bob, err := cstore.Create("Bob", "friend", ed) + bob, _, err := cstore.Create("Bob", "friend", ed) if err != nil { // this should never happen fmt.Println(err) diff --git a/keys/keys.go b/keys/keys.go new file mode 100644 index 000000000..0ed89b3fd --- /dev/null +++ b/keys/keys.go @@ -0,0 +1,32 @@ +package keys + +import "fmt" + +type CryptoAlgo string + +const ( + AlgoEd25519 = CryptoAlgo("ed25519") + AlgoSecp256k1 = CryptoAlgo("secp256k1") +) + +func cryptoAlgoToByte(key CryptoAlgo) byte { + switch key { + case AlgoEd25519: + return 0x01 + case AlgoSecp256k1: + return 0x02 + default: + panic(fmt.Sprintf("Unexpected type key %v", key)) + } +} + +func byteToCryptoAlgo(b byte) CryptoAlgo { + switch b { + case 0x01: + return AlgoEd25519 + case 0x02: + return AlgoSecp256k1 + default: + panic(fmt.Sprintf("Unexpected type byte %X", b)) + } +} diff --git a/keys/mintkey.go b/keys/mintkey.go index d2724d658..73263b83a 100644 --- a/keys/mintkey.go +++ b/keys/mintkey.go @@ -12,8 +12,34 @@ import ( const ( blockTypePrivKey = "TENDERMINT PRIVATE KEY" + blockTypeKeyInfo = "TENDERMINT KEY INFO" ) +func armorInfoBytes(bz []byte) string { + header := map[string]string{ + "type": "Info", + "version": "0.0.0", + } + armorStr := crypto.EncodeArmor(blockTypeKeyInfo, header, bz) + return armorStr +} + +func unarmorInfoBytes(armorStr string) (bz []byte, err error) { + blockType, header, bz, err := crypto.DecodeArmor(armorStr) + if err != nil { + return + } + if blockType != blockTypeKeyInfo { + err = fmt.Errorf("Unrecognized armor type: %v", blockType) + return + } + if header["version"] != "0.0.0" { + err = fmt.Errorf("Unrecognized version: %v", header["version"]) + return + } + return +} + func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { saltBytes, encBytes := encryptPrivKey(privKey, passphrase) header := map[string]string{ diff --git a/keys/types.go b/keys/types.go index fdb729e0a..cec9c36db 100644 --- a/keys/types.go +++ b/keys/types.go @@ -1,15 +1,40 @@ package keys import ( - wire "github.com/tendermint/go-wire" - crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" ) +// Keybase allows simple CRUD on a keystore, as an aid to signing +type Keybase interface { + // Sign some bytes + Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error) + // Create a new keypair + Create(name, passphrase string, algo CryptoAlgo) (info Info, seed string, err error) + // Recover takes a seedphrase and loads in the key + Recover(name, passphrase, seedphrase string) (info Info, erro error) + List() ([]Info, error) + Get(name string) (Info, error) + Update(name, oldpass, newpass string) error + Delete(name, passphrase string) error + + Import(name string, armor string) (err error) + Export(name string) (armor string, err error) +} + // Info is the public information about a key type Info struct { - Name string `json:"name"` - PubKey crypto.PubKey `json:"pubkey"` + Name string `json:"name"` + PubKey crypto.PubKey `json:"pubkey"` + PrivKeyArmor string `json:"privkey.armor"` +} + +func newInfo(name string, pub crypto.PubKey, privArmor string) Info { + return Info{ + Name: name, + PubKey: pub, + PrivKeyArmor: privArmor, + } } // Address is a helper function to calculate the address from the pubkey @@ -18,31 +43,14 @@ func (i Info) Address() []byte { } func (i Info) bytes() []byte { - return wire.BinaryBytes(i) -} - -func readInfo(bs []byte) (info Info, err error) { - err = wire.ReadBinaryBytes(bs, &info) - return -} - -func info(name string, privKey crypto.PrivKey) Info { - return Info{ - Name: name, - PubKey: privKey.PubKey(), + bz, err := wire.MarshalBinary(i) + if err != nil { + panic(err) } + return bz } -// Keybase allows simple CRUD on a keystore, as an aid to signing -type Keybase interface { - // Sign some bytes - Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error) - // Create a new keypair - Create(name, passphrase, algo string) (seedphrase string, _ Info, _ error) - // Recover takes a seedphrase and loads in the key - Recover(name, passphrase, algo, seedphrase string) (Info, error) - List() ([]Info, error) - Get(name string) (Info, error) - Update(name, oldpass, newpass string) error - Delete(name, passphrase string) error +func readInfo(bz []byte) (info Info, err error) { + err = wire.UnmarshalBinary(bz, &info) + return }