@ -1,6 +0,0 @@ | |||
package main | |||
import ( | |||
_ "github.com/tendermint/go-wire/gen" | |||
_ "github.com/clipperhouse/stringer" | |||
) |
@ -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 |
@ -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 ExampleSha256() { | |||
sum := crypto.Sha256([]byte("This is Tendermint")) | |||
fmt.Printf("%x\n", sum) | |||
// Output: | |||
// f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e | |||
} | |||
func ExampleRipemd160() { | |||
sum := crypto.Ripemd160([]byte("This is Tendermint")) | |||
fmt.Printf("%x\n", sum) | |||
// Output: | |||
// 051e22663e8f0fd2f2302f1210f954adff009005 | |||
} |
@ -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 |
@ -1,58 +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" | |||
data "github.com/tendermint/go-wire/data" | |||
"github.com/tendermint/go-crypto/keys/server/types" | |||
"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) | |||
} |
@ -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") | |||
} |
@ -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 | |||
} |
@ -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"` | |||
} |
@ -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") | |||
} |
@ -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, key []byte, info Info) error | |||
Get(name string) ([]byte, Info, error) | |||
List() (Infos, error) | |||
Delete(name string) error | |||
} |
@ -0,0 +1,294 @@ | |||
package nano | |||
import ( | |||
"bytes" | |||
"encoding/hex" | |||
"github.com/pkg/errors" | |||
ledger "github.com/ethanfrey/ledger" | |||
crypto "github.com/tendermint/go-crypto" | |||
wire "github.com/tendermint/go-wire" | |||
) | |||
//nolint | |||
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 | |||
// 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, Timeout) | |||
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 PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil | |||
} | |||
// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano | |||
// we cache the PubKey from the first call to use it later | |||
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 | |||
} | |||
// NewPrivKeyLedgerEd25519Ed25519 will generate a new key and store the | |||
// public key for later use. | |||
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 | |||
_, err := pk.getPubKey() | |||
return pk.Wrap(), err | |||
} | |||
// ValidateKey allows us to verify the sanity of a key | |||
// after loading it from disk | |||
func (pk *PrivKeyLedgerEd25519) ValidateKey() error { | |||
// getPubKey will return an error if the ledger is not | |||
// properly set up... | |||
pub, err := pk.forceGetPubKey() | |||
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 *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} | |||
// 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() | |||
if err != nil { | |||
panic(err) | |||
} | |||
pub, sig, err := signLedger(dev, msg) | |||
if err != nil { | |||
panic(err) | |||
} | |||
// 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 | |||
} | |||
// PubKey returns the stored PubKey | |||
// TODO: query the ledger if not there, once it is not volatile | |||
func (pk *PrivKeyLedgerEd25519) PubKey() crypto.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 *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) { | |||
// if we have no pubkey, set it | |||
if pk.CachedPubKey.Empty() { | |||
pk.CachedPubKey, err = pk.forceGetPubKey() | |||
} | |||
return pk.CachedPubKey, err | |||
} | |||
// forceGetPubKey is like getPubKey but ignores any cached key | |||
// and ensures we get it from the ledger itself. | |||
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") | |||
} | |||
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 - makes sure both keys refer to the | |||
// same | |||
func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { | |||
if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { | |||
return pk.CachedPubKey.Equals(ledger.CachedPubKey) | |||
} | |||
return false | |||
} | |||
// MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response | |||
// for use in test cases | |||
type MockPrivKeyLedgerEd25519 struct { | |||
Msg []byte | |||
Pub [KeyLength]byte | |||
Sig [SigLength]byte | |||
} | |||
// NewMockKey returns | |||
func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) { | |||
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 = MockPrivKeyLedgerEd25519{} | |||
// AssertIsPrivKeyInner fulfils PrivKey Interface | |||
func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} | |||
// Bytes fulfils PrivKey Interface - not supported | |||
func (pk MockPrivKeyLedgerEd25519) Bytes() []byte { | |||
return nil | |||
} | |||
// Sign returns a real SignatureLedger, if the msg matches what we expect | |||
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 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 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) | |||
} | |||
return false | |||
} | |||
//////////////////////////////////////////// | |||
// pubkey | |||
// PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes | |||
type PubKeyLedgerEd25519 struct { | |||
crypto.PubKeyEd25519 | |||
} | |||
// 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 PubKeyLedgerEd25519) Bytes() []byte { | |||
return wire.BinaryBytes(pk.Wrap()) | |||
} | |||
// VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand | |||
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 PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool { | |||
if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok { | |||
return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap()) | |||
} | |||
return false | |||
} | |||
/*** registration with go-data ***/ | |||
func init() { | |||
crypto.PrivKeyMapper. | |||
RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519). | |||
RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11) | |||
crypto.PubKeyMapper. | |||
RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519) | |||
} | |||
// Wrap fulfils interface for PrivKey struct | |||
func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey { | |||
return crypto.PrivKey{PrivKeyInner: pk} | |||
} | |||
// Wrap fulfils interface for PrivKey struct | |||
func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey { | |||
return crypto.PrivKey{PrivKeyInner: pk} | |||
} | |||
// Wrap fulfils interface for PubKey struct | |||
func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey { | |||
return crypto.PubKey{PubKeyInner: pk} | |||
} |
@ -0,0 +1,142 @@ | |||
package nano | |||
import ( | |||
"encoding/hex" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
crypto "github.com/tendermint/go-crypto" | |||
) | |||
func TestLedgerKeys(t *testing.T) { | |||
assert, require := assert.New(t), require.New(t) | |||
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 { | |||
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) | |||
} | |||
} | |||
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") | |||
} | |||
msg := []byte("kuhehfeohg") | |||
priv, err := NewPrivKeyLedgerEd25519Ed25519() | |||
require.Nil(err, "%+v", err) | |||
pub := priv.PubKey() | |||
sig := priv.Sign(msg) | |||
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) | |||
// make sure pubkeys serialize properly as well | |||
bs = pub.Bytes() | |||
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 := NewPrivKeyLedgerEd25519Ed25519() | |||
require.Error(err) | |||
led := PrivKeyLedgerEd25519{} // empty | |||
// or with some pub key | |||
ed := crypto.GenPrivKeyEd25519() | |||
led2 := PrivKeyLedgerEd25519{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) | |||
} |
@ -0,0 +1,63 @@ | |||
package nano | |||
import ( | |||
"bytes" | |||
"crypto/sha512" | |||
"github.com/pkg/errors" | |||
) | |||
const ( | |||
App = 0x80 | |||
Init = 0x00 | |||
Update = 0x01 | |||
Digest = 0x02 | |||
MaxChunk = 253 | |||
KeyLength = 32 | |||
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") | |||
} | |||
sig = resp[len(separator):] | |||
return key, sig, nil | |||
} | |||
func hashMsg(data []byte) []byte { | |||
res := sha512.Sum512(data) | |||
return res[:] | |||
} |
@ -0,0 +1,159 @@ | |||
package nano | |||
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) | |||
cases := []struct { | |||
output string | |||
key string | |||
sig string | |||
valid bool | |||
}{ | |||
{ | |||
output: "80028E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C9300CAFE00787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", | |||
key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93", | |||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400", | |||
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) | |||
} | |||
} | |||
} | |||
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{ | |||
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 { | |||
msg, key, sig, err := toBytes(tc) | |||
require.Nil(err, "%d: %+v", i, err) | |||
pk, err := parseEdKey(key) | |||
require.Nil(err, "%d: %+v", i, err) | |||
psig, err := parseSig(sig) | |||
require.Nil(err, "%d: %+v", i, err) | |||
// it is not the signature of the message itself | |||
valid := pk.VerifyBytes(msg, psig) | |||
assert.False(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) | |||
} | |||
} |
@ -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) | |||
} | |||
} | |||
} |
@ -1,3 +1,3 @@ | |||
package crypto | |||
const Version = "0.3.0" | |||
const Version = "0.4.0" |