From f6a250976439b2fd5f5b7b9e714d24220afc45e7 Mon Sep 17 00:00:00 2001 From: Emmanuel Odeke Date: Fri, 15 Sep 2017 17:07:14 -0600 Subject: [PATCH 01/43] doc.go file started Fixes #35 Updates https://github.com/tendermint/coding/issues/27 Started a doc.go file to provide an overview/high level dive into the functionality of this repo. Also added an example_test.go file in which we can put end-to-end code examples/actual usage patterns that can be copied and pasted and will always have to compile when tests are run to ensure that we don't regress. --- doc.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ example_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 doc.go create mode 100644 example_test.go diff --git a/doc.go b/doc.go new file mode 100644 index 000000000..c6701bc58 --- /dev/null +++ b/doc.go @@ -0,0 +1,48 @@ +/* +go-crypto is a customized/convenience cryptography package +for supporting Tendermint. + +It wraps select functionality of equivalent functions in the +Go standard library, for easy usage with our libraries. + +Keys: + +All key generation functions return an instance of the PrivKey interface +which implements methods + + AssertIsPrivKeyInner() + Bytes() []byte + Sign(msg []byte) Signature + PubKey() PubKey + Equals(PrivKey) bool + Wrap() PrivKey + +From the above method we can: +a) Retrieve the public key if needed + + pubKey := key.PubKey() + +For example: + privKey, err := crypto.GenPrivKeyEd25519() + if err != nil { + ... + } + pubKey := privKey.PubKey() + ... + // And then you can use the private and public key + doSomething(privKey, pubKey) + + +We also provide hashing wrappers around algorithms: + +Sha256 + sum := crypto.Sha256([]byte("This is Tendermint")) + fmt.Printf("%x\n", sum) + +Ripemd160 + sum := crypto.Ripemd160([]byte("This is consensus")) + fmt.Printf("%x\n", sum) +*/ +package crypto + +// TODO: Add more docs in here diff --git a/example_test.go b/example_test.go new file mode 100644 index 000000000..c9cd2688d --- /dev/null +++ b/example_test.go @@ -0,0 +1,35 @@ +// Copyright 2017 Tendermint. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package crypto_test + +import ( + "fmt" + + "github.com/tendermint/go-crypto" +) + +func Example_Sha256() { + sum := crypto.Sha256([]byte("This is Tendermint")) + fmt.Printf("%x\n", sum) + // Output: + // f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e +} + +func Example_Ripemd160() { + sum := crypto.Ripemd160([]byte("This is Tendermint")) + fmt.Printf("%x\n", sum) + // Output: + // 051e22663e8f0fd2f2302f1210f954adff009005 +} From fe66a683bcbb28d25a07299e45741a835cd13dbd Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Fri, 22 Sep 2017 13:31:01 -0400 Subject: [PATCH 02/43] linter: add metalinter to Makefile & apply some fixes --- Makefile | 38 ++++++++++++- _gen.go | 6 -- armor.go | 2 +- bcrypt/bcrypt.go | 2 +- hash.go | 1 + hd/address.go | 19 +------ hd/hd_test.go | 97 +------------------------------- keys/cryptostore/holder.go | 2 + keys/server/helpers.go | 4 +- keys/storage/filestorage/main.go | 5 +- keys/storage/memstorage/main.go | 1 + keys/wordcodec_test.go | 4 +- keys/wordlist/wordlist.go | 15 ++--- 13 files changed, 65 insertions(+), 131 deletions(-) delete mode 100644 _gen.go diff --git a/Makefile b/Makefile index 0f414e1c2..cdfee009b 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,9 @@ GOTOOLS = \ github.com/Masterminds/glide \ - github.com/jteeuwen/go-bindata/go-bindata + github.com/jteeuwen/go-bindata/go-bindata \ + github.com/alecthomas/gometalinter + REPO:=github.com/tendermint/go-crypto all: get_vendor_deps test @@ -31,3 +33,37 @@ codegen: @echo "--> regenerating all interface wrappers" @gen @echo "Done!" + +metalinter: ensure_tools + @gometalinter --install + gometalinter --vendor --deadline=600s --enable-all --disable=lll ./... + +metalinter_test: ensure_tools + @gometalinter --install + gometalinter --vendor --deadline=600s --disable-all \ + --enable=aligncheck \ + --enable=deadcode \ + --enable=gas \ + --enable=goconst \ + --enable=gocyclo \ + --enable=goimports \ + --enable=gosimple \ + --enable=gotype \ + --enable=ineffassign \ + --enable=interfacer \ + --enable=megacheck \ + --enable=misspell \ + --enable=safesql \ + --enable=staticcheck \ + --enable=structcheck \ + --enable=unconvert \ + --enable=unused \ + --enable=vetshadow \ + --enable=vet \ + --enable=unparam \ + --enable=varcheck \ + ./... + + #--enable=dupl \ + #--enable=errcheck \ + #--enable=golint \ <== comments on anything exported diff --git a/_gen.go b/_gen.go deleted file mode 100644 index a98feaf4e..000000000 --- a/_gen.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -import ( - _ "github.com/tendermint/go-wire/gen" - _ "github.com/clipperhouse/stringer" -) diff --git a/armor.go b/armor.go index 3d2eff5e7..5f199df43 100644 --- a/armor.go +++ b/armor.go @@ -22,7 +22,7 @@ func EncodeArmor(blockType string, headers map[string]string, data []byte) strin if err != nil { PanicSanity("Error encoding ascii armor: " + err.Error()) } - return string(buf.Bytes()) + return buf.String() } func DecodeArmor(armorStr string) (blockType string, headers map[string]string, data []byte, err error) { diff --git a/bcrypt/bcrypt.go b/bcrypt/bcrypt.go index a6b4a2cf6..6b23b7a9d 100644 --- a/bcrypt/bcrypt.go +++ b/bcrypt/bcrypt.go @@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string { type InvalidCostError int func (ic InvalidCostError) Error() string { - return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) + return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert } const ( diff --git a/hash.go b/hash.go index 165b1e153..b04afe085 100644 --- a/hash.go +++ b/hash.go @@ -1,3 +1,4 @@ +// nolint: goimports package crypto import ( diff --git a/hd/address.go b/hd/address.go index d7553a4d7..b6532e351 100644 --- a/hd/address.go +++ b/hd/address.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "hash" - "log" "math/big" "strconv" "strings" @@ -88,18 +87,6 @@ func ComputeTxId(rawTxHex string) string { // Private methods... -func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) { - if pubKeyBytes == nil { - pubKeyBytes = PubKeyBytesFromPrivKeyBytes(privKeyBytes, true) - } - addr := AddrFromPubKeyBytes(pubKeyBytes) - log.Println("\nprikey:\t%v\npubKeyBytes:\t%v\naddr:\t%v\nchain:\t%v", - HexEncode(privKeyBytes), - HexEncode(pubKeyBytes), - addr, - HexEncode(chain)) -} - func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []byte { data := privKeyBytes parts := strings.Split(path, "/") @@ -144,7 +131,7 @@ func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byt } func DerivePrivateKey(privKeyBytes []byte, chain []byte, i uint32, prime bool) ([]byte, []byte) { - data := []byte{} + data := []byte{} // nolint [ megacheck, deadcode ] if prime { i = i | 0x80000000 data = append([]byte{byte(0)}, privKeyBytes...) @@ -177,7 +164,7 @@ func addPoints(a []byte, b []byte) []byte { panic(err) } sumX, sumY := btcec.S256().Add(ap.X, ap.Y, bp.X, bp.Y) - sum := (*btcec.PublicKey)(&btcec.PublicKey{ + sum := (*btcec.PublicKey)(&btcec.PublicKey{ // nolint: unconvert Curve: btcec.S256(), X: sumX, Y: sumY, @@ -248,7 +235,7 @@ func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string { func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyBytes []byte) { x, y := btcec.S256().ScalarBaseMult(privKeyBytes) - pub := (*btcec.PublicKey)(&btcec.PublicKey{ + pub := (*btcec.PublicKey)(&btcec.PublicKey{ // nolint: unconvert Curve: btcec.S256(), X: x, Y: y, diff --git a/hd/hd_test.go b/hd/hd_test.go index b2f7d2e8f..60d77ad66 100644 --- a/hd/hd_test.go +++ b/hd/hd_test.go @@ -1,10 +1,8 @@ +// nolint: goimports package hd import ( "bytes" - "crypto/hmac" - "crypto/sha512" - "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -15,11 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/tyler-smith/go-bip39" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/mndrix/btcutil" - "github.com/tyler-smith/go-bip32" - "github.com/tendermint/go-crypto" ) @@ -33,7 +26,7 @@ type addrData struct { } // NOTE: atom fundraiser address -var hdPath string = "m/44'/118'/0'/0/0" +// var hdPath string = "m/44'/118'/0'/0/0" var hdToAddrTable []addrData func init() { @@ -109,13 +102,6 @@ func TestReverseBytes(t *testing.T) { } } -func ifExit(err error, n int) { - if err != nil { - fmt.Println(n, err) - os.Exit(1) - } -} - func gocrypto(seed []byte) ([]byte, []byte, []byte) { _, priv, ch, _ := ComputeMastersFromSeed(string(seed)) @@ -131,83 +117,6 @@ func gocrypto(seed []byte) ([]byte, []byte, []byte) { return HexDecode(priv), privBytes, pubBytes } -func btcsuite(seed []byte) ([]byte, []byte, []byte) { - fmt.Println("HD") - masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) - if err != nil { - hmac := hmac.New(sha512.New, []byte("Bitcoin seed")) - hmac.Write([]byte(seed)) - intermediary := hmac.Sum(nil) - - curve := btcutil.Secp256k1() - curveParams := curve.Params() - - // Split it into our key and chain code - keyBytes := intermediary[:32] - fmt.Printf("\t%X\n", keyBytes) - fmt.Printf("\t%X\n", curveParams.N.Bytes()) - keyInt, _ := binary.ReadVarint(bytes.NewBuffer(keyBytes)) - fmt.Printf("\t%d\n", keyInt) - } - fh := hdkeychain.HardenedKeyStart - k, err := masterKey.Child(uint32(fh + 44)) - ifExit(err, 44) - k, err = k.Child(uint32(fh + 118)) - ifExit(err, 118) - k, err = k.Child(uint32(fh + 0)) - ifExit(err, 1) - k, err = k.Child(uint32(0)) - ifExit(err, 2) - k, err = k.Child(uint32(0)) - ifExit(err, 3) - ecpriv, err := k.ECPrivKey() - ifExit(err, 10) - ecpub, err := k.ECPubKey() - ifExit(err, 11) - - priv := ecpriv.Serialize() - pub := ecpub.SerializeCompressed() - mkey, _ := masterKey.ECPrivKey() - return mkey.Serialize(), priv, pub -} - -// return priv and pub -func tylerSmith(seed []byte) ([]byte, []byte, []byte) { - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - hmac := hmac.New(sha512.New, []byte("Bitcoin seed")) - hmac.Write([]byte(seed)) - intermediary := hmac.Sum(nil) - - curve := btcutil.Secp256k1() - curveParams := curve.Params() - - // Split it into our key and chain code - keyBytes := intermediary[:32] - fmt.Printf("\t%X\n", keyBytes) - fmt.Printf("\t%X\n", curveParams.N.Bytes()) - keyInt, _ := binary.ReadVarint(bytes.NewBuffer(keyBytes)) - fmt.Printf("\t%d\n", keyInt) - - } - ifExit(err, 0) - fh := bip32.FirstHardenedChild - k, err := masterKey.NewChildKey(fh + 44) - ifExit(err, 44) - k, err = k.NewChildKey(fh + 118) - ifExit(err, 118) - k, err = k.NewChildKey(fh + 0) - ifExit(err, 1) - k, err = k.NewChildKey(0) - ifExit(err, 2) - k, err = k.NewChildKey(0) - ifExit(err, 3) - - priv := k.Key - pub := k.PublicKey().Key - return masterKey.Key, priv, pub -} - // Benchmarks var revBytesCases = [][]byte{ @@ -237,6 +146,6 @@ func BenchmarkReverseBytes(b *testing.B) { // sink is necessary to ensure if the compiler tries // to smart, that it won't optimize away the benchmarks. - if sink != nil { + if sink != nil { // nolint: megacheck } } diff --git a/keys/cryptostore/holder.go b/keys/cryptostore/holder.go index 18437a9b5..f9e5f5bb3 100644 --- a/keys/cryptostore/holder.go +++ b/keys/cryptostore/holder.go @@ -25,11 +25,13 @@ func New(coder Encoder, store keys.Storage, codec keys.Codec) Manager { } // exists just to make sure we fulfill the Signer interface +// nolint [ megacheck, deadcode ] func (s Manager) assertSigner() keys.Signer { return s } // exists just to make sure we fulfill the Manager interface +// nolint [ megacheck, deadcode ] func (s Manager) assertKeyManager() keys.Manager { return s } diff --git a/keys/server/helpers.go b/keys/server/helpers.go index 710e4f392..4820c937e 100644 --- a/keys/server/helpers.go +++ b/keys/server/helpers.go @@ -5,6 +5,8 @@ for key management, transaction signing, and query validation. Please read the README and godoc to see how to configure the server for your application. */ + +// nolint: goimports package server import ( @@ -12,8 +14,8 @@ import ( "io/ioutil" "net/http" - data "github.com/tendermint/go-wire/data" "github.com/tendermint/go-crypto/keys/server/types" + data "github.com/tendermint/go-wire/data" "github.com/pkg/errors" ) diff --git a/keys/storage/filestorage/main.go b/keys/storage/filestorage/main.go index 696b200fc..d8058a5b9 100644 --- a/keys/storage/filestorage/main.go +++ b/keys/storage/filestorage/main.go @@ -22,8 +22,8 @@ const ( PrivExt = "tlc" PubExt = "pub" keyPerm = os.FileMode(0600) - pubPerm = os.FileMode(0644) - dirPerm = os.FileMode(0700) + // pubPerm = os.FileMode(0644) + dirPerm = os.FileMode(0700) ) type FileStore struct { @@ -43,6 +43,7 @@ func New(dir string) FileStore { } // assertStorage just makes sure we implement the proper Storage interface +// nolint [ megacheck, deadcode ] func (s FileStore) assertStorage() keys.Storage { return s } diff --git a/keys/storage/memstorage/main.go b/keys/storage/memstorage/main.go index 195fa7a17..403a01318 100644 --- a/keys/storage/memstorage/main.go +++ b/keys/storage/memstorage/main.go @@ -23,6 +23,7 @@ func New() MemStore { } // assertStorage just makes sure we implement the Storage interface +// nolint [ megacheck, deadcode ] func (s MemStore) assertStorage() keys.Storage { return s } diff --git a/keys/wordcodec_test.go b/keys/wordcodec_test.go index 25c5439a6..a44607be2 100644 --- a/keys/wordcodec_test.go +++ b/keys/wordcodec_test.go @@ -119,8 +119,8 @@ func TestCheckInvalidLists(t *testing.T) { w, err := codec.BytesToWords(data) if tc.valid { assert.Nil(err, "%d: %+v", i, err) - b, err := codec.WordsToBytes(w) - assert.Nil(err, "%d: %+v", i, err) + b, err1 := codec.WordsToBytes(w) + assert.Nil(err1, "%d: %+v", i, err1) assert.Equal(data, b) } else { assert.NotNil(err, "%d", i) diff --git a/keys/wordlist/wordlist.go b/keys/wordlist/wordlist.go index 97ddb2369..915dda102 100644 --- a/keys/wordlist/wordlist.go +++ b/keys/wordlist/wordlist.go @@ -6,6 +6,7 @@ // keys/wordlist/spanish.txt // DO NOT EDIT! +// nolint: goimports package wordlist import ( @@ -204,9 +205,9 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "keys/wordlist/chinese_simplified.txt": keysWordlistChinese_simplifiedTxt, - "keys/wordlist/english.txt": keysWordlistEnglishTxt, - "keys/wordlist/japanese.txt": keysWordlistJapaneseTxt, - "keys/wordlist/spanish.txt": keysWordlistSpanishTxt, + "keys/wordlist/english.txt": keysWordlistEnglishTxt, + "keys/wordlist/japanese.txt": keysWordlistJapaneseTxt, + "keys/wordlist/spanish.txt": keysWordlistSpanishTxt, } // AssetDir returns the file names below a certain @@ -248,13 +249,14 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } + var _bintree = &bintree{nil, map[string]*bintree{ "keys": &bintree{nil, map[string]*bintree{ "wordlist": &bintree{nil, map[string]*bintree{ "chinese_simplified.txt": &bintree{keysWordlistChinese_simplifiedTxt, map[string]*bintree{}}, - "english.txt": &bintree{keysWordlistEnglishTxt, map[string]*bintree{}}, - "japanese.txt": &bintree{keysWordlistJapaneseTxt, map[string]*bintree{}}, - "spanish.txt": &bintree{keysWordlistSpanishTxt, map[string]*bintree{}}, + "english.txt": &bintree{keysWordlistEnglishTxt, map[string]*bintree{}}, + "japanese.txt": &bintree{keysWordlistJapaneseTxt, map[string]*bintree{}}, + "spanish.txt": &bintree{keysWordlistSpanishTxt, map[string]*bintree{}}, }}, }}, }} @@ -305,4 +307,3 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } - From 7921fb0c05c61cf55ad8dca18d492cea741e29dc Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Fri, 22 Sep 2017 13:35:02 -0400 Subject: [PATCH 03/43] linter: last fixes & add to circle --- Makefile | 2 +- keys/cryptostore/holder_test.go | 1 + keys/server/keys.go | 1 + keys/server/keys_test.go | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cdfee009b..4114de740 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ GOTOOLS = \ REPO:=github.com/tendermint/go-crypto -all: get_vendor_deps test +all: get_vendor_deps metalinter_test test test: go test `glide novendor` diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 434966f46..0ef4ae4ba 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -144,6 +144,7 @@ func TestKeyManagement(t *testing.T) { // } // } +// nolint: unparam func assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) { err := cstore.Update(name, badpass, pass) assert.NotNil(err) diff --git a/keys/server/keys.go b/keys/server/keys.go index 80852802e..cae3e451d 100644 --- a/keys/server/keys.go +++ b/keys/server/keys.go @@ -52,6 +52,7 @@ func (k Keys) GetKey(w http.ResponseWriter, r *http.Request) { writeSuccess(w, &key) } +// nolint: unparam func (k Keys) ListKeys(w http.ResponseWriter, r *http.Request) { keys, err := k.manager.List() diff --git a/keys/server/keys_test.go b/keys/server/keys_test.go index 2aa17753c..cad415644 100644 --- a/keys/server/keys_test.go +++ b/keys/server/keys_test.go @@ -120,6 +120,7 @@ func listKeys(h http.Handler) (keys.Infos, int, error) { return data, rr.Code, err } +// nolint: unparam func getKey(h http.Handler, name string) (*keys.Info, int, error) { rr := httptest.NewRecorder() req, err := http.NewRequest("GET", "/keys/"+name, nil) @@ -137,6 +138,7 @@ func getKey(h http.Handler, name string) (*keys.Info, int, error) { return &data, rr.Code, err } +// nolint: unparam func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyResponse, int, error) { rr := httptest.NewRecorder() post := types.CreateKeyRequest{ @@ -165,6 +167,7 @@ func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyR return data, rr.Code, err } +// nolint: unparam func deleteKey(h http.Handler, name, passphrase string) (*types.ErrorResponse, int, error) { rr := httptest.NewRecorder() post := types.DeleteKeyRequest{ From 87cb57c3e5e76cab8b766253c218ec7b68ecec09 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Wed, 4 Oct 2017 18:16:48 -0400 Subject: [PATCH 04/43] linting: fixup some stuffs --- Makefile | 4 +- hash.go | 1 - hd/address.go | 24 ++++++-- hd/hd_test.go | 96 +++++++++++++++++++++++++++++++- keys/cryptostore/holder.go | 5 +- keys/cryptostore/holder_test.go | 1 - keys/server/helpers.go | 1 - keys/server/keys.go | 1 - keys/server/keys_test.go | 3 - keys/storage/filestorage/main.go | 3 +- keys/storage/memstorage/main.go | 3 +- keys/wordlist/wordlist.go | 1 - 12 files changed, 121 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 4114de740..b1dc51ae0 100644 --- a/Makefile +++ b/Makefile @@ -46,9 +46,7 @@ metalinter_test: ensure_tools --enable=gas \ --enable=goconst \ --enable=gocyclo \ - --enable=goimports \ --enable=gosimple \ - --enable=gotype \ --enable=ineffassign \ --enable=interfacer \ --enable=megacheck \ @@ -66,4 +64,6 @@ metalinter_test: ensure_tools #--enable=dupl \ #--enable=errcheck \ + #--enable=goimports \ #--enable=golint \ <== comments on anything exported + #--enable=gotype \ diff --git a/hash.go b/hash.go index b04afe085..165b1e153 100644 --- a/hash.go +++ b/hash.go @@ -1,4 +1,3 @@ -// nolint: goimports package crypto import ( diff --git a/hd/address.go b/hd/address.go index b6532e351..5b664b496 100644 --- a/hd/address.go +++ b/hd/address.go @@ -85,7 +85,19 @@ func ComputeTxId(rawTxHex string) string { return HexEncode(ReverseBytes(CalcHash256(HexDecode(rawTxHex)))) } -// Private methods... +/* +func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) { + if pubKeyBytes == nil { + pubKeyBytes = PubKeyBytesFromPrivKeyBytes(privKeyBytes, true) + } + addr := AddrFromPubKeyBytes(pubKeyBytes) + log.Println("\nprikey:\t%v\npubKeyBytes:\t%v\naddr:\t%v\nchain:\t%v", + HexEncode(privKeyBytes), + HexEncode(pubKeyBytes), + addr, + HexEncode(chain)) +} +*/ func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []byte { data := privKeyBytes @@ -131,7 +143,7 @@ func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byt } func DerivePrivateKey(privKeyBytes []byte, chain []byte, i uint32, prime bool) ([]byte, []byte) { - data := []byte{} // nolint [ megacheck, deadcode ] + var data []byte if prime { i = i | 0x80000000 data = append([]byte{byte(0)}, privKeyBytes...) @@ -164,11 +176,11 @@ func addPoints(a []byte, b []byte) []byte { panic(err) } sumX, sumY := btcec.S256().Add(ap.X, ap.Y, bp.X, bp.Y) - sum := (*btcec.PublicKey)(&btcec.PublicKey{ // nolint: unconvert + sum := &btcec.PublicKey{ Curve: btcec.S256(), X: sumX, Y: sumY, - }) + } return sum.SerializeCompressed() } @@ -235,11 +247,11 @@ func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string { func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyBytes []byte) { x, y := btcec.S256().ScalarBaseMult(privKeyBytes) - pub := (*btcec.PublicKey)(&btcec.PublicKey{ // nolint: unconvert + pub := &btcec.PublicKey{ Curve: btcec.S256(), X: x, Y: y, - }) + } if compress { return pub.SerializeCompressed() diff --git a/hd/hd_test.go b/hd/hd_test.go index 60d77ad66..fcb348b5c 100644 --- a/hd/hd_test.go +++ b/hd/hd_test.go @@ -1,8 +1,10 @@ -// nolint: goimports package hd import ( "bytes" + "crypto/hmac" + "crypto/sha512" + "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -13,6 +15,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/tyler-smith/go-bip39" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil/hdkeychain" + "github.com/mndrix/btcutil" + "github.com/tyler-smith/go-bip32" + "github.com/tendermint/go-crypto" ) @@ -102,6 +109,13 @@ func TestReverseBytes(t *testing.T) { } } +func ifExit(err error, n int) { + if err != nil { + fmt.Println(n, err) + os.Exit(1) + } +} + func gocrypto(seed []byte) ([]byte, []byte, []byte) { _, priv, ch, _ := ComputeMastersFromSeed(string(seed)) @@ -117,6 +131,83 @@ func gocrypto(seed []byte) ([]byte, []byte, []byte) { return HexDecode(priv), privBytes, pubBytes } +func btcsuite(seed []byte) ([]byte, []byte, []byte) { + fmt.Println("HD") + masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + hmac := hmac.New(sha512.New, []byte("Bitcoin seed")) + hmac.Write([]byte(seed)) + intermediary := hmac.Sum(nil) + + curve := btcutil.Secp256k1() + curveParams := curve.Params() + + // Split it into our key and chain code + keyBytes := intermediary[:32] + fmt.Printf("\t%X\n", keyBytes) + fmt.Printf("\t%X\n", curveParams.N.Bytes()) + keyInt, _ := binary.ReadVarint(bytes.NewBuffer(keyBytes)) + fmt.Printf("\t%d\n", keyInt) + } + fh := hdkeychain.HardenedKeyStart + k, err := masterKey.Child(uint32(fh + 44)) + ifExit(err, 44) + k, err = k.Child(uint32(fh + 118)) + ifExit(err, 118) + k, err = k.Child(uint32(fh + 0)) + ifExit(err, 1) + k, err = k.Child(uint32(0)) + ifExit(err, 2) + k, err = k.Child(uint32(0)) + ifExit(err, 3) + ecpriv, err := k.ECPrivKey() + ifExit(err, 10) + ecpub, err := k.ECPubKey() + ifExit(err, 11) + + priv := ecpriv.Serialize() + pub := ecpub.SerializeCompressed() + mkey, _ := masterKey.ECPrivKey() + return mkey.Serialize(), priv, pub +} + +// return priv and pub +func tylerSmith(seed []byte) ([]byte, []byte, []byte) { + masterKey, err := bip32.NewMasterKey(seed) + if err != nil { + hmac := hmac.New(sha512.New, []byte("Bitcoin seed")) + hmac.Write([]byte(seed)) + intermediary := hmac.Sum(nil) + + curve := btcutil.Secp256k1() + curveParams := curve.Params() + + // Split it into our key and chain code + keyBytes := intermediary[:32] + fmt.Printf("\t%X\n", keyBytes) + fmt.Printf("\t%X\n", curveParams.N.Bytes()) + keyInt, _ := binary.ReadVarint(bytes.NewBuffer(keyBytes)) + fmt.Printf("\t%d\n", keyInt) + + } + ifExit(err, 0) + fh := bip32.FirstHardenedChild + k, err := masterKey.NewChildKey(fh + 44) + ifExit(err, 44) + k, err = k.NewChildKey(fh + 118) + ifExit(err, 118) + k, err = k.NewChildKey(fh + 0) + ifExit(err, 1) + k, err = k.NewChildKey(0) + ifExit(err, 2) + k, err = k.NewChildKey(0) + ifExit(err, 3) + + priv := k.Key + pub := k.PublicKey().Key + return masterKey.Key, priv, pub +} + // Benchmarks var revBytesCases = [][]byte{ @@ -146,6 +237,7 @@ func BenchmarkReverseBytes(b *testing.B) { // sink is necessary to ensure if the compiler tries // to smart, that it won't optimize away the benchmarks. - if sink != nil { // nolint: megacheck + if sink != nil { + _ = sink } } diff --git a/keys/cryptostore/holder.go b/keys/cryptostore/holder.go index f9e5f5bb3..f4d82583b 100644 --- a/keys/cryptostore/holder.go +++ b/keys/cryptostore/holder.go @@ -24,14 +24,15 @@ func New(coder Encoder, store keys.Storage, codec keys.Codec) Manager { } } +var _ keys.Signer = Manager{} +var _ keys.Manager = Manager{} + // exists just to make sure we fulfill the Signer interface -// nolint [ megacheck, deadcode ] func (s Manager) assertSigner() keys.Signer { return s } // exists just to make sure we fulfill the Manager interface -// nolint [ megacheck, deadcode ] func (s Manager) assertKeyManager() keys.Manager { return s } diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 0ef4ae4ba..434966f46 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -144,7 +144,6 @@ func TestKeyManagement(t *testing.T) { // } // } -// nolint: unparam func assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) { err := cstore.Update(name, badpass, pass) assert.NotNil(err) diff --git a/keys/server/helpers.go b/keys/server/helpers.go index 4820c937e..3fb947316 100644 --- a/keys/server/helpers.go +++ b/keys/server/helpers.go @@ -6,7 +6,6 @@ Please read the README and godoc to see how to configure the server for your application. */ -// nolint: goimports package server import ( diff --git a/keys/server/keys.go b/keys/server/keys.go index cae3e451d..80852802e 100644 --- a/keys/server/keys.go +++ b/keys/server/keys.go @@ -52,7 +52,6 @@ func (k Keys) GetKey(w http.ResponseWriter, r *http.Request) { writeSuccess(w, &key) } -// nolint: unparam func (k Keys) ListKeys(w http.ResponseWriter, r *http.Request) { keys, err := k.manager.List() diff --git a/keys/server/keys_test.go b/keys/server/keys_test.go index cad415644..2aa17753c 100644 --- a/keys/server/keys_test.go +++ b/keys/server/keys_test.go @@ -120,7 +120,6 @@ func listKeys(h http.Handler) (keys.Infos, int, error) { return data, rr.Code, err } -// nolint: unparam func getKey(h http.Handler, name string) (*keys.Info, int, error) { rr := httptest.NewRecorder() req, err := http.NewRequest("GET", "/keys/"+name, nil) @@ -138,7 +137,6 @@ func getKey(h http.Handler, name string) (*keys.Info, int, error) { return &data, rr.Code, err } -// nolint: unparam func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyResponse, int, error) { rr := httptest.NewRecorder() post := types.CreateKeyRequest{ @@ -167,7 +165,6 @@ func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyR return data, rr.Code, err } -// nolint: unparam func deleteKey(h http.Handler, name, passphrase string) (*types.ErrorResponse, int, error) { rr := httptest.NewRecorder() post := types.DeleteKeyRequest{ diff --git a/keys/storage/filestorage/main.go b/keys/storage/filestorage/main.go index d8058a5b9..e33ca5000 100644 --- a/keys/storage/filestorage/main.go +++ b/keys/storage/filestorage/main.go @@ -42,8 +42,9 @@ func New(dir string) FileStore { return FileStore{dir} } +var _ keys.Storage = FileStore{} + // assertStorage just makes sure we implement the proper Storage interface -// nolint [ megacheck, deadcode ] func (s FileStore) assertStorage() keys.Storage { return s } diff --git a/keys/storage/memstorage/main.go b/keys/storage/memstorage/main.go index 403a01318..6464b1308 100644 --- a/keys/storage/memstorage/main.go +++ b/keys/storage/memstorage/main.go @@ -22,8 +22,9 @@ func New() MemStore { return MemStore{} } +var _ keys.Storage = MemStore{} + // assertStorage just makes sure we implement the Storage interface -// nolint [ megacheck, deadcode ] func (s MemStore) assertStorage() keys.Storage { return s } diff --git a/keys/wordlist/wordlist.go b/keys/wordlist/wordlist.go index 915dda102..58ff41181 100644 --- a/keys/wordlist/wordlist.go +++ b/keys/wordlist/wordlist.go @@ -6,7 +6,6 @@ // keys/wordlist/spanish.txt // DO NOT EDIT! -// nolint: goimports package wordlist import ( From 796024f42f215b85ace955538ccbb5c2acddb8ea Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Wed, 4 Oct 2017 18:27:35 -0400 Subject: [PATCH 05/43] linting: little more fixes --- Makefile | 2 +- example_test.go | 10 ++++++---- hd/hd_test.go | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index b1dc51ae0..c7d11ce0a 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,6 @@ metalinter_test: ensure_tools --enable=unused \ --enable=vetshadow \ --enable=vet \ - --enable=unparam \ --enable=varcheck \ ./... @@ -67,3 +66,4 @@ metalinter_test: ensure_tools #--enable=goimports \ #--enable=golint \ <== comments on anything exported #--enable=gotype \ + #--enable=unparam \ diff --git a/example_test.go b/example_test.go index c9cd2688d..fff49c84d 100644 --- a/example_test.go +++ b/example_test.go @@ -15,21 +15,23 @@ package crypto_test import ( - "fmt" +//"fmt" - "github.com/tendermint/go-crypto" +//"github.com/tendermint/go-crypto" ) -func Example_Sha256() { +/* +func example_Sha256() { sum := crypto.Sha256([]byte("This is Tendermint")) fmt.Printf("%x\n", sum) // Output: // f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e } -func Example_Ripemd160() { +func example_Ripemd160() { sum := crypto.Ripemd160([]byte("This is Tendermint")) fmt.Printf("%x\n", sum) // Output: // 051e22663e8f0fd2f2302f1210f954adff009005 } +*/ diff --git a/hd/hd_test.go b/hd/hd_test.go index fcb348b5c..02d80388c 100644 --- a/hd/hd_test.go +++ b/hd/hd_test.go @@ -2,9 +2,9 @@ package hd import ( "bytes" - "crypto/hmac" - "crypto/sha512" - "encoding/binary" + //"crypto/hmac" + //"crypto/sha512" + //"encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -15,10 +15,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/tyler-smith/go-bip39" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/mndrix/btcutil" - "github.com/tyler-smith/go-bip32" + //"github.com/btcsuite/btcd/chaincfg" + //"github.com/btcsuite/btcutil/hdkeychain" + //"github.com/mndrix/btcutil" + //"github.com/tyler-smith/go-bip32" "github.com/tendermint/go-crypto" ) @@ -109,12 +109,14 @@ func TestReverseBytes(t *testing.T) { } } +/* func ifExit(err error, n int) { if err != nil { fmt.Println(n, err) os.Exit(1) } } +*/ func gocrypto(seed []byte) ([]byte, []byte, []byte) { @@ -131,6 +133,7 @@ func gocrypto(seed []byte) ([]byte, []byte, []byte) { return HexDecode(priv), privBytes, pubBytes } +/* func btcsuite(seed []byte) ([]byte, []byte, []byte) { fmt.Println("HD") masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) @@ -207,9 +210,9 @@ func tylerSmith(seed []byte) ([]byte, []byte, []byte) { pub := k.PublicKey().Key return masterKey.Key, priv, pub } +*/ // Benchmarks - var revBytesCases = [][]byte{ nil, []byte(""), From 1775be1cd93549a48bc3453adb4cfb96fa8d1c60 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 5 Oct 2017 22:19:48 -0400 Subject: [PATCH 06/43] remove some assertXxx funcs --- keys/cryptostore/holder.go | 11 +---------- keys/storage/filestorage/main.go | 6 +----- keys/storage/memstorage/main.go | 6 +----- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/keys/cryptostore/holder.go b/keys/cryptostore/holder.go index f4d82583b..44332f175 100644 --- a/keys/cryptostore/holder.go +++ b/keys/cryptostore/holder.go @@ -24,19 +24,10 @@ func New(coder Encoder, store keys.Storage, codec keys.Codec) Manager { } } +// assert Manager satisfies keys.Signer and keys.Manager interfaces var _ keys.Signer = Manager{} var _ keys.Manager = Manager{} -// exists just to make sure we fulfill the Signer interface -func (s Manager) assertSigner() keys.Signer { - return s -} - -// exists just to make sure we fulfill the Manager interface -func (s Manager) assertKeyManager() keys.Manager { - return s -} - // Create adds a new key to the storage engine, returning error if // another key already stored under this name // diff --git a/keys/storage/filestorage/main.go b/keys/storage/filestorage/main.go index e33ca5000..bed80efb3 100644 --- a/keys/storage/filestorage/main.go +++ b/keys/storage/filestorage/main.go @@ -42,13 +42,9 @@ func New(dir string) FileStore { return FileStore{dir} } +// assert FileStore satisfies keys.Storage var _ keys.Storage = FileStore{} -// assertStorage just makes sure we implement the proper Storage interface -func (s FileStore) assertStorage() keys.Storage { - return s -} - // Put creates two files, one with the public info as json, the other // with the (encoded) private key as gpg ascii-armor style func (s FileStore) Put(name string, key []byte, info keys.Info) error { diff --git a/keys/storage/memstorage/main.go b/keys/storage/memstorage/main.go index 6464b1308..a988fe0ff 100644 --- a/keys/storage/memstorage/main.go +++ b/keys/storage/memstorage/main.go @@ -22,13 +22,9 @@ func New() MemStore { return MemStore{} } +// assert MemStore satisfies keys.Storage var _ keys.Storage = MemStore{} -// assertStorage just makes sure we implement the Storage interface -func (s MemStore) assertStorage() keys.Storage { - return s -} - // Put adds the given key, returns an error if it another key // is already stored under this name func (s MemStore) Put(name string, key []byte, info keys.Info) error { From 32dec98c1c78f9b0cb326e10e167a2d1cf12c515 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Tue, 10 Oct 2017 17:33:51 -0400 Subject: [PATCH 07/43] example: fix func suffix --- example_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/example_test.go b/example_test.go index fff49c84d..2c8b94549 100644 --- a/example_test.go +++ b/example_test.go @@ -15,23 +15,21 @@ package crypto_test import ( -//"fmt" + "fmt" -//"github.com/tendermint/go-crypto" + "github.com/tendermint/go-crypto" ) -/* -func example_Sha256() { +func ExampleSha256() { sum := crypto.Sha256([]byte("This is Tendermint")) fmt.Printf("%x\n", sum) // Output: // f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e } -func example_Ripemd160() { +func ExampleRipemd160() { sum := crypto.Ripemd160([]byte("This is Tendermint")) fmt.Printf("%x\n", sum) // Output: // 051e22663e8f0fd2f2302f1210f954adff009005 } -*/ From 8e7f0e7701f92206679ad093d013b9b162427631 Mon Sep 17 00:00:00 2001 From: Adrian Brink <11320251+adrianbrink@users.noreply.github.com> Date: Thu, 12 Oct 2017 14:26:59 +0200 Subject: [PATCH 08/43] Upgrade keys to use bcrypt with salts (#38) This commit adds salts to the library using bcrypt. --- keys/cryptostore/enc_storage.go | 8 +- keys/cryptostore/encoder.go | 59 +++++++----- keys/cryptostore/encoder_test.go | 20 ++-- keys/cryptostore/generator.go | 1 + keys/cryptostore/holder.go | 18 ++-- keys/cryptostore/holder_test.go | 10 +- keys/cryptostore/storage_test.go | 2 +- keys/storage.go | 4 +- keys/storage/filestorage/main.go | 130 ++++++++++++++++++++------ keys/storage/filestorage/main_test.go | 13 +-- keys/storage/memstorage/main.go | 12 ++- keys/storage/memstorage/main_test.go | 13 +-- 12 files changed, 190 insertions(+), 100 deletions(-) diff --git a/keys/cryptostore/enc_storage.go b/keys/cryptostore/enc_storage.go index daeb220b5..e0c7e59a3 100644 --- a/keys/cryptostore/enc_storage.go +++ b/keys/cryptostore/enc_storage.go @@ -12,21 +12,21 @@ type encryptedStorage struct { } func (es encryptedStorage) Put(name, pass string, key crypto.PrivKey) error { - secret, err := es.coder.Encrypt(key, pass) + saltBytes, encBytes, err := es.coder.Encrypt(key, pass) if err != nil { return err } ki := info(name, key) - return es.store.Put(name, secret, ki) + return es.store.Put(name, saltBytes, encBytes, ki) } func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.Info, error) { - secret, info, err := es.store.Get(name) + saltBytes, encBytes, info, err := es.store.Get(name) if err != nil { return crypto.PrivKey{}, info, err } - key, err := es.coder.Decrypt(secret, pass) + key, err := es.coder.Decrypt(saltBytes, encBytes, pass) return key, info, err } diff --git a/keys/cryptostore/encoder.go b/keys/cryptostore/encoder.go index 31cbc2e54..251543b60 100644 --- a/keys/cryptostore/encoder.go +++ b/keys/cryptostore/encoder.go @@ -2,7 +2,9 @@ package cryptostore import ( "github.com/pkg/errors" + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-crypto/bcrypt" ) var ( @@ -16,45 +18,54 @@ var ( // // This should use a well-designed symetric encryption algorithm type Encoder interface { - Encrypt(key crypto.PrivKey, pass string) ([]byte, error) - Decrypt(data []byte, pass string) (crypto.PrivKey, error) -} - -func secret(passphrase string) []byte { - // TODO: Sha256(Bcrypt(passphrase)) - return crypto.Sha256([]byte(passphrase)) + Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte, err error) + Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) } type secretbox struct{} -func (e secretbox) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) { - if pass == "" { - return key.Bytes(), nil +func (e secretbox) Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte, err error) { + if passphrase == "" { + return nil, privKey.Bytes(), nil + } + + saltBytes = crypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016) + if err != nil { + return nil, nil, errors.Wrap(err, "Couldn't generate bcrypt key from passphrase.") } - s := secret(pass) - cipher := crypto.EncryptSymmetric(key.Bytes(), s) - return cipher, nil + key = crypto.Sha256(key) // Get 32 bytes + privKeyBytes := privKey.Bytes() + return saltBytes, crypto.EncryptSymmetric(privKeyBytes, key), nil } -func (e secretbox) Decrypt(data []byte, pass string) (key crypto.PrivKey, err error) { - private := data - if pass != "" { - s := secret(pass) - private, err = crypto.DecryptSymmetric(data, s) +func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { + privKeyBytes := encBytes + // NOTE: Some keys weren't encrypted with a passphrase and hence we have the conditional + if passphrase != "" { + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016) if err != nil { return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase") } + key = crypto.Sha256(key) // Get 32 bytes + privKeyBytes, err = crypto.DecryptSymmetric(encBytes, key) + if err != nil { + return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase") + } + } + privKey, err = crypto.PrivKeyFromBytes(privKeyBytes) + if err != nil { + return crypto.PrivKey{}, errors.Wrap(err, "Couldn't get privKey from bytes") } - key, err = crypto.PrivKeyFromBytes(private) - return key, errors.Wrap(err, "Invalid Passphrase") + return privKey, nil } type noop struct{} -func (n noop) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) { - return key.Bytes(), nil +func (n noop) Encrypt(key crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte, err error) { + return []byte{}, key.Bytes(), nil } -func (n noop) Decrypt(data []byte, pass string) (crypto.PrivKey, error) { - return crypto.PrivKeyFromBytes(data) +func (n noop) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { + return crypto.PrivKeyFromBytes(encBytes) } diff --git a/keys/cryptostore/encoder_test.go b/keys/cryptostore/encoder_test.go index f468591f3..ce1118d20 100644 --- a/keys/cryptostore/encoder_test.go +++ b/keys/cryptostore/encoder_test.go @@ -18,22 +18,22 @@ func TestNoopEncoder(t *testing.T) { key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) key2 := cryptostore.GenSecp256k1.Generate(cmn.RandBytes(16)) - b, err := noop.Encrypt(key, "encode") + _, b, err := noop.Encrypt(key, "encode") require.Nil(err) assert.NotEmpty(b) - b2, err := noop.Encrypt(key2, "encode") + _, b2, err := noop.Encrypt(key2, "encode") require.Nil(err) assert.NotEmpty(b2) assert.NotEqual(b, b2) // note the decode with a different password works - not secure! - pk, err := noop.Decrypt(b, "decode") + pk, err := noop.Decrypt(nil, b, "decode") require.Nil(err) require.NotNil(pk) assert.Equal(key, pk) - pk2, err := noop.Decrypt(b2, "kggugougp") + pk2, err := noop.Decrypt(nil, b2, "kggugougp") require.Nil(err) require.NotNil(pk2) assert.Equal(key2, pk2) @@ -46,17 +46,17 @@ func TestSecretBox(t *testing.T) { key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) pass := "some-special-secret" - b, err := enc.Encrypt(key, pass) + s, b, err := enc.Encrypt(key, pass) require.Nil(err) assert.NotEmpty(b) // decoding with a different pass is an error - pk, err := enc.Decrypt(b, "decode") + pk, err := enc.Decrypt(s, b, "decode") require.NotNil(err) require.True(pk.Empty()) // but decoding with the same passphrase gets us our key - pk, err = enc.Decrypt(b, pass) + pk, err = enc.Decrypt(s, b, pass) require.Nil(err) assert.Equal(key, pk) } @@ -80,11 +80,11 @@ func TestSecretBoxNoPass(t *testing.T) { } for i, tc := range cases { - b, err := enc.Encrypt(key, tc.encode) + s, b, err := enc.Encrypt(key, tc.encode) require.Nil(err, "%d: %+v", i, err) assert.NotEmpty(b, "%d", i) - pk, err := enc.Decrypt(b, tc.decode) + pk, err := enc.Decrypt(s, b, tc.decode) if tc.valid { require.Nil(err, "%d: %+v", i, err) assert.Equal(key, pk, "%d", i) @@ -95,7 +95,7 @@ func TestSecretBoxNoPass(t *testing.T) { // now let's make sure raw bytes also work... b := key.Bytes() - pk, err := enc.Decrypt(b, "") + pk, err := enc.Decrypt(nil, b, "") require.Nil(err, "%+v", err) assert.Equal(key, pk) } diff --git a/keys/cryptostore/generator.go b/keys/cryptostore/generator.go index 0a2bb55c2..1f162ec08 100644 --- a/keys/cryptostore/generator.go +++ b/keys/cryptostore/generator.go @@ -2,6 +2,7 @@ package cryptostore import ( "github.com/pkg/errors" + crypto "github.com/tendermint/go-crypto" ) diff --git a/keys/cryptostore/holder.go b/keys/cryptostore/holder.go index 18437a9b5..2fc617b4e 100644 --- a/keys/cryptostore/holder.go +++ b/keys/cryptostore/holder.go @@ -100,7 +100,7 @@ func (s Manager) List() (keys.Infos, error) { // Get returns the public information about one key func (s Manager) Get(name string) (keys.Info, error) { - _, info, err := s.es.store.Get(name) + _, _, info, err := s.es.store.Get(name) return info, err } @@ -124,21 +124,23 @@ func (s Manager) Sign(name, passphrase string, tx keys.Signable) error { // // This is designed to copy from one device to another, or provide backups // during version updates. -func (s Manager) Export(name, oldpass, transferpass string) ([]byte, error) { +// TODO: How to handle Export with salt? +func (s Manager) Export(name, oldpass, transferpass string) (salt, data []byte, err error) { key, _, err := s.es.Get(name, oldpass) if err != nil { - return nil, err + return nil, nil, err } - res, err := s.es.coder.Encrypt(key, transferpass) - return res, err + salt, data, err = s.es.coder.Encrypt(key, transferpass) + return salt, data, err } // 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 +// If they are valid, it stores the key under the given name with the // new passphrase. -func (s Manager) Import(name, newpass, transferpass string, data []byte) error { - key, err := s.es.coder.Decrypt(data, transferpass) +// TODO: How to handle Import with salt? +func (s Manager) Import(name, newpass, transferpass string, salt, data []byte) error { + key, err := s.es.coder.Decrypt(salt, data, transferpass) if err != nil { return err } diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 434966f46..7484b1517 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -168,7 +168,7 @@ func TestImportUnencrypted(t *testing.T) { pass := "top-secret" // import raw bytes - err := cstore.Import(name, pass, "", key.Bytes()) + err := cstore.Import(name, pass, "", nil, key.Bytes()) require.Nil(err, "%+v", err) // make sure the address matches @@ -209,15 +209,15 @@ func TestAdvancedKeyManagement(t *testing.T) { assertPassword(assert, cstore, n1, p2, p1) // exporting requires the proper name and passphrase - _, err = cstore.Export(n2, p2, pt) + _, _, err = cstore.Export(n2, p2, pt) assert.NotNil(err) - _, err = cstore.Export(n1, p1, pt) + _, _, err = cstore.Export(n1, p1, pt) assert.NotNil(err) - exported, err := cstore.Export(n1, p2, pt) + salt, 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) + err = cstore.Import(n2, p3, p2, salt, exported) assert.NotNil(err) } diff --git a/keys/cryptostore/storage_test.go b/keys/cryptostore/storage_test.go index 907a19f11..468435143 100644 --- a/keys/cryptostore/storage_test.go +++ b/keys/cryptostore/storage_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" + crypto "github.com/tendermint/go-crypto" keys "github.com/tendermint/go-crypto/keys" ) diff --git a/keys/storage.go b/keys/storage.go index 0c25eb8a5..0aba4ebdb 100644 --- a/keys/storage.go +++ b/keys/storage.go @@ -3,8 +3,8 @@ package keys // Storage has many implementation, based on security and sharing requirements // like disk-backed, mem-backed, vault, db, etc. type Storage interface { - Put(name string, key []byte, info Info) error - Get(name string) ([]byte, Info, error) + Put(name string, salt []byte, key []byte, info Info) error + Get(name string) (salt []byte, key []byte, info Info, err error) List() (Infos, error) Delete(name string) error } diff --git a/keys/storage/filestorage/main.go b/keys/storage/filestorage/main.go index 696b200fc..bd29ce41f 100644 --- a/keys/storage/filestorage/main.go +++ b/keys/storage/filestorage/main.go @@ -6,6 +6,7 @@ like standard ssh key storage. package filestorage import ( + "encoding/hex" "fmt" "io/ioutil" "os" @@ -13,19 +14,25 @@ import ( "strings" "github.com/pkg/errors" + crypto "github.com/tendermint/go-crypto" keys "github.com/tendermint/go-crypto/keys" ) const ( + // BlockType is the type of block. BlockType = "Tendermint Light Client" - PrivExt = "tlc" - PubExt = "pub" - keyPerm = os.FileMode(0600) - pubPerm = os.FileMode(0644) - dirPerm = os.FileMode(0700) + // PrivExt is the extension for private keys. + PrivExt = "tlc" + // PubExt is the extensions for public keys. + PubExt = "pub" + + keyPerm = os.FileMode(0600) + pubPerm = os.FileMode(0644) + dirPerm = os.FileMode(0700) ) +// FileStore is a file-based key storage with tight permissions. type FileStore struct { keyDir string } @@ -36,9 +43,11 @@ type FileStore struct { // be created if it doesn't exist already. func New(dir string) FileStore { err := os.MkdirAll(dir, dirPerm) + if err != nil { panic(err) } + return FileStore{dir} } @@ -49,7 +58,7 @@ func (s FileStore) assertStorage() keys.Storage { // Put creates two files, one with the public info as json, the other // with the (encoded) private key as gpg ascii-armor style -func (s FileStore) Put(name string, key []byte, info keys.Info) error { +func (s FileStore) Put(name string, salt, key []byte, info keys.Info) error { pub, priv := s.nameToPaths(name) // write public info @@ -59,31 +68,34 @@ func (s FileStore) Put(name string, key []byte, info keys.Info) error { } // write private info - return write(priv, name, key) + return write(priv, name, salt, key) } // Get loads the info and (encoded) private key from the directory // It uses `name` to generate the filename, and returns an error if the // files don't exist or are in the incorrect format -func (s FileStore) Get(name string) ([]byte, keys.Info, error) { +func (s FileStore) Get(name string) (salt []byte, key []byte, info keys.Info, err error) { pub, priv := s.nameToPaths(name) - info, err := readInfo(pub) + info, err = readInfo(pub) if err != nil { - return nil, info, err + return nil, nil, info, err } - key, _, err := read(priv) - return key, info.Format(), err + salt, key, _, err = read(priv) + return salt, key, info.Format(), err } // List parses the key directory for public info and returns a list of // Info for all keys located in this directory. func (s FileStore) List() (keys.Infos, error) { dir, err := os.Open(s.keyDir) + defer dir.Close() + if err != nil { return nil, errors.Wrap(err, "List Keys") } + names, err := dir.Readdirnames(0) if err != nil { return nil, errors.Wrap(err, "List Keys") @@ -111,61 +123,121 @@ func (s FileStore) List() (keys.Infos, error) { func (s FileStore) Delete(name string) error { pub, priv := s.nameToPaths(name) err := os.Remove(priv) + if err != nil { return errors.Wrap(err, "Deleting Private Key") } + err = os.Remove(pub) + return errors.Wrap(err, "Deleting Public Key") } func (s FileStore) nameToPaths(name string) (pub, priv string) { privName := fmt.Sprintf("%s.%s", name, PrivExt) pubName := fmt.Sprintf("%s.%s", name, PubExt) - return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName) -} -func writeInfo(path string, info keys.Info) error { - return write(path, info.Name, info.PubKey.Bytes()) + return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName) } func readInfo(path string) (info keys.Info, err error) { - var data []byte - data, info.Name, err = read(path) + f, err := os.Open(path) + defer f.Close() + if err != nil { - return + return info, errors.Wrap(err, "Reading data") } - pk, err := crypto.PubKeyFromBytes(data) + + d, err := ioutil.ReadAll(f) + if err != nil { + return info, errors.Wrap(err, "Reading data") + } + + block, headers, key, err := crypto.DecodeArmor(string(d)) + if err != nil { + return info, errors.Wrap(err, "Invalid Armor") + } + + if block != BlockType { + return info, errors.Errorf("Unknown key type: %s", block) + } + + pk, _ := crypto.PubKeyFromBytes(key) + info.Name = headers["name"] info.PubKey = pk - return + + return info, nil } -func read(path string) ([]byte, string, error) { +func read(path string) (salt, key []byte, name string, err error) { f, err := os.Open(path) + defer f.Close() + if err != nil { - return nil, "", errors.Wrap(err, "Reading data") + return nil, nil, "", errors.Wrap(err, "Reading data") } + d, err := ioutil.ReadAll(f) if err != nil { - return nil, "", errors.Wrap(err, "Reading data") + return nil, nil, "", errors.Wrap(err, "Reading data") } + block, headers, key, err := crypto.DecodeArmor(string(d)) if err != nil { - return nil, "", errors.Wrap(err, "Invalid Armor") + return nil, nil, "", errors.Wrap(err, "Invalid Armor") } + if block != BlockType { - return nil, "", errors.Errorf("Unknown key type: %s", block) + return nil, nil, "", errors.Errorf("Unknown key type: %s", block) + } + + if headers["kdf"] != "bcrypt" { + return nil, nil, "", errors.Errorf("Unrecognized KDF type: %v", headers["kdf"]) + } + + if headers["salt"] == "" { + return nil, nil, "", errors.Errorf("Missing salt bytes") } - return key, headers["name"], nil + + salt, err = hex.DecodeString(headers["salt"]) + if err != nil { + return nil, nil, "", errors.Errorf("Error decoding salt: %v", err.Error()) + } + + return salt, key, headers["name"], nil } -func write(path, name string, key []byte) error { +func writeInfo(path string, info keys.Info) error { f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm) + defer f.Close() + if err != nil { return errors.Wrap(err, "Writing data") } + + headers := map[string]string{"name": info.Name} + text := crypto.EncodeArmor(BlockType, headers, info.PubKey.Bytes()) + _, err = f.WriteString(text) + + return errors.Wrap(err, "Writing data") +} + +func write(path, name string, salt, key []byte) error { + f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm) defer f.Close() - headers := map[string]string{"name": name} + + if err != nil { + return errors.Wrap(err, "Writing data") + } + + headers := map[string]string{ + "name": name, + "kdf": "bcrypt", + "salt": fmt.Sprintf("%X", salt), + } + text := crypto.EncodeArmor(BlockType, headers, key) _, err = f.WriteString(text) + return errors.Wrap(err, "Writing data") } diff --git a/keys/storage/filestorage/main_test.go b/keys/storage/filestorage/main_test.go index 28c950c2c..bed46ab1f 100644 --- a/keys/storage/filestorage/main_test.go +++ b/keys/storage/filestorage/main_test.go @@ -22,6 +22,7 @@ func TestBasicCRUD(t *testing.T) { name := "bar" key := []byte("secret-key-here") + salt := []byte("salt-here") pubkey := crypto.GenPrivKeyEd25519().PubKey() info := keys.Info{ Name: name, @@ -29,7 +30,7 @@ func TestBasicCRUD(t *testing.T) { } // No data: Get and Delete return nothing - _, _, err = store.Get(name) + _, _, _, err = store.Get(name) assert.NotNil(err) err = store.Delete(name) assert.NotNil(err) @@ -39,14 +40,14 @@ func TestBasicCRUD(t *testing.T) { assert.Empty(l) // Putting the key in the store must work - err = store.Put(name, key, info) + err = store.Put(name, salt, key, info) assert.Nil(err) // But a second time is a failure - err = store.Put(name, key, info) + err = store.Put(name, salt, key, info) assert.NotNil(err) // Now, we can get and list properly - k, i, err := store.Get(name) + _, k, i, err := store.Get(name) require.Nil(err, "%+v", err) assert.Equal(key, k) assert.Equal(info.Name, i.Name) @@ -58,7 +59,7 @@ func TestBasicCRUD(t *testing.T) { assert.Equal(i, l[0]) // querying a non-existent key fails - _, _, err = store.Get("badname") + _, _, _, err = store.Get("badname") assert.NotNil(err) // We can only delete once @@ -68,7 +69,7 @@ func TestBasicCRUD(t *testing.T) { assert.NotNil(err) // and then Get and List don't work - _, _, err = store.Get(name) + _, _, _, err = store.Get(name) assert.NotNil(err) // List returns empty list l, err = store.List() diff --git a/keys/storage/memstorage/main.go b/keys/storage/memstorage/main.go index 195fa7a17..ddc46afc9 100644 --- a/keys/storage/memstorage/main.go +++ b/keys/storage/memstorage/main.go @@ -7,11 +7,13 @@ package memstorage import ( "github.com/pkg/errors" + keys "github.com/tendermint/go-crypto/keys" ) type data struct { info keys.Info + salt []byte key []byte } @@ -29,22 +31,22 @@ func (s MemStore) assertStorage() keys.Storage { // Put adds the given key, returns an error if it another key // is already stored under this name -func (s MemStore) Put(name string, key []byte, info keys.Info) error { +func (s MemStore) Put(name string, salt, key []byte, info keys.Info) error { if _, ok := s[name]; ok { return errors.Errorf("Key named '%s' already exists", name) } - s[name] = data{info, key} + s[name] = data{info, salt, key} return nil } // Get returns the key stored under the name, or returns an error if not present -func (s MemStore) Get(name string) ([]byte, keys.Info, error) { - var err error +func (s MemStore) Get(name string) (salt, key []byte, info keys.Info, err error) { d, ok := s[name] if !ok { err = errors.Errorf("Key named '%s' doesn't exist", name) } - return d.key, d.info.Format(), err + + return d.salt, d.key, d.info.Format(), err } // List returns the public info of all keys in the MemStore in unsorted order diff --git a/keys/storage/memstorage/main_test.go b/keys/storage/memstorage/main_test.go index feccb387f..01975df58 100644 --- a/keys/storage/memstorage/main_test.go +++ b/keys/storage/memstorage/main_test.go @@ -14,6 +14,7 @@ func TestBasicCRUD(t *testing.T) { name := "foo" key := []byte("secret-key-here") + salt := []byte("salt-here") pubkey := crypto.GenPrivKeyEd25519().PubKey() info := keys.Info{ Name: name, @@ -21,7 +22,7 @@ func TestBasicCRUD(t *testing.T) { } // No data: Get and Delete return nothing - _, _, err := store.Get(name) + _, _, _, err := store.Get(name) assert.NotNil(err) err = store.Delete(name) assert.NotNil(err) @@ -31,14 +32,14 @@ func TestBasicCRUD(t *testing.T) { assert.Empty(l) // Putting the key in the store must work - err = store.Put(name, key, info) + err = store.Put(name, salt, key, info) assert.Nil(err) // But a second time is a failure - err = store.Put(name, key, info) + err = store.Put(name, salt, key, info) assert.NotNil(err) // Now, we can get and list properly - k, i, err := store.Get(name) + _, k, i, err := store.Get(name) assert.Nil(err) assert.Equal(key, k) assert.Equal(info.Name, i.Name) @@ -50,7 +51,7 @@ func TestBasicCRUD(t *testing.T) { assert.Equal(i, l[0]) // querying a non-existent key fails - _, _, err = store.Get("badname") + _, _, _, err = store.Get("badname") assert.NotNil(err) // We can only delete once @@ -60,7 +61,7 @@ func TestBasicCRUD(t *testing.T) { assert.NotNil(err) // and then Get and List don't work - _, _, err = store.Get(name) + _, _, _, err = store.Get(name) assert.NotNil(err) // List returns empty list l, err = store.List() From 47d3fa47419156c3c2f0c6e83d6eb4dfded0eb42 Mon Sep 17 00:00:00 2001 From: Zach Ramsay Date: Mon, 23 Oct 2017 08:30:18 -0400 Subject: [PATCH 09/43] linting: a few fixes --- Makefile | 2 +- embed_test.go | 8 ++++---- keys/cryptostore/holder_test.go | 14 +++++++------- keys/storage/filestorage/main.go | 15 +++++---------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index c7d11ce0a..f775704a9 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ metalinter: ensure_tools metalinter_test: ensure_tools @gometalinter --install gometalinter --vendor --deadline=600s --disable-all \ - --enable=aligncheck \ --enable=deadcode \ --enable=gas \ --enable=goconst \ @@ -49,6 +48,7 @@ metalinter_test: ensure_tools --enable=gosimple \ --enable=ineffassign \ --enable=interfacer \ + --enable=maligned \ --enable=megacheck \ --enable=misspell \ --enable=safesql \ diff --git a/embed_test.go b/embed_test.go index e2d2fe504..e5c37c0c1 100644 --- a/embed_test.go +++ b/embed_test.go @@ -73,8 +73,8 @@ func TestEncodeDemo(t *testing.T) { // Try to encode as binary b, err := data.ToWire(tc.in) if assert.Nil(err, "%d: %#v", i, tc.in) { - err := data.FromWire(b, tc.out) - if assert.Nil(err) { + err2 := data.FromWire(b, tc.out) + if assert.Nil(err2) { assert.Equal(tc.expected, tc.out.String()) } } @@ -82,8 +82,8 @@ func TestEncodeDemo(t *testing.T) { // Try to encode it as json j, err := data.ToJSON(tc.in) if assert.Nil(err, "%d: %#v", i, tc.in) { - err := data.FromJSON(j, tc.out) - if assert.Nil(err) { + err2 := data.FromJSON(j, tc.out) + if assert.Nil(err2) { assert.Equal(tc.expected, tc.out.String()) } } diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 7484b1517..a8fc90989 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -50,22 +50,22 @@ func TestKeyManagement(t *testing.T) { assert.NotNil(err) // list shows them in order - keys, err := cstore.List() + keyS, err := cstore.List() require.Nil(err) - require.Equal(2, len(keys)) + require.Equal(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(n2, keyS[0].Name) + assert.Equal(n1, keyS[1].Name) + assert.Equal(i2.PubKey, keyS[0].PubKey) // deleting a key removes it err = cstore.Delete("bad name", "foo") require.NotNil(err) err = cstore.Delete(n1, p1) require.Nil(err) - keys, err = cstore.List() + keyS, err = cstore.List() require.Nil(err) - assert.Equal(1, len(keys)) + assert.Equal(1, len(keyS)) _, err = cstore.Get(n1) assert.NotNil(err) diff --git a/keys/storage/filestorage/main.go b/keys/storage/filestorage/main.go index 70b7e6251..2bd4dcc1f 100644 --- a/keys/storage/filestorage/main.go +++ b/keys/storage/filestorage/main.go @@ -89,11 +89,10 @@ func (s FileStore) Get(name string) (salt []byte, key []byte, info keys.Info, er // Info for all keys located in this directory. func (s FileStore) List() (keys.Infos, error) { dir, err := os.Open(s.keyDir) - defer dir.Close() - if err != nil { return nil, errors.Wrap(err, "List Keys") } + defer dir.Close() names, err := dir.Readdirnames(0) if err != nil { @@ -141,11 +140,10 @@ func (s FileStore) nameToPaths(name string) (pub, priv string) { func readInfo(path string) (info keys.Info, err error) { f, err := os.Open(path) - defer f.Close() - if err != nil { return info, errors.Wrap(err, "Reading data") } + defer f.Close() d, err := ioutil.ReadAll(f) if err != nil { @@ -170,11 +168,10 @@ func readInfo(path string) (info keys.Info, err error) { func read(path string) (salt, key []byte, name string, err error) { f, err := os.Open(path) - defer f.Close() - if err != nil { return nil, nil, "", errors.Wrap(err, "Reading data") } + defer f.Close() d, err := ioutil.ReadAll(f) if err != nil { @@ -208,11 +205,10 @@ func read(path string) (salt, key []byte, name string, err error) { func writeInfo(path string, info keys.Info) error { f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm) - defer f.Close() - if err != nil { return errors.Wrap(err, "Writing data") } + defer f.Close() headers := map[string]string{"name": info.Name} text := crypto.EncodeArmor(BlockType, headers, info.PubKey.Bytes()) @@ -223,11 +219,10 @@ func writeInfo(path string, info keys.Info) error { func write(path, name string, salt, key []byte) error { f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm) - defer f.Close() - if err != nil { return errors.Wrap(err, "Writing data") } + defer f.Close() headers := map[string]string{ "name": name, From 0781c12ede58390a77f6ea5c7245e120895a6127 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 28 Jul 2017 23:44:16 -0400 Subject: [PATCH 10/43] Message encoding for nano --- nano/sign.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++ nano/sign_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 nano/sign.go create mode 100644 nano/sign_test.go diff --git a/nano/sign.go b/nano/sign.go new file mode 100644 index 000000000..fbe550548 --- /dev/null +++ b/nano/sign.go @@ -0,0 +1,59 @@ +package nano + +import ( + "bytes" + "fmt" + + "github.com/pkg/errors" +) + +const ( + App = 0x80 + Init = 0x00 + Update = 0x01 + Digest = 0x02 + MaxChunk = 253 + KeyLength = 65 + SigLength = 64 +) + +var separator = []byte{0, 0xCA, 0xFE, 0} + +func generateSignRequests(payload []byte) [][]byte { + // nice one-shot + digest := []byte{App, Digest} + if len(payload) < MaxChunk { + return [][]byte{append(digest, payload...)} + } + + // large payload is multi-chunk + result := [][]byte{{App, Init}} + update := []byte{App, Update} + for len(payload) > MaxChunk { + msg := append(update, payload[:MaxChunk]...) + payload = payload[MaxChunk:] + result = append(result, msg) + } + result = append(result, append(update, payload...)) + result = append(result, digest) + return result +} + +func parseDigest(resp []byte) (key, sig []byte, err error) { + if resp[0] != App || resp[1] != Digest { + return nil, nil, errors.New("Invalid header") + } + resp = resp[2:] + if len(resp) != KeyLength+SigLength+len(separator) { + return nil, nil, errors.Errorf("Incorrect length: %d", len(resp)) + } + + key, resp = resp[:KeyLength], resp[KeyLength:] + if !bytes.Equal(separator, resp[:len(separator)]) { + return nil, nil, errors.New("Cannot find 0xCAFE") + } + fmt.Println("successs") + + sig = resp[len(separator):] + return key, sig, nil +} diff --git a/nano/sign_test.go b/nano/sign_test.go new file mode 100644 index 000000000..8029ef828 --- /dev/null +++ b/nano/sign_test.go @@ -0,0 +1,51 @@ +package nano + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseDigest(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cases := []struct { + output string + key string + sig string + valid bool + }{ + { + output: "800204338EB1DD3CCDEE1F6FB586F66E640F56FFDD14537A3F0ED9EEEDF10B528FE4195FD17AC9EDAE9718A50196A1459E2434C1E53F1238F4CFDF177FAFBA8B39249B00CAFE00FFDEA42A699205B217004E7E2FFB884E174A548D644116F4B20469CBC32F60A9CB0EEB5BB6A7F266BD0F6A0A99A45B4F18F0F477AED7C854C404EF43530DAB00", + key: "04338EB1DD3CCDEE1F6FB586F66E640F56FFDD14537A3F0ED9EEEDF10B528FE4195FD17AC9EDAE9718A50196A1459E2434C1E53F1238F4CFDF177FAFBA8B39249B", + sig: "FFDEA42A699205B217004E7E2FFB884E174A548D644116F4B20469CBC32F60A9CB0EEB5BB6A7F266BD0F6A0A99A45B4F18F0F477AED7C854C404EF43530DAB00", + valid: true, + }, + { + output: "800235467890876543525437890796574535467890", + key: "", + sig: "", + valid: false, + }, + } + + for i, tc := range cases { + msg, err := hex.DecodeString(tc.output) + require.Nil(err, "%d: %+v", i, err) + + lKey, lSig, err := parseDigest(msg) + if !tc.valid { + assert.NotNil(err, "%d", i) + } else if assert.Nil(err, "%d: %+v", i, err) { + key, err := hex.DecodeString(tc.key) + require.Nil(err, "%d: %+v", i, err) + sig, err := hex.DecodeString(tc.sig) + require.Nil(err, "%d: %+v", i, err) + + assert.Equal(key, lKey, "%d", i) + assert.Equal(sig, lSig, "%d", i) + } + } +} From 8220d591784cfa064e003c4344dc7d1aa138882a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Sat, 29 Jul 2017 13:35:52 -0400 Subject: [PATCH 11/43] Work on using returned bytes as crypto keys --- nano/sign.go | 19 +++++++++++++++++++ nano/sign_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/nano/sign.go b/nano/sign.go index fbe550548..b98b7f469 100644 --- a/nano/sign.go +++ b/nano/sign.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/pkg/errors" + crypto "github.com/tendermint/go-crypto" ) const ( @@ -57,3 +58,21 @@ func parseDigest(resp []byte) (key, sig []byte, err error) { sig = resp[len(separator):] return key, sig, nil } + +func parseKey(data []byte) (key crypto.PubKey, err error) { + ed := crypto.PubKeyEd25519{} + if len(data) < len(ed) { + return key, errors.Errorf("Key length too short: %d", len(data)) + } + copy(ed[:], data) + return ed.Wrap(), nil +} + +func parseSig(data []byte) (key crypto.Signature, err error) { + ed := crypto.SignatureEd25519{} + if len(data) < len(ed) { + return key, errors.Errorf("Sig length too short: %d", len(data)) + } + copy(ed[:], data) + return ed.Wrap(), nil +} diff --git a/nano/sign_test.go b/nano/sign_test.go index 8029ef828..be1e66c86 100644 --- a/nano/sign_test.go +++ b/nano/sign_test.go @@ -49,3 +49,50 @@ func TestParseDigest(t *testing.T) { } } } + +type cryptoCase struct { + msg string + key string + sig string + valid bool +} + +func toBytes(c cryptoCase) (msg, key, sig []byte, err error) { + msg, err = hex.DecodeString(c.msg) + if err != nil { + return + } + key, err = hex.DecodeString(c.key) + if err != nil { + return + } + sig, err = hex.DecodeString(c.sig) + return +} + +func TestCryptoConvert(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cases := []cryptoCase{ + { + msg: "00", + key: "04338EB1DD3CCDEE1F6FB586F66E640F56FFDD14537A3F0ED9EEEDF10B528FE4195FD17AC9EDAE9718A50196A1459E2434C1E53F1238F4CFDF177FAFBA8B39249B", + sig: "FFDEA42A699205B217004E7E2FFB884E174A548D644116F4B20469CBC32F60A9CB0EEB5BB6A7F266BD0F6A0A99A45B4F18F0F477AED7C854C404EF43530DAB00", + valid: true, + }, + } + + for i, tc := range cases { + msg, key, sig, err := toBytes(tc) + require.Nil(err, "%d: %+v", i, err) + + pk, err := parseKey(key) + require.Nil(err, "%d: %+v", i, err) + psig, err := parseSig(sig) + require.Nil(err, "%d: %+v", i, err) + + // how do i make this valid? + valid := pk.VerifyBytes(msg, psig) + assert.Equal(tc.valid, valid, "%d", i) + } +} From 74878ee3130cff56dcd096a69ed7a3b97e7d233f Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 09:50:07 +0200 Subject: [PATCH 12/43] Update code to work with current nano implementation --- nano/sign.go | 9 ++++++++- nano/sign_test.go | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/nano/sign.go b/nano/sign.go index b98b7f469..50b874d15 100644 --- a/nano/sign.go +++ b/nano/sign.go @@ -2,9 +2,11 @@ package nano import ( "bytes" + "crypto/sha512" "fmt" "github.com/pkg/errors" + crypto "github.com/tendermint/go-crypto" ) @@ -14,7 +16,7 @@ const ( Update = 0x01 Digest = 0x02 MaxChunk = 253 - KeyLength = 65 + KeyLength = 32 SigLength = 64 ) @@ -76,3 +78,8 @@ func parseSig(data []byte) (key crypto.Signature, err error) { copy(ed[:], data) return ed.Wrap(), nil } + +func hashMsg(data []byte) []byte { + res := sha512.Sum512(data) + return res[:] +} diff --git a/nano/sign_test.go b/nano/sign_test.go index be1e66c86..2f94c5e6f 100644 --- a/nano/sign_test.go +++ b/nano/sign_test.go @@ -18,9 +18,9 @@ func TestParseDigest(t *testing.T) { valid bool }{ { - output: "800204338EB1DD3CCDEE1F6FB586F66E640F56FFDD14537A3F0ED9EEEDF10B528FE4195FD17AC9EDAE9718A50196A1459E2434C1E53F1238F4CFDF177FAFBA8B39249B00CAFE00FFDEA42A699205B217004E7E2FFB884E174A548D644116F4B20469CBC32F60A9CB0EEB5BB6A7F266BD0F6A0A99A45B4F18F0F477AED7C854C404EF43530DAB00", - key: "04338EB1DD3CCDEE1F6FB586F66E640F56FFDD14537A3F0ED9EEEDF10B528FE4195FD17AC9EDAE9718A50196A1459E2434C1E53F1238F4CFDF177FAFBA8B39249B", - sig: "FFDEA42A699205B217004E7E2FFB884E174A548D644116F4B20469CBC32F60A9CB0EEB5BB6A7F266BD0F6A0A99A45B4F18F0F477AED7C854C404EF43530DAB00", + output: "80028E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C9300CAFE00787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", + key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", + sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", valid: true, }, { @@ -75,9 +75,9 @@ func TestCryptoConvert(t *testing.T) { cases := []cryptoCase{ { - msg: "00", - key: "04338EB1DD3CCDEE1F6FB586F66E640F56FFDD14537A3F0ED9EEEDF10B528FE4195FD17AC9EDAE9718A50196A1459E2434C1E53F1238F4CFDF177FAFBA8B39249B", - sig: "FFDEA42A699205B217004E7E2FFB884E174A548D644116F4B20469CBC32F60A9CB0EEB5BB6A7F266BD0F6A0A99A45B4F18F0F477AED7C854C404EF43530DAB00", + msg: "F00D", + key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", + sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", valid: true, }, } @@ -91,8 +91,13 @@ func TestCryptoConvert(t *testing.T) { psig, err := parseSig(sig) require.Nil(err, "%d: %+v", i, err) - // how do i make this valid? + // it is not the signature of the message itself valid := pk.VerifyBytes(msg, psig) + assert.NotEqual(tc.valid, valid, "%d", i) + + // but rather of the hash of the msg + hmsg := hashMsg(msg) + valid = pk.VerifyBytes(hmsg, psig) assert.Equal(tc.valid, valid, "%d", i) } } From 06d74b24dd3fc80ff7b63b5dca2afe89142a5558 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 10:37:01 +0200 Subject: [PATCH 13/43] Wrote PrivKey/PubKey for ledger --- nano/keys.go | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 nano/keys.go diff --git a/nano/keys.go b/nano/keys.go new file mode 100644 index 000000000..7b76fa87f --- /dev/null +++ b/nano/keys.go @@ -0,0 +1,150 @@ +package nano + +import ( + "bytes" + "encoding/hex" + + crypto "github.com/tendermint/go-crypto" +) + +// // Implements PrivKey, calling the ledger nano +// type PrivKeyLedger struct{} + +// var _ PrivKeyInner = PrivKeyLedger{} + +// func (privKey PrivKeyLedger) AssertIsPrivKeyInner() {} + +// func (privKey PrivKeyLedger) Bytes() []byte { +// return wire.BinaryBytes(PrivKey{privKey}) +// } + +// func (privKey PrivKeyLedger) Sign(msg []byte) Signature { +// privKeyBytes := [64]byte(privKey) +// signatureBytes := ed25519.Sign(&privKeyBytes, msg) +// return SignatureEd25519(*signatureBytes).Wrap() +// } + +// func (privKey PrivKeyLedger) PubKey() PubKey { +// privKeyBytes := [64]byte(privKey) +// pubBytes := *ed25519.MakePublicKey(&privKeyBytes) +// return PubKeyEd25519(pubBytes).Wrap() +// } + +// func (privKey PrivKeyLedger) Equals(other PrivKey) bool { +// if otherEd, ok := other.Unwrap().(PrivKeyLedger); ok { +// return bytes.Equal(privKey[:], otherEd[:]) +// } else { +// return false +// } +// } + +// MockPrivKeyLedger behaves as the ledger, but stores a pre-packaged call-response +// for use in test cases +type MockPrivKeyLedger struct { + Msg []byte + Pub [KeyLength]byte + Sig [SigLength]byte +} + +// NewMockKey returns +func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedger) { + var err error + pk.Msg, err = hex.DecodeString(msg) + if err != nil { + panic(err) + } + + bpk, err := hex.DecodeString(pubkey) + if err != nil { + panic(err) + } + bsig, err := hex.DecodeString(sig) + if err != nil { + panic(err) + } + + copy(pk.Pub[:], bpk) + copy(pk.Sig[:], bsig) + return pk +} + +var _ crypto.PrivKeyInner = MockPrivKeyLedger{} + +// AssertIsPrivKeyInner fulfils PrivKey Interface +func (pk MockPrivKeyLedger) AssertIsPrivKeyInner() {} + +// Bytes fulfils PrivKey Interface - not supported +func (pk MockPrivKeyLedger) Bytes() []byte { + return nil +} + +// Sign returns a real SignatureLedger, if the msg matches what we expect +func (pk MockPrivKeyLedger) Sign(msg []byte) crypto.Signature { + if !bytes.Equal(pk.Msg, msg) { + panic("Mock key is for different msg") + } + return crypto.SignatureEd25519(pk.Sig).Wrap() +} + +// PubKey returns a real PubKeyLedger, that will verify this signature +func (pk MockPrivKeyLedger) PubKey() crypto.PubKey { + return PubKeyLedger{crypto.PubKeyEd25519(pk.Pub)}.Wrap() +} + +// Equals compares that two Mocks have the same data +func (pk MockPrivKeyLedger) Equals(other crypto.PrivKey) bool { + if mock, ok := other.Unwrap().(MockPrivKeyLedger); ok { + return bytes.Equal(mock.Pub[:], pk.Pub[:]) && + bytes.Equal(mock.Sig[:], pk.Sig[:]) && + bytes.Equal(mock.Msg, pk.Msg) + } + return false +} + +//////////////////////////////////////////// +// pubkey + +// PubKeyLedger works like a normal Ed25519 except a hash before the verify bytes +type PubKeyLedger struct { + crypto.PubKeyEd25519 +} + +// VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand +func (pk PubKeyLedger) VerifyBytes(msg []byte, sig crypto.Signature) bool { + hmsg := hashMsg(msg) + return pk.PubKeyEd25519.VerifyBytes(hmsg, sig) +} + +// Equals implements PubKey interface +func (pk PubKeyLedger) Equals(other crypto.PubKey) bool { + if ledger, ok := other.Unwrap().(PubKeyLedger); ok { + return bytes.Equal(pk.PubKeyEd25519[:], ledger.PubKeyEd25519[:]) + } + return false +} + +/*** registration with go-data ***/ + +func init() { + crypto.PrivKeyMapper. + // RegisterImplementation(PrivKeyLedger{}, "ledger", 0x10). + RegisterImplementation(MockPrivKeyLedger{}, "mock-ledger", 0x11) + + crypto.PubKeyMapper. + RegisterImplementation(PubKeyLedger{}, "ledger", 0x10) +} + +// // Wrap fulfils interface for PrivKey struct +// func (hi PrivKeyLedger) Wrap() crypto.PrivKey { +// return PrivKey{hi} +// } + +// Wrap fulfils interface for PrivKey struct +func (pk MockPrivKeyLedger) Wrap() crypto.PrivKey { + return crypto.PrivKey{pk} +} + +// Wrap fulfils interface for PubKey struct +func (pk PubKeyLedger) Wrap() crypto.PubKey { + return crypto.PubKey{pk} +} From 7c5a10a7d49f2e908237644c496e90fa4fd2eb1d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 10:42:12 +0200 Subject: [PATCH 14/43] Add tests for pub/priv key validation --- nano/keys_test.go | 38 ++++++++++++++++++++++++++++++++++++++ nano/sign.go | 2 +- nano/sign_test.go | 2 +- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 nano/keys_test.go diff --git a/nano/keys_test.go b/nano/keys_test.go new file mode 100644 index 000000000..32a0d444b --- /dev/null +++ b/nano/keys_test.go @@ -0,0 +1,38 @@ +package nano + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLedgerKeys(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // cryptoCase from sign_test + cases := []struct { + msg, pubkey, sig string + valid bool + }{ + { + msg: "F00D", + pubkey: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", + sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", + valid: true, + }, + } + + for i, tc := range cases { + bmsg, err := hex.DecodeString(tc.msg) + require.NoError(err, "%d", i) + + priv := NewMockKey(tc.msg, tc.pubkey, tc.sig) + pub := priv.PubKey() + sig := priv.Sign(bmsg) + + valid := pub.VerifyBytes(bmsg, sig) + assert.Equal(tc.valid, valid, "%d", i) + } +} diff --git a/nano/sign.go b/nano/sign.go index 50b874d15..3f0df9ee5 100644 --- a/nano/sign.go +++ b/nano/sign.go @@ -61,7 +61,7 @@ func parseDigest(resp []byte) (key, sig []byte, err error) { return key, sig, nil } -func parseKey(data []byte) (key crypto.PubKey, err error) { +func parseEdKey(data []byte) (key crypto.PubKey, err error) { ed := crypto.PubKeyEd25519{} if len(data) < len(ed) { return key, errors.Errorf("Key length too short: %d", len(data)) diff --git a/nano/sign_test.go b/nano/sign_test.go index 2f94c5e6f..2521514eb 100644 --- a/nano/sign_test.go +++ b/nano/sign_test.go @@ -86,7 +86,7 @@ func TestCryptoConvert(t *testing.T) { msg, key, sig, err := toBytes(tc) require.Nil(err, "%d: %+v", i, err) - pk, err := parseKey(key) + pk, err := parseEdKey(key) require.Nil(err, "%d: %+v", i, err) psig, err := parseSig(sig) require.Nil(err, "%d: %+v", i, err) From 265e261c6352e5b72d372e4bb8ed4eaff1fa2b09 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 10:47:16 +0200 Subject: [PATCH 15/43] Add more test cases from running the ledger --- nano/keys_test.go | 39 +++++++++++++++++++++++++++++++++++++-- nano/sign_test.go | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/nano/keys_test.go b/nano/keys_test.go index 32a0d444b..b46e4c9ff 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -11,17 +11,52 @@ import ( func TestLedgerKeys(t *testing.T) { assert, require := assert.New(t), require.New(t) - // cryptoCase from sign_test cases := []struct { msg, pubkey, sig string valid bool }{ - { + 0: { msg: "F00D", pubkey: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", valid: true, }, + 1: { + msg: "DEADBEEF", + pubkey: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C", + sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00", + valid: true, + }, + 2: { + msg: "1234567890AA", + pubkey: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA", + sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B", + valid: true, + }, + 3: { + msg: "1234432112344321", + pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: true, + }, + 4: { + msg: "12344321123443", + pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: false, + }, + 5: { + msg: "1234432112344321", + pubkey: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: false, + }, + 6: { + msg: "1234432112344321", + pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: false, + }, } for i, tc := range cases { diff --git a/nano/sign_test.go b/nano/sign_test.go index 2521514eb..89bd7568e 100644 --- a/nano/sign_test.go +++ b/nano/sign_test.go @@ -74,12 +74,48 @@ func TestCryptoConvert(t *testing.T) { assert, require := assert.New(t), require.New(t) cases := []cryptoCase{ - { + 0: { msg: "F00D", key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", valid: true, }, + 1: { + msg: "DEADBEEF", + key: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C", + sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00", + valid: true, + }, + 2: { + msg: "1234567890AA", + key: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA", + sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B", + valid: true, + }, + 3: { + msg: "1234432112344321", + key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: true, + }, + 4: { + msg: "12344321123443", + key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: false, + }, + 5: { + msg: "1234432112344321", + key: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: false, + }, + 6: { + msg: "1234432112344321", + key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120", + sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208", + valid: false, + }, } for i, tc := range cases { @@ -93,7 +129,7 @@ func TestCryptoConvert(t *testing.T) { // it is not the signature of the message itself valid := pk.VerifyBytes(msg, psig) - assert.NotEqual(tc.valid, valid, "%d", i) + assert.False(valid, "%d", i) // but rather of the hash of the msg hmsg := hashMsg(msg) From 623bd803c283d742f28f7ae46058b9b105628fbf Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 11:40:30 +0200 Subject: [PATCH 16/43] write code to actually call ledger app --- nano/keys.go | 117 ++++++++++++++++++++++++++++++++++------------ nano/keys_test.go | 8 ++++ signature.go | 6 +++ 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/nano/keys.go b/nano/keys.go index 7b76fa87f..cd5c90317 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -4,39 +4,91 @@ import ( "bytes" "encoding/hex" + ledger "github.com/ethanfrey/ledger" + crypto "github.com/tendermint/go-crypto" ) -// // Implements PrivKey, calling the ledger nano -// type PrivKeyLedger struct{} +var device *ledger.Ledger + +// getLedger gets a copy of the device, and caches it +func getLedger() (*ledger.Ledger, error) { + var err error + if device == nil { + device, err = ledger.FindLedger() + } + return device, err +} + +func signLedger(device *ledger.Ledger, msg []byte) (pk crypto.PubKey, sig crypto.Signature, err error) { + var resp []byte + + packets := generateSignRequests(msg) + for _, pack := range packets { + resp, err = device.Exchange(pack, 100) + if err != nil { + return pk, sig, err + } + } + + // the last call is the result we want and needs to be parsed + key, bsig, err := parseDigest(resp) + if err != nil { + return pk, sig, err + } + + var b [32]byte + copy(b[:], key) + return PubKeyLedgerFromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil +} + +// PrivKeyLedger implements PrivKey, calling the ledger nano +// we cache the PubKey from the first call to use it later +type PrivKeyLedger struct { + pubKey crypto.PubKey +} + +var _ crypto.PrivKeyInner = &PrivKeyLedger{} + +// AssertIsPrivKeyInner fulfils PrivKey Interface +func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {} -// var _ PrivKeyInner = PrivKeyLedger{} +// Bytes fulfils pk Interface - not supported +func (pk *PrivKeyLedger) Bytes() []byte { + return nil +} -// func (privKey PrivKeyLedger) AssertIsPrivKeyInner() {} +// Sign calls the ledger and stores the pk for future use +func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { + // oh, I wish there was better error handling + dev, err := getLedger() + if err != nil { + panic(err) + } -// func (privKey PrivKeyLedger) Bytes() []byte { -// return wire.BinaryBytes(PrivKey{privKey}) -// } + pub, sig, err := signLedger(dev, msg) + if err != nil { + panic(err) + } -// func (privKey PrivKeyLedger) Sign(msg []byte) Signature { -// privKeyBytes := [64]byte(privKey) -// signatureBytes := ed25519.Sign(&privKeyBytes, msg) -// return SignatureEd25519(*signatureBytes).Wrap() -// } + pk.pubKey = pub + return sig +} -// func (privKey PrivKeyLedger) PubKey() PubKey { -// privKeyBytes := [64]byte(privKey) -// pubBytes := *ed25519.MakePublicKey(&privKeyBytes) -// return PubKeyEd25519(pubBytes).Wrap() -// } +// PubKey returns the stored PubKey +// TODO: query the ledger if not there, once it is not volatile +func (pk *PrivKeyLedger) PubKey() crypto.PubKey { + return pk.pubKey +} -// func (privKey PrivKeyLedger) Equals(other PrivKey) bool { -// if otherEd, ok := other.Unwrap().(PrivKeyLedger); ok { -// return bytes.Equal(privKey[:], otherEd[:]) -// } else { -// return false -// } -// } +// Equals fulfils PrivKey Interface +// TODO: needs to be fixed +func (pk *PrivKeyLedger) Equals(other crypto.PrivKey) bool { + if _, ok := other.Unwrap().(*PrivKeyLedger); ok { + return true + } + return false +} // MockPrivKeyLedger behaves as the ledger, but stores a pre-packaged call-response // for use in test cases @@ -88,7 +140,7 @@ func (pk MockPrivKeyLedger) Sign(msg []byte) crypto.Signature { // PubKey returns a real PubKeyLedger, that will verify this signature func (pk MockPrivKeyLedger) PubKey() crypto.PubKey { - return PubKeyLedger{crypto.PubKeyEd25519(pk.Pub)}.Wrap() + return PubKeyLedgerFromBytes(pk.Pub) } // Equals compares that two Mocks have the same data @@ -109,6 +161,11 @@ type PubKeyLedger struct { crypto.PubKeyEd25519 } +// PubKeyLedgerFromBytes creates a PubKey from the raw bytes +func PubKeyLedgerFromBytes(key [32]byte) crypto.PubKey { + return PubKeyLedger{crypto.PubKeyEd25519(key)}.Wrap() +} + // VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand func (pk PubKeyLedger) VerifyBytes(msg []byte, sig crypto.Signature) bool { hmsg := hashMsg(msg) @@ -127,17 +184,17 @@ func (pk PubKeyLedger) Equals(other crypto.PubKey) bool { func init() { crypto.PrivKeyMapper. - // RegisterImplementation(PrivKeyLedger{}, "ledger", 0x10). + RegisterImplementation(&PrivKeyLedger{}, "ledger", 0x10). RegisterImplementation(MockPrivKeyLedger{}, "mock-ledger", 0x11) crypto.PubKeyMapper. RegisterImplementation(PubKeyLedger{}, "ledger", 0x10) } -// // Wrap fulfils interface for PrivKey struct -// func (hi PrivKeyLedger) Wrap() crypto.PrivKey { -// return PrivKey{hi} -// } +// Wrap fulfils interface for PrivKey struct +func (pk *PrivKeyLedger) Wrap() crypto.PrivKey { + return crypto.PrivKey{pk} +} // Wrap fulfils interface for PrivKey struct func (pk MockPrivKeyLedger) Wrap() crypto.PrivKey { diff --git a/nano/keys_test.go b/nano/keys_test.go index b46e4c9ff..5ceae2afa 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -2,6 +2,7 @@ package nano import ( "encoding/hex" + "os" "testing" "github.com/stretchr/testify/assert" @@ -71,3 +72,10 @@ func TestLedgerKeys(t *testing.T) { assert.Equal(tc.valid, valid, "%d", i) } } + +func TestRealLedger(t *testing.T) { + if os.Getenv("WITH_LEDGER") == "" { + t.Skip("Set WITH_LEDGER to run code on real ledger") + } + // let's try for real.... +} diff --git a/signature.go b/signature.go index 5b1d6cb05..d2ea45132 100644 --- a/signature.go +++ b/signature.go @@ -63,6 +63,12 @@ func (sig *SignatureEd25519) UnmarshalJSON(enc []byte) error { return err } +func SignatureEd25519FromBytes(data []byte) Signature { + var sig SignatureEd25519 + copy(sig[:], data) + return sig.Wrap() +} + //------------------------------------- var _ SignatureInner = SignatureSecp256k1{} From 008dba3af821bacf599da3c282f387fb9394119d Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 11:48:43 +0200 Subject: [PATCH 17/43] Test ledger calls --- nano/keys.go | 5 ++++- nano/keys_test.go | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/nano/keys.go b/nano/keys.go index cd5c90317..55af915cb 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -48,7 +48,10 @@ type PrivKeyLedger struct { pubKey crypto.PubKey } -var _ crypto.PrivKeyInner = &PrivKeyLedger{} +func NewPrivKeyLedger() crypto.PrivKey { + var pk PrivKeyLedger + return pk.Wrap() +} // AssertIsPrivKeyInner fulfils PrivKey Interface func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {} diff --git a/nano/keys_test.go b/nano/keys_test.go index 5ceae2afa..3f2eb9a1f 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -77,5 +77,13 @@ func TestRealLedger(t *testing.T) { if os.Getenv("WITH_LEDGER") == "" { t.Skip("Set WITH_LEDGER to run code on real ledger") } - // let's try for real.... + + priv := NewPrivKeyLedger() + msg := []byte("kuhehfeohg") + + sig := priv.Sign(msg) + pub := priv.PubKey() + + valid := pub.VerifyBytes(msg, sig) + assert.True(t, valid) } From 081f21af119b8d748c42c049e2b81815c93ae323 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 12 Sep 2017 12:57:30 +0200 Subject: [PATCH 18/43] Add glide version for ledger drivers --- glide.lock | 30 ++++++++++++++---------------- glide.yaml | 1 + 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/glide.lock b/glide.lock index 1c23d8bea..29124c3db 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: c0a2db1b80c6b1b8aab31c526ce43e22e49b23c893c78b8fdb8546aa2e7b7cc6 -updated: 2017-09-22T10:21:34.220901552-04:00 +hash: a2243bfd21937edf660778300855e7cb72185164641cb278dbf0c220e8a0f60a +updated: 2017-10-23T17:21:02.40831023+02:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -11,16 +11,16 @@ imports: - chaincfg/chainhash - wire - name: github.com/btcsuite/btcutil - version: 86346b5a958c0cf94186b87855469ae991be501c + version: 66871daeb12123ece012a9628d2798d01195c4b3 subpackages: - base58 - hdkeychain - name: github.com/btcsuite/fastsha256 version: 637e656429416087660c84436a2a035d69d54e2e -- name: github.com/btcsuite/golangcrypto - version: 53f62d9b43e87a6c56975cf862af7edf33a8d0df - subpackages: - - ripemd160 +- name: github.com/ethanfrey/ledger + version: 5e432577be582bd18a3b4a9cd75dae7a317ade36 +- name: github.com/flynn/hid + version: ed06a31c6245d4552e8dbba7e32e5b010b875d65 - name: github.com/go-kit/kit version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8 subpackages: @@ -44,7 +44,7 @@ imports: - name: github.com/gorilla/mux version: bcd8bc72b08df0f70df986b97f95590779502d31 - name: github.com/howeyc/crc16 - version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f + version: 58da63c846043d0bea709c8d47039df06577d6d9 - name: github.com/kr/logfmt version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/pkg/errors @@ -59,12 +59,12 @@ imports: - edwards25519 - extra25519 - name: github.com/tendermint/go-wire - version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb + version: 55ae61f1fc83cfaa57ab7d54250d7a1a2be0b83c subpackages: - data - data/base58 - name: github.com/tendermint/tmlibs - version: bffe6744ec277d60f707ab442e25513617842f8e + version: 8e5266a9ef2527e68a1571f932db8228a331b556 subpackages: - common - log @@ -81,16 +81,14 @@ imports: - ripemd160 - salsa20/salsa - name: gopkg.in/go-playground/validator.v9 - version: d529ee1b0f30352444f507cc6cdac96bfd12decc + version: 6d8c18553ea1ac493d049edd6f102f52e618f085 testImports: +- name: github.com/cmars/basen + version: fe3947df716ebfda9847eb1b9a48f9592e06478c - name: github.com/davecgh/go-spew version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: - spew -- name: github.com/FactomProject/basen - version: fe3947df716ebfda9847eb1b9a48f9592e06478c -- name: github.com/FactomProject/btcutilecc - version: d3a63a5752ecf3fbc06bd97365da752111c263df - name: github.com/mndrix/btcutil version: d3a63a5752ecf3fbc06bd97365da752111c263df - name: github.com/pmezard/go-difflib @@ -103,6 +101,6 @@ testImports: - assert - require - name: github.com/tyler-smith/go-bip32 - version: 2c9cfd17756470a0b7c3e4b7954bae7d11035504 + version: eb790af526c30f23a7c8b00a48e342f9d0bd6386 - name: github.com/tyler-smith/go-bip39 version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc diff --git a/glide.yaml b/glide.yaml index a99d3b63f..9f20ddefc 100644 --- a/glide.yaml +++ b/glide.yaml @@ -30,6 +30,7 @@ import: - package: github.com/spf13/viper - package: gopkg.in/go-playground/validator.v9 - package: github.com/howeyc/crc16 +- package: github.com/ethanfrey/ledger testImport: - package: github.com/mndrix/btcutil - package: github.com/stretchr/testify From 3edeb0cd45a9a8a97dbafb901894a51c8f8af688 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 11:53:14 +0200 Subject: [PATCH 19/43] Ledger caches pubkey, works with newer firmware --- nano/keys.go | 39 +++++++++++++++++++++++++++++++++++---- nano/keys_test.go | 10 ++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/nano/keys.go b/nano/keys.go index 55af915cb..7cac5eee8 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/hex" + "github.com/pkg/errors" + ledger "github.com/ethanfrey/ledger" crypto "github.com/tendermint/go-crypto" @@ -48,9 +50,13 @@ type PrivKeyLedger struct { pubKey crypto.PubKey } -func NewPrivKeyLedger() crypto.PrivKey { +func NewPrivKeyLedger() (crypto.PrivKey, error) { var pk PrivKeyLedger - return pk.Wrap() + // getPubKey will cache the pubkey for later use, + // this allows us to return an error early if the ledger + // is not plugged in + _, err := pk.getPubKey() + return pk.Wrap(), err } // AssertIsPrivKeyInner fulfils PrivKey Interface @@ -74,14 +80,39 @@ func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { panic(err) } - pk.pubKey = pub + // if we have no pubkey yet, store it for future queries + if pk.pubKey.Empty() { + pk.pubKey = pub + } return sig } // PubKey returns the stored PubKey // TODO: query the ledger if not there, once it is not volatile func (pk *PrivKeyLedger) PubKey() crypto.PubKey { - return pk.pubKey + key, err := pk.getPubKey() + if err != nil { + panic(err) + } + return key +} + +// getPubKey reads the pubkey from cache or from the ledger itself +// since this involves IO, it may return an error, which is not exposed +// in the PubKey interface, so this function allows better error handling +func (pk *PrivKeyLedger) getPubKey() (key crypto.PubKey, err error) { + // if we have no pubkey, set it + if pk.pubKey.Empty() { + dev, err := getLedger() + if err != nil { + return key, errors.WithMessage(err, "Can't connect to ledger") + } + pk.pubKey, _, err = signLedger(dev, []byte{0}) + if err != nil { + return key, errors.WithMessage(err, "Can't sign with app") + } + } + return pk.pubKey, nil } // Equals fulfils PrivKey Interface diff --git a/nano/keys_test.go b/nano/keys_test.go index 3f2eb9a1f..3a4d9c1eb 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -74,16 +74,18 @@ func TestLedgerKeys(t *testing.T) { } func TestRealLedger(t *testing.T) { + assert, require := assert.New(t), require.New(t) + if os.Getenv("WITH_LEDGER") == "" { t.Skip("Set WITH_LEDGER to run code on real ledger") } - - priv := NewPrivKeyLedger() msg := []byte("kuhehfeohg") - sig := priv.Sign(msg) + priv, err := NewPrivKeyLedger() + require.Nil(err, "%+v", err) pub := priv.PubKey() + sig := priv.Sign(msg) valid := pub.VerifyBytes(msg, sig) - assert.True(t, valid) + assert.True(valid) } From 0383feab494f1a8a3d4d1327890375fa123f3f07 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 11:59:17 +0200 Subject: [PATCH 20/43] Add and test serialization of ledger privkey --- nano/keys.go | 5 +++-- nano/keys_test.go | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/nano/keys.go b/nano/keys.go index 7cac5eee8..6b337e8b7 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -9,6 +9,7 @@ import ( ledger "github.com/ethanfrey/ledger" crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" ) var device *ledger.Ledger @@ -62,9 +63,9 @@ func NewPrivKeyLedger() (crypto.PrivKey, error) { // AssertIsPrivKeyInner fulfils PrivKey Interface func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {} -// Bytes fulfils pk Interface - not supported +// Bytes fulfils pk Interface - no data, just type info func (pk *PrivKeyLedger) Bytes() []byte { - return nil + return wire.BinaryBytes(pk.Wrap()) } // Sign calls the ledger and stores the pk for future use diff --git a/nano/keys_test.go b/nano/keys_test.go index 3a4d9c1eb..b42b091f0 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" ) func TestLedgerKeys(t *testing.T) { @@ -88,4 +89,19 @@ func TestRealLedger(t *testing.T) { valid := pub.VerifyBytes(msg, sig) assert.True(valid) + + // now, let's serialize the key and make sure it still works + bs := priv.Bytes() + priv2, err := crypto.PrivKeyFromBytes(bs) + require.Nil(err, "%+v", err) + + // make sure we get the same pubkey when we load from disk + pub2 := priv2.PubKey() + require.Equal(pub, pub2) + + // signing with the loaded key should match the original pubkey + sig = priv2.Sign(msg) + valid = pub.VerifyBytes(msg, sig) + assert.True(valid) + } From 8c98c4fdf46aafc3b9d8690902ad38b8835a636a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 12:41:54 +0200 Subject: [PATCH 21/43] Add nano ledger to key manager --- keys/cryptostore/generator.go | 18 ++++++++++++++++++ nano/keys.go | 24 ++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/keys/cryptostore/generator.go b/keys/cryptostore/generator.go index 1f162ec08..c1984fc82 100644 --- a/keys/cryptostore/generator.go +++ b/keys/cryptostore/generator.go @@ -4,6 +4,7 @@ import ( "github.com/pkg/errors" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-crypto/nano" ) var ( @@ -11,6 +12,8 @@ var ( GenEd25519 Generator = GenFunc(genEd25519) // GenSecp256k1 produces Secp256k1 private keys GenSecp256k1 Generator = GenFunc(genSecp256) + // GenLedger used Ed25519 keys stored on nano ledger s with cosmos app + GenLedger Generator = GenFunc(genLedger) ) // Generator determines the type of private key the keystore creates @@ -33,12 +36,25 @@ func genSecp256(secret []byte) crypto.PrivKey { return crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap() } +// secret is completely ignored for the ledger... +// just for interface compatibility +func genLedger(secret []byte) crypto.PrivKey { + key, err := nano.NewPrivKeyLedger() + if err != nil { + // TODO: cleaner error handling + panic(err) + } + return key +} + func getGenerator(algo string) (Generator, error) { switch algo { case crypto.NameEd25519: return GenEd25519, nil case crypto.NameSecp256k1: return GenSecp256k1, nil + case nano.NameLedger: + return GenLedger, nil default: return nil, errors.Errorf("Cannot generate keys for algorithm: %s", algo) } @@ -50,6 +66,8 @@ func getGeneratorByType(typ byte) (Generator, error) { return GenEd25519, nil case crypto.TypeSecp256k1: return GenSecp256k1, nil + case nano.TypeLedger: + return GenLedger, nil default: return nil, errors.Errorf("Cannot generate keys for algorithm: %X", typ) } diff --git a/nano/keys.go b/nano/keys.go index 6b337e8b7..2ab4d59a8 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -12,6 +12,11 @@ import ( wire "github.com/tendermint/go-wire" ) +const ( + NameLedger = "ledger" + TypeLedger = 0x10 +) + var device *ledger.Ledger // getLedger gets a copy of the device, and caches it @@ -48,7 +53,10 @@ func signLedger(device *ledger.Ledger, msg []byte) (pk crypto.PubKey, sig crypto // PrivKeyLedger implements PrivKey, calling the ledger nano // we cache the PubKey from the first call to use it later type PrivKeyLedger struct { - pubKey crypto.PubKey + // PubKey should be private, but we want to encode it via go-wire + // so we can view the address later, even without having the ledger + // attached + CachedPubKey crypto.PubKey } func NewPrivKeyLedger() (crypto.PrivKey, error) { @@ -82,8 +90,8 @@ func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { } // if we have no pubkey yet, store it for future queries - if pk.pubKey.Empty() { - pk.pubKey = pub + if pk.CachedPubKey.Empty() { + pk.CachedPubKey = pub } return sig } @@ -103,17 +111,17 @@ func (pk *PrivKeyLedger) PubKey() crypto.PubKey { // in the PubKey interface, so this function allows better error handling func (pk *PrivKeyLedger) getPubKey() (key crypto.PubKey, err error) { // if we have no pubkey, set it - if pk.pubKey.Empty() { + if pk.CachedPubKey.Empty() { dev, err := getLedger() if err != nil { return key, errors.WithMessage(err, "Can't connect to ledger") } - pk.pubKey, _, err = signLedger(dev, []byte{0}) + pk.CachedPubKey, _, err = signLedger(dev, []byte{0}) if err != nil { return key, errors.WithMessage(err, "Can't sign with app") } } - return pk.pubKey, nil + return pk.CachedPubKey, nil } // Equals fulfils PrivKey Interface @@ -219,11 +227,11 @@ func (pk PubKeyLedger) Equals(other crypto.PubKey) bool { func init() { crypto.PrivKeyMapper. - RegisterImplementation(&PrivKeyLedger{}, "ledger", 0x10). + RegisterImplementation(&PrivKeyLedger{}, NameLedger, TypeLedger). RegisterImplementation(MockPrivKeyLedger{}, "mock-ledger", 0x11) crypto.PubKeyMapper. - RegisterImplementation(PubKeyLedger{}, "ledger", 0x10) + RegisterImplementation(PubKeyLedger{}, NameLedger, TypeLedger) } // Wrap fulfils interface for PrivKey struct From 34b9309f245df9bfdcd2697e7cd148f57e57fa2a Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 13:13:38 +0200 Subject: [PATCH 22/43] Re-enable signing tests with cryptostore --- keys/cryptostore/holder_test.go | 118 +++++++++++++++++--------------- keys/transactions.go | 51 ++++++++++++++ 2 files changed, 112 insertions(+), 57 deletions(-) diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index a8fc90989..d9e2783af 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -84,65 +84,69 @@ func TestKeyManagement(t *testing.T) { // TestSignVerify does some detailed checks on how we sign and validate // signatures -// func TestSignVerify(t *testing.T) { -// assert, require := assert.New(t), require.New(t) +func TestSignVerify(t *testing.T) { + assert, require := assert.New(t), require.New(t) -// // make the storage with reasonable defaults -// cstore := cryptostore.New( -// cryptostore.GenSecp256k1, -// cryptostore.SecretBox, -// memstorage.New(), -// ) + // make the storage with reasonable defaults + cstore := cryptostore.New( + cryptostore.SecretBox, + memstorage.New(), + keys.MustLoadCodec("english"), + ) + algo := crypto.NameSecp256k1 -// n1, n2 := "some dude", "a dudette" -// p1, p2 := "1234", "foobar" - -// // create two users and get their info -// err := cstore.Create(n1, p1) -// require.Nil(err) -// i1, err := cstore.Get(n1) -// require.Nil(err) - -// err = cstore.Create(n2, p2) -// require.Nil(err) -// i2, err := cstore.Get(n2) -// require.Nil(err) - -// // let's try to sign some messages -// d1 := []byte("my first message") -// d2 := []byte("some other important info!") - -// // try signing both data with both keys... -// s11, err := cstore.Signature(n1, p1, d1) -// require.Nil(err) -// s12, err := cstore.Signature(n1, p1, d2) -// require.Nil(err) -// s21, err := cstore.Signature(n2, p2, d1) -// require.Nil(err) -// s22, err := cstore.Signature(n2, p2, d2) -// require.Nil(err) - -// // let's try to validate and make sure it only works when everything is proper -// keys := [][]byte{i1.PubKey, i2.PubKey} -// data := [][]byte{d1, d2} -// sigs := [][]byte{s11, s12, s21, s22} - -// // loop over keys and data -// for k := 0; k < 2; k++ { -// for d := 0; d < 2; d++ { -// // make sure only the proper sig works -// good := 2*k + d -// for s := 0; s < 4; s++ { -// err = cstore.Verify(data[d], sigs[s], keys[k]) -// if s == good { -// assert.Nil(err, "%+v", err) -// } else { -// assert.NotNil(err) -// } -// } -// } -// } -// } + 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) + + i2, _, err := cstore.Create(n2, p2, algo) + require.Nil(err) + + // let's try to sign some messages + d1 := []byte("my first message") + d2 := []byte("some other important info!") + + // try signing both data with both keys... + s11 := keys.NewMockSignable(d1) + err = cstore.Sign(n1, p1, s11) + require.Nil(err) + s12 := keys.NewMockSignable(d2) + err = cstore.Sign(n1, p1, s12) + require.Nil(err) + s21 := keys.NewMockSignable(d1) + err = cstore.Sign(n2, p2, s21) + require.Nil(err) + s22 := keys.NewMockSignable(d2) + err = cstore.Sign(n2, p2, s22) + require.Nil(err) + + // let's try to validate and make sure it only works when everything is proper + cases := []struct { + key crypto.PubKey + data []byte + sig crypto.Signature + valid bool + }{ + // proper matches + {i1.PubKey, d1, s11.Signature, true}, + // change data, pubkey, or signature leads to fail + {i1.PubKey, d2, s11.Signature, false}, + {i2.PubKey, d1, s11.Signature, false}, + {i1.PubKey, d1, s21.Signature, false}, + // make sure other successes + {i1.PubKey, d2, s12.Signature, true}, + {i2.PubKey, d1, s21.Signature, true}, + {i2.PubKey, d2, s22.Signature, true}, + } + + for i, tc := range cases { + valid := tc.key.VerifyBytes(tc.data, tc.sig) + assert.Equal(tc.valid, valid, "%d", i) + } +} func assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) { err := cstore.Update(name, badpass, pass) diff --git a/keys/transactions.go b/keys/transactions.go index 10da7a6fa..1834ada26 100644 --- a/keys/transactions.go +++ b/keys/transactions.go @@ -1,9 +1,11 @@ package keys import ( + "fmt" "sort" crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" data "github.com/tendermint/go-wire/data" ) @@ -72,3 +74,52 @@ type Manager interface { Update(name, oldpass, newpass string) error Delete(name, passphrase string) error } + +/**** MockSignable allows us to view data ***/ + +// MockSignable lets us wrap arbitrary data with a go-crypto signature +type MockSignable struct { + Data []byte + PubKey crypto.PubKey + Signature crypto.Signature +} + +var _ Signable = &MockSignable{} + +// NewMockSignable sets the data to sign +func NewMockSignable(data []byte) *MockSignable { + return &MockSignable{Data: data} +} + +// TxBytes returns the full data with signatures +func (s *MockSignable) TxBytes() ([]byte, error) { + return wire.BinaryBytes(s), nil +} + +// SignBytes returns the original data passed into `NewSig` +func (s *MockSignable) SignBytes() []byte { + return s.Data +} + +// Sign will add a signature and pubkey. +// +// Depending on the Signable, one may be able to call this multiple times for multisig +// Returns error if called with invalid data or too many times +func (s *MockSignable) Sign(pubkey crypto.PubKey, sig crypto.Signature) error { + s.PubKey = pubkey + s.Signature = sig + return nil +} + +// Signers will return the public key(s) that signed if the signature +// is valid, or an error if there is any issue with the signature, +// including if there are no signatures +func (s *MockSignable) Signers() ([]crypto.PubKey, error) { + if s.PubKey.Empty() { + return nil, fmt.Errorf("no signers") + } + if !s.PubKey.VerifyBytes(s.SignBytes(), s.Signature) { + return nil, fmt.Errorf("invalid signature") + } + return []crypto.PubKey{s.PubKey}, nil +} From 88475230c46274bfde4f58649d3228622b7c8eba Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 13:23:18 +0200 Subject: [PATCH 23/43] Re-enabled crypostore example usage --- keys/cryptostore/holder_test.go | 100 ++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index d9e2783af..59d3663ae 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -1,6 +1,8 @@ package cryptostore_test import ( + "bytes" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -260,45 +262,59 @@ func TestSeedPhrase(t *testing.T) { assert.Equal(info.PubKey, newInfo.PubKey) } -// func ExampleStore() { -// // Select the encryption and storage for your cryptostore -// cstore := cryptostore.New( -// cryptostore.GenEd25519, -// cryptostore.SecretBox, -// // Note: use filestorage.New(dir) for real data -// memstorage.New(), -// ) - -// // Add keys and see they return in alphabetical order -// cstore.Create("Bob", "friend") -// cstore.Create("Alice", "secret") -// cstore.Create("Carl", "mitm") -// info, _ := cstore.List() -// for _, i := range info { -// fmt.Println(i.Name) -// } - -// // We need to use passphrase to generate a signature -// tx := mock.NewSig([]byte("deadbeef")) -// err := cstore.Sign("Bob", "friend", tx) -// if err != nil { -// fmt.Println("don't accept real passphrase") -// } - -// // and we can validate the signature with publically available info -// binfo, _ := cstore.Get("Bob") -// sigs, err := tx.Signers() -// if err != nil { -// fmt.Println("badly signed") -// } else if bytes.Equal(sigs[0].Bytes(), binfo.PubKey.Bytes()) { -// fmt.Println("signed by Bob") -// } else { -// fmt.Println("signed by someone else") -// } - -// // Output: -// // Alice -// // Bob -// // Carl -// // signed by Bob -// } +func ExampleStore() { + // Select the encryption and storage for your cryptostore + cstore := cryptostore.New( + cryptostore.SecretBox, + // Note: use filestorage.New(dir) for real data + memstorage.New(), + keys.MustLoadCodec("english"), + ) + ed := crypto.NameEd25519 + sec := crypto.NameSecp256k1 + + // Add keys and see they return in alphabetical order + bob, _, err := cstore.Create("Bob", "friend", ed) + if err != nil { + // this should never happen + fmt.Println(err) + } else { + // return info here just like in List + fmt.Println(bob.Name) + } + cstore.Create("Alice", "secret", sec) + cstore.Create("Carl", "mitm", ed) + info, _ := cstore.List() + for _, i := range info { + fmt.Println(i.Name) + } + + // We need to use passphrase to generate a signature + tx := keys.NewMockSignable([]byte("deadbeef")) + err = cstore.Sign("Bob", "friend", tx) + if err != nil { + fmt.Println("don't accept real passphrase") + } + + // and we can validate the signature with publically available info + binfo, _ := cstore.Get("Bob") + if !binfo.PubKey.Equals(bob.PubKey) { + fmt.Println("Get and Create return different keys") + } + + sigs, err := tx.Signers() + if err != nil { + fmt.Println("badly signed") + } else if bytes.Equal(sigs[0].Bytes(), binfo.PubKey.Bytes()) { + fmt.Println("signed by Bob") + } else { + fmt.Println("signed by someone else") + } + + // Output: + // Bob + // Alice + // Bob + // Carl + // signed by Bob +} From 91fccb8b14574c7a7041ed221663bcdc3f225939 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 13:34:04 +0200 Subject: [PATCH 24/43] Add test for cryptostore working with ledger --- keys/cryptostore/holder_test.go | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 59d3663ae..dbd269717 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -3,6 +3,7 @@ package cryptostore_test import ( "bytes" "fmt" + "os" "testing" "github.com/stretchr/testify/assert" @@ -14,6 +15,7 @@ import ( "github.com/tendermint/go-crypto/keys" "github.com/tendermint/go-crypto/keys/cryptostore" "github.com/tendermint/go-crypto/keys/storage/memstorage" + "github.com/tendermint/go-crypto/nano" ) // TestKeyManagement makes sure we can manipulate these keys well @@ -150,6 +152,59 @@ func TestSignVerify(t *testing.T) { } } +// 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 := assert.New(t), require.New(t) + if os.Getenv("WITH_LEDGER") == "" { + t.Skip("Set WITH_LEDGER to run code on real ledger") + } + + // make the storage with reasonable defaults + cstore := cryptostore.New( + cryptostore.SecretBox, + memstorage.New(), + keys.MustLoadCodec("english"), + ) + n := "nano-s" + p := "hard2hack" + + // create a nano user + c, _, err := cstore.Create(n, p, nano.NameLedger) + require.Nil(err, "%+v", err) + assert.Equal(c.Name, n) + _, ok := c.PubKey.Unwrap().(nano.PubKeyLedger) + require.True(ok) + + // make sure we can get it back + info, err := cstore.Get(n) + require.Nil(err, "%+v", err) + assert.Equal(info.Name, n) + key := info.PubKey + require.False(key.Empty()) + + // let's try to sign some messages + d1 := []byte("welcome to cosmos") + d2 := []byte("please turn on the app") + + // try signing both data with the ledger... + s1 := keys.NewMockSignable(d1) + err = cstore.Sign(n, p, s1) + require.Nil(err) + s2 := keys.NewMockSignable(d2) + err = cstore.Sign(n, p, s2) + require.Nil(err) + + // now, let's check those signatures work + assert.True(key.VerifyBytes(d1, s1.Signature)) + assert.True(key.VerifyBytes(d2, s2.Signature)) + // and mismatched signatures don't + assert.False(key.VerifyBytes(d1, s2.Signature)) +} + func assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) { err := cstore.Update(name, badpass, pass) assert.NotNil(err) From 085d72d212b72aea196ffd1b32f06df908b09a9b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Wed, 13 Sep 2017 13:53:08 +0200 Subject: [PATCH 25/43] Cleanup nano calls, cannot run parallel test on one hardware device --- Makefile | 2 +- keys/cryptostore/holder_test.go | 1 + nano/keys.go | 2 ++ nano/sign.go | 2 -- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f775704a9..c1974c410 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ REPO:=github.com/tendermint/go-crypto all: get_vendor_deps metalinter_test test test: - go test `glide novendor` + go test -p 1 `glide novendor` get_vendor_deps: ensure_tools @rm -rf vendor/ diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index dbd269717..f0dc77c4d 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -185,6 +185,7 @@ func TestSignWithLedger(t *testing.T) { assert.Equal(info.Name, n) key := info.PubKey require.False(key.Empty()) + require.True(key.Equals(c.PubKey)) // let's try to sign some messages d1 := []byte("welcome to cosmos") diff --git a/nano/keys.go b/nano/keys.go index 2ab4d59a8..c0eccc492 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -92,6 +92,8 @@ func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { // if we have no pubkey yet, store it for future queries if pk.CachedPubKey.Empty() { pk.CachedPubKey = pub + } else if !pk.CachedPubKey.Equals(pub) { + panic("signed with a different key than stored") } return sig } diff --git a/nano/sign.go b/nano/sign.go index 3f0df9ee5..874d134e8 100644 --- a/nano/sign.go +++ b/nano/sign.go @@ -3,7 +3,6 @@ package nano import ( "bytes" "crypto/sha512" - "fmt" "github.com/pkg/errors" @@ -55,7 +54,6 @@ func parseDigest(resp []byte) (key, sig []byte, err error) { if !bytes.Equal(separator, resp[:len(separator)]) { return nil, nil, errors.New("Cannot find 0xCAFE") } - fmt.Println("successs") sig = resp[len(separator):] return key, sig, nil From ae078ee915c8387bae25fd9104754a7b2342e68b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 16:35:26 +0200 Subject: [PATCH 26/43] Generate/recover can return error, not panic on ledger --- keys/cryptostore/encoder_test.go | 12 ++++-- keys/cryptostore/generator.go | 63 ++++++++++++++++++++------------ keys/cryptostore/holder.go | 13 ++++--- keys/cryptostore/holder_test.go | 6 ++- keys/cryptostore/storage_test.go | 5 ++- 5 files changed, 62 insertions(+), 37 deletions(-) diff --git a/keys/cryptostore/encoder_test.go b/keys/cryptostore/encoder_test.go index ce1118d20..9cde95a12 100644 --- a/keys/cryptostore/encoder_test.go +++ b/keys/cryptostore/encoder_test.go @@ -15,8 +15,10 @@ func TestNoopEncoder(t *testing.T) { assert, require := assert.New(t), require.New(t) noop := cryptostore.Noop - key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) - key2 := cryptostore.GenSecp256k1.Generate(cmn.RandBytes(16)) + key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + require.NoError(err) + key2, err := cryptostore.GenSecp256k1.Generate(cmn.RandBytes(16)) + require.NoError(err) _, b, err := noop.Encrypt(key, "encode") require.Nil(err) @@ -43,7 +45,8 @@ func TestSecretBox(t *testing.T) { assert, require := assert.New(t), require.New(t) enc := cryptostore.SecretBox - key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + require.NoError(err) pass := "some-special-secret" s, b, err := enc.Encrypt(key, pass) @@ -65,7 +68,8 @@ func TestSecretBoxNoPass(t *testing.T) { assert, require := assert.New(t), require.New(t) enc := cryptostore.SecretBox - key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + require.NoError(err) cases := []struct { encode string diff --git a/keys/cryptostore/generator.go b/keys/cryptostore/generator.go index c1984fc82..5a05b7e6e 100644 --- a/keys/cryptostore/generator.go +++ b/keys/cryptostore/generator.go @@ -18,57 +18,72 @@ var ( // Generator determines the type of private key the keystore creates type Generator interface { - Generate(secret []byte) crypto.PrivKey + Generate(secret []byte) (crypto.PrivKey, error) } // GenFunc is a helper to transform a function into a Generator -type GenFunc func(secret []byte) crypto.PrivKey +type GenFunc func(secret []byte) (crypto.PrivKey, error) -func (f GenFunc) Generate(secret []byte) crypto.PrivKey { +func (f GenFunc) Generate(secret []byte) (crypto.PrivKey, error) { return f(secret) } -func genEd25519(secret []byte) crypto.PrivKey { - return crypto.GenPrivKeyEd25519FromSecret(secret).Wrap() +func genEd25519(secret []byte) (crypto.PrivKey, error) { + key := crypto.GenPrivKeyEd25519FromSecret(secret).Wrap() + return key, nil } -func genSecp256(secret []byte) crypto.PrivKey { - return crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap() +func genSecp256(secret []byte) (crypto.PrivKey, error) { + key := crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap() + return key, nil } // secret is completely ignored for the ledger... // just for interface compatibility -func genLedger(secret []byte) crypto.PrivKey { - key, err := nano.NewPrivKeyLedger() - if err != nil { - // TODO: cleaner error handling - panic(err) - } - return key +func genLedger(secret []byte) (crypto.PrivKey, error) { + return nano.NewPrivKeyLedger() +} + +type genInvalidByte struct { + typ byte +} + +func (g genInvalidByte) Generate(secret []byte) (crypto.PrivKey, error) { + err := errors.Errorf("Cannot generate keys for algorithm: %X", g.typ) + return crypto.PrivKey{}, err +} + +type genInvalidAlgo struct { + algo string +} + +func (g genInvalidAlgo) Generate(secret []byte) (crypto.PrivKey, error) { + err := errors.Errorf("Cannot generate keys for algorithm: %s", g.algo) + return crypto.PrivKey{}, err } -func getGenerator(algo string) (Generator, error) { +func getGenerator(algo string) Generator { switch algo { case crypto.NameEd25519: - return GenEd25519, nil + return GenEd25519 case crypto.NameSecp256k1: - return GenSecp256k1, nil + return GenSecp256k1 case nano.NameLedger: - return GenLedger, nil + return GenLedger default: - return nil, errors.Errorf("Cannot generate keys for algorithm: %s", algo) + return genInvalidAlgo{algo} } } -func getGeneratorByType(typ byte) (Generator, error) { +func getGeneratorByType(typ byte) Generator { switch typ { case crypto.TypeEd25519: - return GenEd25519, nil + return GenEd25519 case crypto.TypeSecp256k1: - return GenSecp256k1, nil + return GenSecp256k1 case nano.TypeLedger: - return GenLedger, nil + return GenLedger default: - return nil, errors.Errorf("Cannot generate keys for algorithm: %X", typ) + return genInvalidByte{typ} } } diff --git a/keys/cryptostore/holder.go b/keys/cryptostore/holder.go index cb8a2e149..923190c1b 100644 --- a/keys/cryptostore/holder.go +++ b/keys/cryptostore/holder.go @@ -33,14 +33,15 @@ var _ keys.Manager = Manager{} // // algo must be a supported go-crypto algorithm: ed25519, secp256k1 func (s Manager) Create(name, passphrase, algo string) (keys.Info, string, error) { - gen, err := getGenerator(algo) + // 128-bits are the all the randomness we can make use of + secret := crypto.CRandBytes(16) + gen := getGenerator(algo) + + key, err := gen.Generate(secret) if err != nil { return keys.Info{}, "", err } - // 128-bits are the all the randomness we can make use of - secret := crypto.CRandBytes(16) - key := gen.Generate(secret) err = s.es.Put(name, passphrase, key) if err != nil { return keys.Info{}, "", err @@ -74,11 +75,11 @@ func (s Manager) Recover(name, passphrase, seedphrase string) (keys.Info, error) l := len(secret) secret, typ := secret[:l-1], secret[l-1] - gen, err := getGeneratorByType(typ) + gen := getGeneratorByType(typ) + key, err := gen.Generate(secret) if err != nil { return keys.Info{}, err } - key := gen.Generate(secret) // d00d, it worked! create the bugger.... err = s.es.Put(name, passphrase, key) diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index f0dc77c4d..80ebcc528 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -224,13 +224,15 @@ func TestImportUnencrypted(t *testing.T) { keys.MustLoadCodec("english"), ) - key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + require.NoError(err) + addr := key.PubKey().Address() name := "john" pass := "top-secret" // import raw bytes - err := cstore.Import(name, pass, "", nil, key.Bytes()) + err = cstore.Import(name, pass, "", nil, key.Bytes()) require.Nil(err, "%+v", err) // make sure the address matches diff --git a/keys/cryptostore/storage_test.go b/keys/cryptostore/storage_test.go index 468435143..23931c294 100644 --- a/keys/cryptostore/storage_test.go +++ b/keys/cryptostore/storage_test.go @@ -14,7 +14,10 @@ import ( func TestSortKeys(t *testing.T) { assert := assert.New(t) - gen := func() crypto.PrivKey { return GenEd25519.Generate(cmn.RandBytes(16)) } + gen := func() crypto.PrivKey { + key, _ := GenEd25519.Generate(cmn.RandBytes(16)) + return key + } assert.NotEqual(gen(), gen()) // alphabetical order is n3, n1, n2 From 1b8d52bb8258c1490e3bc57a30b8b18a2cb25d40 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 16:38:57 +0200 Subject: [PATCH 27/43] Rename NameLedger to NameLedgerEd25519 --- keys/cryptostore/generator.go | 4 ++-- keys/cryptostore/holder_test.go | 2 +- nano/keys.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/keys/cryptostore/generator.go b/keys/cryptostore/generator.go index 5a05b7e6e..6b2d47a1a 100644 --- a/keys/cryptostore/generator.go +++ b/keys/cryptostore/generator.go @@ -68,7 +68,7 @@ func getGenerator(algo string) Generator { return GenEd25519 case crypto.NameSecp256k1: return GenSecp256k1 - case nano.NameLedger: + case nano.NameLedgerEd25519: return GenLedger default: return genInvalidAlgo{algo} @@ -81,7 +81,7 @@ func getGeneratorByType(typ byte) Generator { return GenEd25519 case crypto.TypeSecp256k1: return GenSecp256k1 - case nano.TypeLedger: + case nano.TypeLedgerEd25519: return GenLedger default: return genInvalidByte{typ} diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 80ebcc528..831fa251e 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -173,7 +173,7 @@ func TestSignWithLedger(t *testing.T) { p := "hard2hack" // create a nano user - c, _, err := cstore.Create(n, p, nano.NameLedger) + c, _, err := cstore.Create(n, p, nano.NameLedgerEd25519) require.Nil(err, "%+v", err) assert.Equal(c.Name, n) _, ok := c.PubKey.Unwrap().(nano.PubKeyLedger) diff --git a/nano/keys.go b/nano/keys.go index c0eccc492..bd56af6db 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -13,8 +13,8 @@ import ( ) const ( - NameLedger = "ledger" - TypeLedger = 0x10 + NameLedgerEd25519 = "ledger" + TypeLedgerEd25519 = 0x10 ) var device *ledger.Ledger @@ -229,11 +229,11 @@ func (pk PubKeyLedger) Equals(other crypto.PubKey) bool { func init() { crypto.PrivKeyMapper. - RegisterImplementation(&PrivKeyLedger{}, NameLedger, TypeLedger). + RegisterImplementation(&PrivKeyLedger{}, NameLedgerEd25519, TypeLedgerEd25519). RegisterImplementation(MockPrivKeyLedger{}, "mock-ledger", 0x11) crypto.PubKeyMapper. - RegisterImplementation(PubKeyLedger{}, NameLedger, TypeLedger) + RegisterImplementation(PubKeyLedger{}, NameLedgerEd25519, TypeLedgerEd25519) } // Wrap fulfils interface for PrivKey struct From bce88a20df7d06408453b99127e8b09e1b385933 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 17:11:12 +0200 Subject: [PATCH 28/43] Enable privkey validity checking on load --- priv_key.go | 16 +++++++++++- priv_key_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 priv_key_test.go diff --git a/priv_key.go b/priv_key.go index 0c6bd2ae7..e6e7ac036 100644 --- a/priv_key.go +++ b/priv_key.go @@ -13,13 +13,27 @@ import ( func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) { err = wire.ReadBinaryBytes(privKeyBytes, &privKey) + if err == nil { + // add support for a ValidateKey method on PrivKeys + // to make sure they load correctly + val, ok := privKey.Unwrap().(validatable) + if ok { + err = val.ValidateKey() + } + } return } +// validatable is an optional interface for keys that want to +// check integrity +type validatable interface { + ValidateKey() error +} + //---------------------------------------- // DO NOT USE THIS INTERFACE. -// You probably want to use PubKey +// You probably want to use PrivKey // +gen wrapper:"PrivKey,Impl[PrivKeyEd25519,PrivKeySecp256k1],ed25519,secp256k1" type PrivKeyInner interface { AssertIsPrivKeyInner() diff --git a/priv_key_test.go b/priv_key_test.go new file mode 100644 index 000000000..154df5593 --- /dev/null +++ b/priv_key_test.go @@ -0,0 +1,65 @@ +package crypto + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + wire "github.com/tendermint/go-wire" +) + +type BadKey struct { + PrivKeyEd25519 +} + +// Wrap fulfils interface for PrivKey struct +func (pk BadKey) Wrap() PrivKey { + return PrivKey{pk} +} + +func (pk BadKey) Bytes() []byte { + return wire.BinaryBytes(pk.Wrap()) +} + +func (pk BadKey) ValidateKey() error { + return fmt.Errorf("fuggly key") +} + +func init() { + PrivKeyMapper. + RegisterImplementation(BadKey{}, "bad", 0x66) +} + +func TestReadPrivKey(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // garbage in, garbage out + garbage := []byte("hjgewugfbiewgofwgewr") + _, err := PrivKeyFromBytes(garbage) + require.Error(err) + + edKey := GenPrivKeyEd25519() + badKey := BadKey{edKey} + + cases := []struct { + key PrivKey + valid bool + }{ + {edKey.Wrap(), true}, + {badKey.Wrap(), false}, + } + + for i, tc := range cases { + data := tc.key.Bytes() + key, err := PrivKeyFromBytes(data) + if tc.valid { + assert.NoError(err, "%d", i) + assert.Equal(tc.key, key, "%d", i) + } else { + assert.Error(err, "%d: %#v", i, key) + } + } + +} From 2490952515786d308cd5097d4c789afc940f2a3b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 17:19:35 +0200 Subject: [PATCH 29/43] nano validates key on load --- nano/keys.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nano/keys.go b/nano/keys.go index bd56af6db..4bb0e97c5 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -59,6 +59,8 @@ type PrivKeyLedger struct { CachedPubKey crypto.PubKey } +// NewPrivKeyLedger will generate a new key and store the +// public key for later use. func NewPrivKeyLedger() (crypto.PrivKey, error) { var pk PrivKeyLedger // getPubKey will cache the pubkey for later use, @@ -68,6 +70,22 @@ func NewPrivKeyLedger() (crypto.PrivKey, error) { return pk.Wrap(), err } +// ValidateKey allows us to verify the sanity of a key +// after loading it from disk +func (pk *PrivKeyLedger) ValidateKey() error { + // getPubKey will return an error if the ledger is not + // properly set up... + pub, err := pk.getPubKey() + if err != nil { + return err + } + // verify this matches cached address + if !pub.Equals(pk.CachedPubKey) { + return errors.New("ledger doesn't match cached key") + } + return nil +} + // AssertIsPrivKeyInner fulfils PrivKey Interface func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {} From 9afceb7ee844443cb41c2378f88871ec3c0dddce Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 18:14:28 +0200 Subject: [PATCH 30/43] ledger pubkey serializes properly, better load checks --- nano/keys.go | 31 ++++++++++++++++++++++--------- nano/keys_test.go | 6 ++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/nano/keys.go b/nano/keys.go index 4bb0e97c5..a3337f1e5 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -12,6 +12,7 @@ import ( wire "github.com/tendermint/go-wire" ) +//nolint const ( NameLedgerEd25519 = "ledger" TypeLedgerEd25519 = 0x10 @@ -132,16 +133,23 @@ func (pk *PrivKeyLedger) PubKey() crypto.PubKey { func (pk *PrivKeyLedger) getPubKey() (key crypto.PubKey, err error) { // if we have no pubkey, set it if pk.CachedPubKey.Empty() { - dev, err := getLedger() - if err != nil { - return key, errors.WithMessage(err, "Can't connect to ledger") - } - pk.CachedPubKey, _, err = signLedger(dev, []byte{0}) - if err != nil { - return key, errors.WithMessage(err, "Can't sign with app") - } + pk.CachedPubKey, err = pk.forceGetPubKey() } - return pk.CachedPubKey, nil + return pk.CachedPubKey, err +} + +// forceGetPubKey is like getPubKey but ignores any cached key +// and ensures we get it from the ledger itself. +func (pk *PrivKeyLedger) forceGetPubKey() (key crypto.PubKey, err error) { + dev, err := getLedger() + if err != nil { + return key, errors.New("Can't connect to ledger device") + } + key, _, err = signLedger(dev, []byte{0}) + if err != nil { + return key, errors.New("Please open cosmos app on the ledger") + } + return key, err } // Equals fulfils PrivKey Interface @@ -229,6 +237,11 @@ func PubKeyLedgerFromBytes(key [32]byte) crypto.PubKey { return PubKeyLedger{crypto.PubKeyEd25519(key)}.Wrap() } +// Bytes fulfils pk Interface - no data, just type info +func (pk PubKeyLedger) Bytes() []byte { + return wire.BinaryBytes(pk.Wrap()) +} + // VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand func (pk PubKeyLedger) VerifyBytes(msg []byte, sig crypto.Signature) bool { hmsg := hashMsg(msg) diff --git a/nano/keys_test.go b/nano/keys_test.go index b42b091f0..40cd1b486 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -104,4 +104,10 @@ func TestRealLedger(t *testing.T) { valid = pub.VerifyBytes(msg, sig) assert.True(valid) + // make sure pubkeys serialize properly as well + bs = pub.Bytes() + bpub, err := crypto.PubKeyFromBytes(bs) + require.NoError(err) + assert.Equal(pub, bpub) + } From 61d1bdb5eda2a335da51ed51fd286b404ea85087 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 18:32:29 +0200 Subject: [PATCH 31/43] Test error handling, load without ledger --- nano/keys.go | 2 +- nano/keys_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/nano/keys.go b/nano/keys.go index a3337f1e5..8d4d3d06d 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -76,7 +76,7 @@ func NewPrivKeyLedger() (crypto.PrivKey, error) { func (pk *PrivKeyLedger) ValidateKey() error { // getPubKey will return an error if the ledger is not // properly set up... - pub, err := pk.getPubKey() + pub, err := pk.forceGetPubKey() if err != nil { return err } diff --git a/nano/keys_test.go b/nano/keys_test.go index 40cd1b486..3815d3944 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" ) @@ -109,5 +110,33 @@ func TestRealLedger(t *testing.T) { bpub, err := crypto.PubKeyFromBytes(bs) require.NoError(err) assert.Equal(pub, bpub) +} + +// TestRealLedgerErrorHandling calls. These tests assume +// the ledger is not plugged in.... +func TestRealLedgerErrorHandling(t *testing.T) { + require := require.New(t) + + if os.Getenv("WITH_LEDGER") != "" { + t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases") + } + + // first, try to generate a key, must return an error + // (no panic) + _, err := NewPrivKeyLedger() + require.Error(err) + + led := PrivKeyLedger{} // empty + // or with some pub key + ed := crypto.GenPrivKeyEd25519() + led2 := PrivKeyLedger{CachedPubKey: ed.PubKey()} + + // loading these should return errors + bs := led.Bytes() + _, err = crypto.PrivKeyFromBytes(bs) + require.Error(err) + bs = led2.Bytes() + _, err = crypto.PrivKeyFromBytes(bs) + require.Error(err) } From 9601e48ab40a5a871992651b6e881b82fb88a26c Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 23 Oct 2017 18:39:34 +0200 Subject: [PATCH 32/43] Improve error message --- keys/cryptostore/encoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keys/cryptostore/encoder.go b/keys/cryptostore/encoder.go index 251543b60..0b957d826 100644 --- a/keys/cryptostore/encoder.go +++ b/keys/cryptostore/encoder.go @@ -55,7 +55,7 @@ func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) } privKey, err = crypto.PrivKeyFromBytes(privKeyBytes) if err != nil { - return crypto.PrivKey{}, errors.Wrap(err, "Couldn't get privKey from bytes") + return crypto.PrivKey{}, errors.Wrap(err, "Private Key") } return privKey, nil } From 4a2c63f5e17f2278f31024112484454a75a7bf86 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 24 Oct 2017 11:23:09 +0200 Subject: [PATCH 33/43] Update ledger names to specify ed25519 --- keys/cryptostore/generator.go | 12 ++--- keys/cryptostore/holder_test.go | 2 +- nano/keys.go | 86 ++++++++++++++++----------------- nano/keys_test.go | 8 +-- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/keys/cryptostore/generator.go b/keys/cryptostore/generator.go index 6b2d47a1a..65cc8e58b 100644 --- a/keys/cryptostore/generator.go +++ b/keys/cryptostore/generator.go @@ -12,8 +12,8 @@ var ( GenEd25519 Generator = GenFunc(genEd25519) // GenSecp256k1 produces Secp256k1 private keys GenSecp256k1 Generator = GenFunc(genSecp256) - // GenLedger used Ed25519 keys stored on nano ledger s with cosmos app - GenLedger Generator = GenFunc(genLedger) + // GenLedgerEd25519 used Ed25519 keys stored on nano ledger s with cosmos app + GenLedgerEd25519 Generator = GenFunc(genLedgerEd25519) ) // Generator determines the type of private key the keystore creates @@ -40,8 +40,8 @@ func genSecp256(secret []byte) (crypto.PrivKey, error) { // secret is completely ignored for the ledger... // just for interface compatibility -func genLedger(secret []byte) (crypto.PrivKey, error) { - return nano.NewPrivKeyLedger() +func genLedgerEd25519(secret []byte) (crypto.PrivKey, error) { + return nano.NewPrivKeyLedgerEd25519Ed25519() } type genInvalidByte struct { @@ -69,7 +69,7 @@ func getGenerator(algo string) Generator { case crypto.NameSecp256k1: return GenSecp256k1 case nano.NameLedgerEd25519: - return GenLedger + return GenLedgerEd25519 default: return genInvalidAlgo{algo} } @@ -82,7 +82,7 @@ func getGeneratorByType(typ byte) Generator { case crypto.TypeSecp256k1: return GenSecp256k1 case nano.TypeLedgerEd25519: - return GenLedger + return GenLedgerEd25519 default: return genInvalidByte{typ} } diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 831fa251e..899f51f02 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -176,7 +176,7 @@ func TestSignWithLedger(t *testing.T) { c, _, err := cstore.Create(n, p, nano.NameLedgerEd25519) require.Nil(err, "%+v", err) assert.Equal(c.Name, n) - _, ok := c.PubKey.Unwrap().(nano.PubKeyLedger) + _, ok := c.PubKey.Unwrap().(nano.PubKeyLedgerEd25519) require.True(ok) // make sure we can get it back diff --git a/nano/keys.go b/nano/keys.go index 8d4d3d06d..dc1eec950 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -14,7 +14,7 @@ import ( //nolint const ( - NameLedgerEd25519 = "ledger" + NameLedgerEd25519 = "ledger-ed25519" TypeLedgerEd25519 = 0x10 ) @@ -48,22 +48,22 @@ func signLedger(device *ledger.Ledger, msg []byte) (pk crypto.PubKey, sig crypto var b [32]byte copy(b[:], key) - return PubKeyLedgerFromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil + return PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil } -// PrivKeyLedger implements PrivKey, calling the ledger nano +// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano // we cache the PubKey from the first call to use it later -type PrivKeyLedger struct { +type PrivKeyLedgerEd25519 struct { // PubKey should be private, but we want to encode it via go-wire // so we can view the address later, even without having the ledger // attached CachedPubKey crypto.PubKey } -// NewPrivKeyLedger will generate a new key and store the +// NewPrivKeyLedgerEd25519Ed25519 will generate a new key and store the // public key for later use. -func NewPrivKeyLedger() (crypto.PrivKey, error) { - var pk PrivKeyLedger +func NewPrivKeyLedgerEd25519Ed25519() (crypto.PrivKey, error) { + var pk PrivKeyLedgerEd25519 // getPubKey will cache the pubkey for later use, // this allows us to return an error early if the ledger // is not plugged in @@ -73,7 +73,7 @@ func NewPrivKeyLedger() (crypto.PrivKey, error) { // ValidateKey allows us to verify the sanity of a key // after loading it from disk -func (pk *PrivKeyLedger) ValidateKey() error { +func (pk *PrivKeyLedgerEd25519) ValidateKey() error { // getPubKey will return an error if the ledger is not // properly set up... pub, err := pk.forceGetPubKey() @@ -88,15 +88,15 @@ func (pk *PrivKeyLedger) ValidateKey() error { } // AssertIsPrivKeyInner fulfils PrivKey Interface -func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {} +func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} // Bytes fulfils pk Interface - no data, just type info -func (pk *PrivKeyLedger) Bytes() []byte { +func (pk *PrivKeyLedgerEd25519) Bytes() []byte { return wire.BinaryBytes(pk.Wrap()) } // Sign calls the ledger and stores the pk for future use -func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { +func (pk *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { // oh, I wish there was better error handling dev, err := getLedger() if err != nil { @@ -119,7 +119,7 @@ func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { // PubKey returns the stored PubKey // TODO: query the ledger if not there, once it is not volatile -func (pk *PrivKeyLedger) PubKey() crypto.PubKey { +func (pk *PrivKeyLedgerEd25519) PubKey() crypto.PubKey { key, err := pk.getPubKey() if err != nil { panic(err) @@ -130,7 +130,7 @@ func (pk *PrivKeyLedger) PubKey() crypto.PubKey { // getPubKey reads the pubkey from cache or from the ledger itself // since this involves IO, it may return an error, which is not exposed // in the PubKey interface, so this function allows better error handling -func (pk *PrivKeyLedger) getPubKey() (key crypto.PubKey, err error) { +func (pk *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) { // if we have no pubkey, set it if pk.CachedPubKey.Empty() { pk.CachedPubKey, err = pk.forceGetPubKey() @@ -140,7 +140,7 @@ func (pk *PrivKeyLedger) getPubKey() (key crypto.PubKey, err error) { // forceGetPubKey is like getPubKey but ignores any cached key // and ensures we get it from the ledger itself. -func (pk *PrivKeyLedger) forceGetPubKey() (key crypto.PubKey, err error) { +func (pk *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) { dev, err := getLedger() if err != nil { return key, errors.New("Can't connect to ledger device") @@ -154,23 +154,23 @@ func (pk *PrivKeyLedger) forceGetPubKey() (key crypto.PubKey, err error) { // Equals fulfils PrivKey Interface // TODO: needs to be fixed -func (pk *PrivKeyLedger) Equals(other crypto.PrivKey) bool { - if _, ok := other.Unwrap().(*PrivKeyLedger); ok { +func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { + if _, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { return true } return false } -// MockPrivKeyLedger behaves as the ledger, but stores a pre-packaged call-response +// MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response // for use in test cases -type MockPrivKeyLedger struct { +type MockPrivKeyLedgerEd25519 struct { Msg []byte Pub [KeyLength]byte Sig [SigLength]byte } // NewMockKey returns -func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedger) { +func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) { var err error pk.Msg, err = hex.DecodeString(msg) if err != nil { @@ -191,32 +191,32 @@ func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedger) { return pk } -var _ crypto.PrivKeyInner = MockPrivKeyLedger{} +var _ crypto.PrivKeyInner = MockPrivKeyLedgerEd25519{} // AssertIsPrivKeyInner fulfils PrivKey Interface -func (pk MockPrivKeyLedger) AssertIsPrivKeyInner() {} +func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} // Bytes fulfils PrivKey Interface - not supported -func (pk MockPrivKeyLedger) Bytes() []byte { +func (pk MockPrivKeyLedgerEd25519) Bytes() []byte { return nil } // Sign returns a real SignatureLedger, if the msg matches what we expect -func (pk MockPrivKeyLedger) Sign(msg []byte) crypto.Signature { +func (pk MockPrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { if !bytes.Equal(pk.Msg, msg) { panic("Mock key is for different msg") } return crypto.SignatureEd25519(pk.Sig).Wrap() } -// PubKey returns a real PubKeyLedger, that will verify this signature -func (pk MockPrivKeyLedger) PubKey() crypto.PubKey { - return PubKeyLedgerFromBytes(pk.Pub) +// PubKey returns a real PubKeyLedgerEd25519, that will verify this signature +func (pk MockPrivKeyLedgerEd25519) PubKey() crypto.PubKey { + return PubKeyLedgerEd25519FromBytes(pk.Pub) } // Equals compares that two Mocks have the same data -func (pk MockPrivKeyLedger) Equals(other crypto.PrivKey) bool { - if mock, ok := other.Unwrap().(MockPrivKeyLedger); ok { +func (pk MockPrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { + if mock, ok := other.Unwrap().(MockPrivKeyLedgerEd25519); ok { return bytes.Equal(mock.Pub[:], pk.Pub[:]) && bytes.Equal(mock.Sig[:], pk.Sig[:]) && bytes.Equal(mock.Msg, pk.Msg) @@ -227,30 +227,30 @@ func (pk MockPrivKeyLedger) Equals(other crypto.PrivKey) bool { //////////////////////////////////////////// // pubkey -// PubKeyLedger works like a normal Ed25519 except a hash before the verify bytes -type PubKeyLedger struct { +// PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes +type PubKeyLedgerEd25519 struct { crypto.PubKeyEd25519 } -// PubKeyLedgerFromBytes creates a PubKey from the raw bytes -func PubKeyLedgerFromBytes(key [32]byte) crypto.PubKey { - return PubKeyLedger{crypto.PubKeyEd25519(key)}.Wrap() +// PubKeyLedgerEd25519FromBytes creates a PubKey from the raw bytes +func PubKeyLedgerEd25519FromBytes(key [32]byte) crypto.PubKey { + return PubKeyLedgerEd25519{crypto.PubKeyEd25519(key)}.Wrap() } // Bytes fulfils pk Interface - no data, just type info -func (pk PubKeyLedger) Bytes() []byte { +func (pk PubKeyLedgerEd25519) Bytes() []byte { return wire.BinaryBytes(pk.Wrap()) } // VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand -func (pk PubKeyLedger) VerifyBytes(msg []byte, sig crypto.Signature) bool { +func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool { hmsg := hashMsg(msg) return pk.PubKeyEd25519.VerifyBytes(hmsg, sig) } // Equals implements PubKey interface -func (pk PubKeyLedger) Equals(other crypto.PubKey) bool { - if ledger, ok := other.Unwrap().(PubKeyLedger); ok { +func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool { + if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok { return bytes.Equal(pk.PubKeyEd25519[:], ledger.PubKeyEd25519[:]) } return false @@ -260,24 +260,24 @@ func (pk PubKeyLedger) Equals(other crypto.PubKey) bool { func init() { crypto.PrivKeyMapper. - RegisterImplementation(&PrivKeyLedger{}, NameLedgerEd25519, TypeLedgerEd25519). - RegisterImplementation(MockPrivKeyLedger{}, "mock-ledger", 0x11) + RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519). + RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11) crypto.PubKeyMapper. - RegisterImplementation(PubKeyLedger{}, NameLedgerEd25519, TypeLedgerEd25519) + RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519) } // Wrap fulfils interface for PrivKey struct -func (pk *PrivKeyLedger) Wrap() crypto.PrivKey { +func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey { return crypto.PrivKey{pk} } // Wrap fulfils interface for PrivKey struct -func (pk MockPrivKeyLedger) Wrap() crypto.PrivKey { +func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey { return crypto.PrivKey{pk} } // Wrap fulfils interface for PubKey struct -func (pk PubKeyLedger) Wrap() crypto.PubKey { +func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey { return crypto.PubKey{pk} } diff --git a/nano/keys_test.go b/nano/keys_test.go index 3815d3944..15aa0d545 100644 --- a/nano/keys_test.go +++ b/nano/keys_test.go @@ -83,7 +83,7 @@ func TestRealLedger(t *testing.T) { } msg := []byte("kuhehfeohg") - priv, err := NewPrivKeyLedger() + priv, err := NewPrivKeyLedgerEd25519Ed25519() require.Nil(err, "%+v", err) pub := priv.PubKey() sig := priv.Sign(msg) @@ -123,13 +123,13 @@ func TestRealLedgerErrorHandling(t *testing.T) { // first, try to generate a key, must return an error // (no panic) - _, err := NewPrivKeyLedger() + _, err := NewPrivKeyLedgerEd25519Ed25519() require.Error(err) - led := PrivKeyLedger{} // empty + led := PrivKeyLedgerEd25519{} // empty // or with some pub key ed := crypto.GenPrivKeyEd25519() - led2 := PrivKeyLedger{CachedPubKey: ed.PubKey()} + led2 := PrivKeyLedgerEd25519{CachedPubKey: ed.PubKey()} // loading these should return errors bs := led.Bytes() From 2d0454408878ef3fa011ccc159e7bff16d13e543 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 24 Oct 2017 11:31:38 +0200 Subject: [PATCH 34/43] Addressed Buckys PR review comments --- nano/keys.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/nano/keys.go b/nano/keys.go index dc1eec950..eb531ef41 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -16,6 +16,10 @@ import ( const ( NameLedgerEd25519 = "ledger-ed25519" TypeLedgerEd25519 = 0x10 + + // Timeout is the number of seconds to wait for a response from the ledger + // if eg. waiting for user confirmation on button push + Timeout = 20 ) var device *ledger.Ledger @@ -34,7 +38,7 @@ func signLedger(device *ledger.Ledger, msg []byte) (pk crypto.PubKey, sig crypto packets := generateSignRequests(msg) for _, pack := range packets { - resp, err = device.Exchange(pack, 100) + resp, err = device.Exchange(pack, Timeout) if err != nil { return pk, sig, err } @@ -90,12 +94,19 @@ func (pk *PrivKeyLedgerEd25519) ValidateKey() error { // AssertIsPrivKeyInner fulfils PrivKey Interface func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} -// Bytes fulfils pk Interface - no data, just type info +// Bytes fulfils pk Interface - stores the cached pubkey so we can verify +// the same key when we reconnect to a ledger func (pk *PrivKeyLedgerEd25519) Bytes() []byte { return wire.BinaryBytes(pk.Wrap()) } // Sign calls the ledger and stores the pk for future use +// +// XXX/TODO: panics if there is an error communicating with the ledger. +// +// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, +// returning an error, so this should only trigger if the privkey is held +// in memory for a while before use. func (pk *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { // oh, I wish there was better error handling dev, err := getLedger() @@ -152,11 +163,11 @@ func (pk *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) return key, err } -// Equals fulfils PrivKey Interface -// TODO: needs to be fixed +// Equals fulfils PrivKey Interface - makes sure both keys refer to the +// same func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { - if _, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { - return true + if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { + return pk.CachedPubKey.Equals(ledger.CachedPubKey) } return false } @@ -251,7 +262,7 @@ func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool // Equals implements PubKey interface func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool { if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok { - return bytes.Equal(pk.PubKeyEd25519[:], ledger.PubKeyEd25519[:]) + return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap()) } return false } From 6f6bbf718e4769fb321af88533769b2c1a154b72 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 24 Oct 2017 11:56:37 +0200 Subject: [PATCH 35/43] Fix metalinter complaints --- keys/cryptostore/encoder.go | 4 ++-- keys/cryptostore/encoder_test.go | 8 ++++---- keys/cryptostore/holder_test.go | 2 +- nano/keys.go | 6 +++--- nano/sign.go | 20 -------------------- nano/sign_test.go | 20 ++++++++++++++++++++ 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/keys/cryptostore/encoder.go b/keys/cryptostore/encoder.go index 0b957d826..531a534ad 100644 --- a/keys/cryptostore/encoder.go +++ b/keys/cryptostore/encoder.go @@ -39,7 +39,7 @@ func (e secretbox) Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes return saltBytes, crypto.EncryptSymmetric(privKeyBytes, key), nil } -func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { +func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (crypto.PrivKey, error) { privKeyBytes := encBytes // NOTE: Some keys weren't encrypted with a passphrase and hence we have the conditional if passphrase != "" { @@ -53,7 +53,7 @@ func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase") } } - privKey, err = crypto.PrivKeyFromBytes(privKeyBytes) + privKey, err := crypto.PrivKeyFromBytes(privKeyBytes) if err != nil { return crypto.PrivKey{}, errors.Wrap(err, "Private Key") } diff --git a/keys/cryptostore/encoder_test.go b/keys/cryptostore/encoder_test.go index 9cde95a12..614286a19 100644 --- a/keys/cryptostore/encoder_test.go +++ b/keys/cryptostore/encoder_test.go @@ -68,8 +68,8 @@ func TestSecretBoxNoPass(t *testing.T) { assert, require := assert.New(t), require.New(t) enc := cryptostore.SecretBox - key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) - require.NoError(err) + key, rerr := cryptostore.GenEd25519.Generate(cmn.RandBytes(16)) + require.NoError(rerr) cases := []struct { encode string @@ -99,7 +99,7 @@ func TestSecretBoxNoPass(t *testing.T) { // now let's make sure raw bytes also work... b := key.Bytes() - pk, err := enc.Decrypt(nil, b, "") - require.Nil(err, "%+v", err) + pk, rerr := enc.Decrypt(nil, b, "") + require.NoError(rerr) assert.Equal(key, pk) } diff --git a/keys/cryptostore/holder_test.go b/keys/cryptostore/holder_test.go index 899f51f02..3709cc55d 100644 --- a/keys/cryptostore/holder_test.go +++ b/keys/cryptostore/holder_test.go @@ -320,7 +320,7 @@ func TestSeedPhrase(t *testing.T) { assert.Equal(info.PubKey, newInfo.PubKey) } -func ExampleStore() { +func ExampleNew() { // Select the encryption and storage for your cryptostore cstore := cryptostore.New( cryptostore.SecretBox, diff --git a/nano/keys.go b/nano/keys.go index eb531ef41..a6d3ea8e4 100644 --- a/nano/keys.go +++ b/nano/keys.go @@ -280,15 +280,15 @@ func init() { // Wrap fulfils interface for PrivKey struct func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey { - return crypto.PrivKey{pk} + return crypto.PrivKey{PrivKeyInner: pk} } // Wrap fulfils interface for PrivKey struct func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey { - return crypto.PrivKey{pk} + return crypto.PrivKey{PrivKeyInner: pk} } // Wrap fulfils interface for PubKey struct func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey { - return crypto.PubKey{pk} + return crypto.PubKey{PubKeyInner: pk} } diff --git a/nano/sign.go b/nano/sign.go index 874d134e8..c40801583 100644 --- a/nano/sign.go +++ b/nano/sign.go @@ -5,8 +5,6 @@ import ( "crypto/sha512" "github.com/pkg/errors" - - crypto "github.com/tendermint/go-crypto" ) const ( @@ -59,24 +57,6 @@ func parseDigest(resp []byte) (key, sig []byte, err error) { return key, sig, nil } -func parseEdKey(data []byte) (key crypto.PubKey, err error) { - ed := crypto.PubKeyEd25519{} - if len(data) < len(ed) { - return key, errors.Errorf("Key length too short: %d", len(data)) - } - copy(ed[:], data) - return ed.Wrap(), nil -} - -func parseSig(data []byte) (key crypto.Signature, err error) { - ed := crypto.SignatureEd25519{} - if len(data) < len(ed) { - return key, errors.Errorf("Sig length too short: %d", len(data)) - } - copy(ed[:], data) - return ed.Wrap(), nil -} - func hashMsg(data []byte) []byte { res := sha512.Sum512(data) return res[:] diff --git a/nano/sign_test.go b/nano/sign_test.go index 89bd7568e..04a6d0be7 100644 --- a/nano/sign_test.go +++ b/nano/sign_test.go @@ -4,10 +4,30 @@ import ( "encoding/hex" "testing" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" ) +func parseEdKey(data []byte) (key crypto.PubKey, err error) { + ed := crypto.PubKeyEd25519{} + if len(data) < len(ed) { + return key, errors.Errorf("Key length too short: %d", len(data)) + } + copy(ed[:], data) + return ed.Wrap(), nil +} + +func parseSig(data []byte) (key crypto.Signature, err error) { + ed := crypto.SignatureEd25519{} + if len(data) < len(ed) { + return key, errors.Errorf("Sig length too short: %d", len(data)) + } + copy(ed[:], data) + return ed.Wrap(), nil +} + func TestParseDigest(t *testing.T) { assert, require := assert.New(t), require.New(t) From 69a7b389b87f0218f367f16eb156f23867246dc0 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 24 Oct 2017 11:59:44 +0200 Subject: [PATCH 36/43] Removed keys/server as it is now in cosmos-sdk --- keys/server/README.md | 13 --- keys/server/helpers.go | 59 ------------ keys/server/keys.go | 128 ------------------------- keys/server/keys_test.go | 193 -------------------------------------- keys/server/types/keys.go | 35 ------- keys/server/valid.go | 12 --- 6 files changed, 440 deletions(-) delete mode 100644 keys/server/README.md delete mode 100644 keys/server/helpers.go delete mode 100644 keys/server/keys.go delete mode 100644 keys/server/keys_test.go delete mode 100644 keys/server/types/keys.go delete mode 100644 keys/server/valid.go diff --git a/keys/server/README.md b/keys/server/README.md deleted file mode 100644 index 032cf574e..000000000 --- a/keys/server/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Proxy Server - -This package provides all the functionality for a local http server, providing access to key management functionality (creating, listing, updating, and deleting keys). This is a nice building block for larger apps, and the HTTP handlers here can be embedded in a larger server that does nice things like signing transactions and posting them to a tendermint chain (which requires domain-knowledge of the transactions types and out of scope of this generic app). - -## Key Management - -We expose a number of methods for safely managing your keychain. If you are embedding this in a larger server, you will typically want to mount all these paths under `/keys`. - -* `POST /` - provide a name and passphrase and create a brand new key -* `GET /` - get a list of all available key names, along with their public key and address -* `GET /{name}` - get public key and address for this named key -* `PUT /{name}` - update the passphrase for the given key. requires you to correctly provide the current passphrase, as well as a new one. -* `DELETE /{name}` - permanently delete this private key. requires you to correctly provide the current passphrase diff --git a/keys/server/helpers.go b/keys/server/helpers.go deleted file mode 100644 index 3fb947316..000000000 --- a/keys/server/helpers.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -package server provides http handlers to construct a server server -for key management, transaction signing, and query validation. - -Please read the README and godoc to see how to -configure the server for your application. -*/ - -package server - -import ( - "encoding/json" - "io/ioutil" - "net/http" - - "github.com/tendermint/go-crypto/keys/server/types" - data "github.com/tendermint/go-wire/data" - - "github.com/pkg/errors" -) - -func readRequest(r *http.Request, o interface{}) error { - defer r.Body.Close() - data, err := ioutil.ReadAll(r.Body) - if err != nil { - return errors.Wrap(err, "Read Request") - } - err = json.Unmarshal(data, o) - if err != nil { - return errors.Wrap(err, "Parse") - } - return validate(o) -} - -// most errors are bad input, so 406... do better.... -func writeError(w http.ResponseWriter, err error) { - // fmt.Printf("Error: %+v\n", err) - res := types.ErrorResponse{ - Code: 406, - Error: err.Error(), - } - writeCode(w, &res, 406) -} - -func writeCode(w http.ResponseWriter, o interface{}, code int) { - // two space indent to make it easier to read - data, err := data.ToJSON(o) - if err != nil { - writeError(w, err) - } else { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - w.Write(data) - } -} - -func writeSuccess(w http.ResponseWriter, o interface{}) { - writeCode(w, o, 200) -} diff --git a/keys/server/keys.go b/keys/server/keys.go deleted file mode 100644 index 80852802e..000000000 --- a/keys/server/keys.go +++ /dev/null @@ -1,128 +0,0 @@ -package server - -import ( - "errors" - "net/http" - - "github.com/gorilla/mux" - keys "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/go-crypto/keys/server/types" -) - -type Keys struct { - manager keys.Manager - algo string -} - -func New(manager keys.Manager, algo string) Keys { - return Keys{ - manager: manager, - algo: algo, - } -} - -func (k Keys) GenerateKey(w http.ResponseWriter, r *http.Request) { - req := types.CreateKeyRequest{ - Algo: k.algo, // default key type from cli - } - err := readRequest(r, &req) - if err != nil { - writeError(w, err) - return - } - - key, seed, err := k.manager.Create(req.Name, req.Passphrase, req.Algo) - if err != nil { - writeError(w, err) - return - } - - res := types.CreateKeyResponse{key, seed} - writeSuccess(w, &res) -} - -func (k Keys) GetKey(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - name := vars["name"] - key, err := k.manager.Get(name) - if err != nil { - writeError(w, err) - return - } - writeSuccess(w, &key) -} - -func (k Keys) ListKeys(w http.ResponseWriter, r *http.Request) { - - keys, err := k.manager.List() - if err != nil { - writeError(w, err) - return - } - writeSuccess(w, keys) -} - -func (k Keys) UpdateKey(w http.ResponseWriter, r *http.Request) { - req := types.UpdateKeyRequest{} - err := readRequest(r, &req) - if err != nil { - writeError(w, err) - return - } - - vars := mux.Vars(r) - name := vars["name"] - if name != req.Name { - writeError(w, errors.New("path and json key names don't match")) - return - } - - err = k.manager.Update(req.Name, req.OldPass, req.NewPass) - if err != nil { - writeError(w, err) - return - } - - key, err := k.manager.Get(req.Name) - if err != nil { - writeError(w, err) - return - } - writeSuccess(w, &key) -} - -func (k Keys) DeleteKey(w http.ResponseWriter, r *http.Request) { - req := types.DeleteKeyRequest{} - err := readRequest(r, &req) - if err != nil { - writeError(w, err) - return - } - - vars := mux.Vars(r) - name := vars["name"] - if name != req.Name { - writeError(w, errors.New("path and json key names don't match")) - return - } - - err = k.manager.Delete(req.Name, req.Passphrase) - if err != nil { - writeError(w, err) - return - } - - // not really an error, but something generic - resp := types.ErrorResponse{ - Success: true, - } - writeSuccess(w, &resp) -} - -func (k Keys) Register(r *mux.Router) { - r.HandleFunc("/", k.GenerateKey).Methods("POST") - r.HandleFunc("/", k.ListKeys).Methods("GET") - r.HandleFunc("/{name}", k.GetKey).Methods("GET") - r.HandleFunc("/{name}", k.UpdateKey).Methods("POST", "PUT") - r.HandleFunc("/{name}", k.DeleteKey).Methods("DELETE") -} diff --git a/keys/server/keys_test.go b/keys/server/keys_test.go deleted file mode 100644 index 2aa17753c..000000000 --- a/keys/server/keys_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package server_test - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - keys "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/go-crypto/keys/cryptostore" - "github.com/tendermint/go-crypto/keys/server" - "github.com/tendermint/go-crypto/keys/server/types" - "github.com/tendermint/go-crypto/keys/storage/memstorage" -) - -func TestKeyServer(t *testing.T) { - assert, require := assert.New(t), require.New(t) - r := setupServer() - - // let's abstract this out a bit.... - keys, code, err := listKeys(r) - require.Nil(err) - require.Equal(http.StatusOK, code) - assert.Equal(0, len(keys)) - - algo := "ed25519" - n1, n2 := "personal", "business" - p0, p1, p2 := "1234", "over10chars...", "really-secure!@#$" - - // this fails for validation - _, code, err = createKey(r, n1, p0, algo) - require.Nil(err, "%+v", err) - require.NotEqual(http.StatusOK, code) - - // new password better - key, code, err := createKey(r, n1, p1, algo) - require.Nil(err, "%+v", err) - require.Equal(http.StatusOK, code) - require.Equal(n1, key.Key.Name) - require.NotEmpty(n1, key.Seed) - - // the other one works - key2, code, err := createKey(r, n2, p2, algo) - require.Nil(err, "%+v", err) - require.Equal(http.StatusOK, code) - require.Equal(key2.Key.Name, n2) - require.NotEmpty(n2, key.Seed) - - // let's abstract this out a bit.... - keys, code, err = listKeys(r) - require.Nil(err) - require.Equal(http.StatusOK, code) - if assert.Equal(2, len(keys)) { - // in alphabetical order - assert.Equal(keys[0].Name, n2) - assert.Equal(keys[1].Name, n1) - } - - // get works - k, code, err := getKey(r, n1) - require.Nil(err, "%+v", err) - require.Equal(http.StatusOK, code) - assert.Equal(n1, k.Name) - assert.NotNil(k.Address) - assert.Equal(key.Key.Address, k.Address) - - // delete with proper key - _, code, err = deleteKey(r, n1, p1) - require.Nil(err, "%+v", err) - require.Equal(http.StatusOK, code) - - // after delete, get and list different - _, code, err = getKey(r, n1) - require.Nil(err, "%+v", err) - require.NotEqual(http.StatusOK, code) - keys, code, err = listKeys(r) - require.Nil(err, "%+v", err) - require.Equal(http.StatusOK, code) - if assert.Equal(1, len(keys)) { - assert.Equal(keys[0].Name, n2) - } - -} - -func setupServer() http.Handler { - // make the storage with reasonable defaults - cstore := cryptostore.New( - cryptostore.SecretBox, - memstorage.New(), - keys.MustLoadCodec("english"), - ) - - // build your http server - ks := server.New(cstore, "ed25519") - r := mux.NewRouter() - sk := r.PathPrefix("/keys").Subrouter() - ks.Register(sk) - return r -} - -// return data, status code, and error -func listKeys(h http.Handler) (keys.Infos, int, error) { - rr := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/keys/", nil) - if err != nil { - return nil, 0, err - } - - h.ServeHTTP(rr, req) - if http.StatusOK != rr.Code { - return nil, rr.Code, nil - } - - data := keys.Infos{} - err = json.Unmarshal(rr.Body.Bytes(), &data) - return data, rr.Code, err -} - -func getKey(h http.Handler, name string) (*keys.Info, int, error) { - rr := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/keys/"+name, nil) - if err != nil { - return nil, 0, err - } - - h.ServeHTTP(rr, req) - if http.StatusOK != rr.Code { - return nil, rr.Code, nil - } - - data := keys.Info{} - err = json.Unmarshal(rr.Body.Bytes(), &data) - return &data, rr.Code, err -} - -func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyResponse, int, error) { - rr := httptest.NewRecorder() - post := types.CreateKeyRequest{ - Name: name, - Passphrase: passphrase, - Algo: algo, - } - var b bytes.Buffer - err := json.NewEncoder(&b).Encode(&post) - if err != nil { - return nil, 0, err - } - - req, err := http.NewRequest("POST", "/keys/", &b) - if err != nil { - return nil, 0, err - } - - h.ServeHTTP(rr, req) - if http.StatusOK != rr.Code { - return nil, rr.Code, nil - } - - data := new(types.CreateKeyResponse) - err = json.Unmarshal(rr.Body.Bytes(), data) - return data, rr.Code, err -} - -func deleteKey(h http.Handler, name, passphrase string) (*types.ErrorResponse, int, error) { - rr := httptest.NewRecorder() - post := types.DeleteKeyRequest{ - Name: name, - Passphrase: passphrase, - } - var b bytes.Buffer - err := json.NewEncoder(&b).Encode(&post) - if err != nil { - return nil, 0, err - } - - req, err := http.NewRequest("DELETE", "/keys/"+name, &b) - if err != nil { - return nil, 0, err - } - - h.ServeHTTP(rr, req) - if http.StatusOK != rr.Code { - return nil, rr.Code, nil - } - - data := types.ErrorResponse{} - err = json.Unmarshal(rr.Body.Bytes(), &data) - return &data, rr.Code, err -} diff --git a/keys/server/types/keys.go b/keys/server/types/keys.go deleted file mode 100644 index 56ed60ceb..000000000 --- a/keys/server/types/keys.go +++ /dev/null @@ -1,35 +0,0 @@ -package types - -import "github.com/tendermint/go-crypto/keys" - -// CreateKeyRequest is sent to create a new key -type CreateKeyRequest struct { - Name string `json:"name" validate:"required,min=4,printascii"` - Passphrase string `json:"passphrase" validate:"required,min=10"` - Algo string `json:"algo"` -} - -// DeleteKeyRequest to destroy a key permanently (careful!) -type DeleteKeyRequest struct { - Name string `json:"name" validate:"required,min=4,printascii"` - Passphrase string `json:"passphrase" validate:"required,min=10"` -} - -// UpdateKeyRequest is sent to update the passphrase for an existing key -type UpdateKeyRequest struct { - Name string `json:"name" validate:"required,min=4,printascii"` - OldPass string `json:"passphrase" validate:"required,min=10"` - NewPass string `json:"new_passphrase" validate:"required,min=10"` -} - -// ErrorResponse is returned for 4xx and 5xx errors -type ErrorResponse struct { - Success bool `json:"success"` - Error string `json:"error"` // error message if Success is false - Code int `json:"code"` // error code if Success is false -} - -type CreateKeyResponse struct { - Key keys.Info `json:"key"` - Seed string `json:"seed_phrase"` -} diff --git a/keys/server/valid.go b/keys/server/valid.go deleted file mode 100644 index 50b51e21b..000000000 --- a/keys/server/valid.go +++ /dev/null @@ -1,12 +0,0 @@ -package server - -import ( - "github.com/pkg/errors" - "gopkg.in/go-playground/validator.v9" -) - -var v = validator.New() - -func validate(req interface{}) error { - return errors.Wrap(v.Struct(req), "Validate") -} From 0219ba2a63e2dcf027b8af8741900409a962224b Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 24 Oct 2017 12:14:20 +0200 Subject: [PATCH 37/43] Fix bug introduced by metalinting... --- keys/cryptostore/encoder.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/keys/cryptostore/encoder.go b/keys/cryptostore/encoder.go index 531a534ad..31f49c3f7 100644 --- a/keys/cryptostore/encoder.go +++ b/keys/cryptostore/encoder.go @@ -39,11 +39,12 @@ func (e secretbox) Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes return saltBytes, crypto.EncryptSymmetric(privKeyBytes, key), nil } -func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (crypto.PrivKey, error) { +func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { privKeyBytes := encBytes // NOTE: Some keys weren't encrypted with a passphrase and hence we have the conditional if passphrase != "" { - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016) + var key []byte + key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016) if err != nil { return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase") } @@ -53,7 +54,7 @@ func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase") } } - privKey, err := crypto.PrivKeyFromBytes(privKeyBytes) + privKey, err = crypto.PrivKeyFromBytes(privKeyBytes) if err != nil { return crypto.PrivKey{}, errors.Wrap(err, "Private Key") } From dfc4cdd2d71513e4a9922d679c74f36357c4c862 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Tue, 24 Oct 2017 11:48:13 +0200 Subject: [PATCH 38/43] Parameterize and lower bcrypt cost --- keys/cryptostore/encoder.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/keys/cryptostore/encoder.go b/keys/cryptostore/encoder.go index 31f49c3f7..99241f1b7 100644 --- a/keys/cryptostore/encoder.go +++ b/keys/cryptostore/encoder.go @@ -7,6 +7,20 @@ import ( "github.com/tendermint/go-crypto/bcrypt" ) +const ( + // BcryptCost is as parameter to increase the resistance of the + // encoded keys to brute force password guessing + // + // Jae: 14 is good today (2016) + // + // Ethan: loading the key (at each signing) takes a second on my desktop, + // this is hard for laptops and deadly for mobile. You can raise it again, + // but for now, I will make this usable + // + // TODO: review value + BCryptCost = 12 +) + var ( // SecretBox uses the algorithm from NaCL to store secrets securely SecretBox Encoder = secretbox{} @@ -30,7 +44,7 @@ func (e secretbox) Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes } saltBytes = crypto.CRandBytes(16) - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BCryptCost) if err != nil { return nil, nil, errors.Wrap(err, "Couldn't generate bcrypt key from passphrase.") } @@ -44,7 +58,7 @@ func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) // NOTE: Some keys weren't encrypted with a passphrase and hence we have the conditional if passphrase != "" { var key []byte - key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016) + key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BCryptCost) if err != nil { return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase") } From 5d8890530a26d86cb7b7baa179473ebac60e07e6 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 26 Oct 2017 12:00:36 +0200 Subject: [PATCH 39/43] Update glide deps, nano/hid for osx/golang1.9 --- glide.lock | 32 +++++++------------------------- glide.yaml | 6 ------ 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/glide.lock b/glide.lock index 29124c3db..7df64f79f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,26 +1,20 @@ -hash: a2243bfd21937edf660778300855e7cb72185164641cb278dbf0c220e8a0f60a -updated: 2017-10-23T17:21:02.40831023+02:00 +hash: 6e06a42eafe0aeff112cee86aef6b2cab0e2f62c2e6bfccfb629aa22f6b62773 +updated: 2017-10-26T11:57:48.785457739+02:00 imports: -- name: github.com/bgentry/speakeasy - version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd - name: github.com/btcsuite/btcd version: b8df516b4b267acf2de46be593a9d948d1d2c420 subpackages: - btcec - - chaincfg - - chaincfg/chainhash - - wire - name: github.com/btcsuite/btcutil version: 66871daeb12123ece012a9628d2798d01195c4b3 subpackages: - base58 - - hdkeychain - name: github.com/btcsuite/fastsha256 version: 637e656429416087660c84436a2a035d69d54e2e +- name: github.com/ethanfrey/hid + version: f379bda1dbc8e79333b04563f71a12e86206efe5 - name: github.com/ethanfrey/ledger - version: 5e432577be582bd18a3b4a9cd75dae7a317ade36 -- name: github.com/flynn/hid - version: ed06a31c6245d4552e8dbba7e32e5b010b875d65 + version: 3689ce9be93e1a5bef836b1cc2abb18381c79176 - name: github.com/go-kit/kit version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8 subpackages: @@ -37,34 +31,24 @@ imports: version: 71201497bace774495daed26a3874fd339e0b538 - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 -- name: github.com/gorilla/context - version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 -- name: github.com/gorilla/handlers - version: a4043c62cc2329bacda331d33fc908ab11ef0ec3 -- name: github.com/gorilla/mux - version: bcd8bc72b08df0f70df986b97f95590779502d31 - name: github.com/howeyc/crc16 version: 58da63c846043d0bea709c8d47039df06577d6d9 - name: github.com/kr/logfmt version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d -- name: github.com/spf13/cobra - version: 4cdb38c072b86bf795d2c81de50784d9fdd6eb77 -- name: github.com/spf13/viper - version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2 - name: github.com/tendermint/ed25519 version: 1f52c6f8b8a5c7908aff4497c186af344b428925 subpackages: - edwards25519 - extra25519 - name: github.com/tendermint/go-wire - version: 55ae61f1fc83cfaa57ab7d54250d7a1a2be0b83c + version: 3180c867ca52bcd9ba6c905ce63613f8d8e9837c subpackages: - data - data/base58 - name: github.com/tendermint/tmlibs - version: 8e5266a9ef2527e68a1571f932db8228a331b556 + version: b30e3ba26d4077edeed83c50a4e0c38b0ec9ddb3 subpackages: - common - log @@ -83,8 +67,6 @@ imports: - name: gopkg.in/go-playground/validator.v9 version: 6d8c18553ea1ac493d049edd6f102f52e618f085 testImports: -- name: github.com/cmars/basen - version: fe3947df716ebfda9847eb1b9a48f9592e06478c - name: github.com/davecgh/go-spew version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: diff --git a/glide.yaml b/glide.yaml index 9f20ddefc..2f3e72471 100644 --- a/glide.yaml +++ b/glide.yaml @@ -22,13 +22,7 @@ import: - nacl/secretbox - openpgp/armor - ripemd160 -- package: github.com/bgentry/speakeasy -- package: github.com/gorilla/handlers -- package: github.com/gorilla/mux - package: github.com/pkg/errors -- package: github.com/spf13/cobra -- package: github.com/spf13/viper -- package: gopkg.in/go-playground/validator.v9 - package: github.com/howeyc/crc16 - package: github.com/ethanfrey/ledger testImport: From 57346134a57c16559ce009464a96dfd37547a06c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 26 Oct 2017 16:43:32 -0400 Subject: [PATCH 40/43] keys: transactions.go -> types.go --- keys/storage.go | 10 ---------- keys/{transactions.go => types.go} | 9 +++++++++ 2 files changed, 9 insertions(+), 10 deletions(-) delete mode 100644 keys/storage.go rename keys/{transactions.go => types.go} (91%) diff --git a/keys/storage.go b/keys/storage.go deleted file mode 100644 index 0aba4ebdb..000000000 --- a/keys/storage.go +++ /dev/null @@ -1,10 +0,0 @@ -package keys - -// Storage has many implementation, based on security and sharing requirements -// like disk-backed, mem-backed, vault, db, etc. -type Storage interface { - Put(name string, salt []byte, key []byte, info Info) error - Get(name string) (salt []byte, key []byte, info Info, err error) - List() (Infos, error) - Delete(name string) error -} diff --git a/keys/transactions.go b/keys/types.go similarity index 91% rename from keys/transactions.go rename to keys/types.go index 1834ada26..008a6f7ae 100644 --- a/keys/transactions.go +++ b/keys/types.go @@ -9,6 +9,15 @@ import ( data "github.com/tendermint/go-wire/data" ) +// Storage has many implementation, based on security and sharing requirements +// like disk-backed, mem-backed, vault, db, etc. +type Storage interface { + Put(name string, salt []byte, key []byte, info Info) error + Get(name string) (salt []byte, key []byte, info Info, err error) + List() (Infos, error) + Delete(name string) error +} + // Info is the public information about a key type Info struct { Name string `json:"name"` From 944d36ab00d5f82ef590ea515d437134e2e161ad Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 27 Oct 2017 12:04:31 -0400 Subject: [PATCH 41/43] changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63d4af1d8..99f1ea7da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.4.0 (October 27, 2017) + +BREAKING CHANGES: + +- `keys`: use bcrypt plus salt + +FEATURES: + +- add support for signing via Ledger Nano + ## 0.3.0 (September 22, 2017) BREAKING CHANGES: From ad31f6a95327a1997e1c75fd57645ee5dd731332 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Fri, 27 Oct 2017 18:46:21 +0200 Subject: [PATCH 42/43] osx + hid = <3 --- glide.lock | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/glide.lock b/glide.lock index 7df64f79f..096ec5c15 100644 --- a/glide.lock +++ b/glide.lock @@ -1,22 +1,20 @@ hash: 6e06a42eafe0aeff112cee86aef6b2cab0e2f62c2e6bfccfb629aa22f6b62773 -updated: 2017-10-26T11:57:48.785457739+02:00 +updated: 2017-10-27T18:45:18.350198941+02:00 imports: - name: github.com/btcsuite/btcd - version: b8df516b4b267acf2de46be593a9d948d1d2c420 + version: c7588cbf7690cd9f047a28efa2dcd8f2435a4e5e subpackages: - btcec - name: github.com/btcsuite/btcutil version: 66871daeb12123ece012a9628d2798d01195c4b3 subpackages: - base58 -- name: github.com/btcsuite/fastsha256 - version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/ethanfrey/hid - version: f379bda1dbc8e79333b04563f71a12e86206efe5 + version: 660bb717bd4e7cbcdf0f7cd5cadf1cb2e4be452a - name: github.com/ethanfrey/ledger - version: 3689ce9be93e1a5bef836b1cc2abb18381c79176 + version: 23a7bb9d74bc83a862fcb4bddde24215b2295ad9 - name: github.com/go-kit/kit - version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8 + version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c subpackages: - log - log/level @@ -24,36 +22,36 @@ imports: - name: github.com/go-logfmt/logfmt version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 - name: github.com/go-playground/locales - version: 1e5f1161c6416a5ff48840eb8724a394e48cc534 + version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6 subpackages: - currency - name: github.com/go-playground/universal-translator version: 71201497bace774495daed26a3874fd339e0b538 - name: github.com/go-stack/stack - version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 + version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf - name: github.com/howeyc/crc16 - version: 58da63c846043d0bea709c8d47039df06577d6d9 + version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f - name: github.com/kr/logfmt version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/pkg/errors version: 645ef00459ed84a119197bfb8d8205042c6df63d - name: github.com/tendermint/ed25519 - version: 1f52c6f8b8a5c7908aff4497c186af344b428925 + version: d8387025d2b9d158cf4efb07e7ebf814bcce2057 subpackages: - edwards25519 - extra25519 - name: github.com/tendermint/go-wire - version: 3180c867ca52bcd9ba6c905ce63613f8d8e9837c + version: 8ee84b5b2581530168daf66fc89c548d27403c57 subpackages: - data - data/base58 - name: github.com/tendermint/tmlibs - version: b30e3ba26d4077edeed83c50a4e0c38b0ec9ddb3 + version: 092eb701c7276907cdbed258750e22ce895b6735 subpackages: - common - log - name: golang.org/x/crypto - version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e + version: edd5e9b0879d13ee6970a50153d85b8fec9f7686 subpackages: - bcrypt - blowfish @@ -65,7 +63,7 @@ imports: - ripemd160 - salsa20/salsa - name: gopkg.in/go-playground/validator.v9 - version: 6d8c18553ea1ac493d049edd6f102f52e618f085 + version: 1304298bf10d085adec514b076772a79c9cadb6b testImports: - name: github.com/davecgh/go-spew version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 From 8630b724b2f267940f696885cebda954f9eaa7cd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 27 Oct 2017 12:57:26 -0400 Subject: [PATCH 43/43] version and changelog --- CHANGELOG.md | 4 ++++ version.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f1ea7da..64dc9998b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ FEATURES: - add support for signing via Ledger Nano +IMPROVEMENTS: + +- linting and comments + ## 0.3.0 (September 22, 2017) BREAKING CHANGES: diff --git a/version.go b/version.go index 23d065446..585d7e0d7 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.3.0" +const Version = "0.4.0"