* signer: introduce external signer command * cmd/signer, rpc: Implement new signer. Add info about remote user to Context * signer: refactored request/response, made use of urfave.cli * cmd/signer: Use common flags * cmd/signer: methods to validate calldata against abi * cmd/signer: work on abi parser * signer: add mutex around UI * cmd/signer: add json 4byte directory, remove passwords from api * cmd/signer: minor changes * cmd/signer: Use ErrRequestDenied, enable lightkdf * cmd/signer: implement tests * cmd/signer: made possible for UI to modify tx parameters * cmd/signer: refactors, removed channels in ui comms, added UI-api via stdin/out * cmd/signer: Made lowercase json-definitions, added UI-signer test functionality * cmd/signer: update documentation * cmd/signer: fix bugs, improve abi detection, abi argument display * cmd/signer: minor change in json format * cmd/signer: rework json communication * cmd/signer: implement mixcase addresses in API, fix json id bug * cmd/signer: rename fromaccount, update pythonpoc with new json encoding format * cmd/signer: make use of new abi interface * signer: documentation * signer/main: remove redundant option * signer: implement audit logging * signer: create package 'signer', minor changes * common: add 0x-prefix to mixcaseaddress in json marshalling + validation * signer, rules, storage: implement rules + ephemeral storage for signer rules * signer: implement OnApprovedTx, change signing response (API BREAKAGE) * signer: refactoring + documentation * signer/rules: implement dispatching to next handler * signer: docs * signer/rules: hide json-conversion from users, ensure context is cleaned * signer: docs * signer: implement validation rules, change signature of call_info * signer: fix log flaw with string pointer * signer: implement custom 4byte databsae that saves submitted signatures * signer/storage: implement aes-gcm-backed credential storage * accounts: implement json unmarshalling of url * signer: fix listresponse, fix gas->uint64 * node: make http/ipc start methods public * signer: add ipc capability+review concerns * accounts: correct docstring * signer: address review concerns * rpc: go fmt -s * signer: review concerns+ baptize Clef * signer,node: move Start-functions to separate file * signer: formatting
		
			
				
	
	
		
			387 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			387 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2018 The go-ethereum Authors
 | 
						|
// This file is part of go-ethereum.
 | 
						|
//
 | 
						|
// go-ethereum is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// go-ethereum is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
// GNU General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU General Public License
 | 
						|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
 | 
						|
//
 | 
						|
package core
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"math/big"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/ethereum/go-ethereum/accounts/keystore"
 | 
						|
	"github.com/ethereum/go-ethereum/cmd/utils"
 | 
						|
	"github.com/ethereum/go-ethereum/common"
 | 
						|
	"github.com/ethereum/go-ethereum/common/hexutil"
 | 
						|
	"github.com/ethereum/go-ethereum/core/types"
 | 
						|
	"github.com/ethereum/go-ethereum/internal/ethapi"
 | 
						|
	"github.com/ethereum/go-ethereum/rlp"
 | 
						|
)
 | 
						|
 | 
						|
//Used for testing
 | 
						|
type HeadlessUI struct {
 | 
						|
	controller chan string
 | 
						|
}
 | 
						|
 | 
						|
func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) {
 | 
						|
}
 | 
						|
 | 
						|
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
 | 
						|
	fmt.Printf("OnApproved called")
 | 
						|
}
 | 
						|
 | 
						|
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
 | 
						|
 | 
						|
	switch <-ui.controller {
 | 
						|
	case "Y":
 | 
						|
		return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
 | 
						|
	case "M": //Modify
 | 
						|
		old := big.Int(request.Transaction.Value)
 | 
						|
		newVal := big.NewInt(0).Add(&old, big.NewInt(1))
 | 
						|
		request.Transaction.Value = hexutil.Big(*newVal)
 | 
						|
		return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
 | 
						|
	default:
 | 
						|
		return SignTxResponse{request.Transaction, false, ""}, nil
 | 
						|
	}
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
 | 
						|
	if "Y" == <-ui.controller {
 | 
						|
		return SignDataResponse{true, <-ui.controller}, nil
 | 
						|
	}
 | 
						|
	return SignDataResponse{false, ""}, nil
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
 | 
						|
 | 
						|
	return ExportResponse{<-ui.controller == "Y"}, nil
 | 
						|
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
 | 
						|
 | 
						|
	if "Y" == <-ui.controller {
 | 
						|
		return ImportResponse{true, <-ui.controller, <-ui.controller}, nil
 | 
						|
	}
 | 
						|
	return ImportResponse{false, "", ""}, nil
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) {
 | 
						|
 | 
						|
	switch <-ui.controller {
 | 
						|
	case "A":
 | 
						|
		return ListResponse{request.Accounts}, nil
 | 
						|
	case "1":
 | 
						|
		l := make([]Account, 1)
 | 
						|
		l[0] = request.Accounts[1]
 | 
						|
		return ListResponse{l}, nil
 | 
						|
	default:
 | 
						|
		return ListResponse{nil}, nil
 | 
						|
	}
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
 | 
						|
 | 
						|
	if "Y" == <-ui.controller {
 | 
						|
		return NewAccountResponse{true, <-ui.controller}, nil
 | 
						|
	}
 | 
						|
	return NewAccountResponse{false, ""}, nil
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ShowError(message string) {
 | 
						|
	//stdout is used by communication
 | 
						|
	fmt.Fprint(os.Stderr, message)
 | 
						|
}
 | 
						|
func (ui *HeadlessUI) ShowInfo(message string) {
 | 
						|
	//stdout is used by communication
 | 
						|
	fmt.Fprint(os.Stderr, message)
 | 
						|
}
 | 
						|
 | 
						|
func tmpDirName(t *testing.T) string {
 | 
						|
	d, err := ioutil.TempDir("", "eth-keystore-test")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	d, err = filepath.EvalSymlinks(d)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	return d
 | 
						|
}
 | 
						|
 | 
						|
func setup(t *testing.T) (*SignerAPI, chan string) {
 | 
						|
 | 
						|
	controller := make(chan string, 10)
 | 
						|
 | 
						|
	db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
 | 
						|
	if err != nil {
 | 
						|
		utils.Fatalf(err.Error())
 | 
						|
	}
 | 
						|
	var (
 | 
						|
		ui  = &HeadlessUI{controller}
 | 
						|
		api = NewSignerAPI(
 | 
						|
			1,
 | 
						|
			tmpDirName(t),
 | 
						|
			true,
 | 
						|
			ui,
 | 
						|
			db,
 | 
						|
			true)
 | 
						|
	)
 | 
						|
	return api, controller
 | 
						|
}
 | 
						|
func createAccount(control chan string, api *SignerAPI, t *testing.T) {
 | 
						|
 | 
						|
	control <- "Y"
 | 
						|
	control <- "apassword"
 | 
						|
	_, err := api.New(context.Background())
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	// Some time to allow changes to propagate
 | 
						|
	time.Sleep(250 * time.Millisecond)
 | 
						|
}
 | 
						|
func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) {
 | 
						|
	control <- "N"
 | 
						|
	acc, err := api.New(context.Background())
 | 
						|
	if err != ErrRequestDenied {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if acc.Address != (common.Address{}) {
 | 
						|
		t.Fatal("Empty address should be returned")
 | 
						|
	}
 | 
						|
}
 | 
						|
func list(control chan string, api *SignerAPI, t *testing.T) []Account {
 | 
						|
	control <- "A"
 | 
						|
	list, err := api.List(context.Background())
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	return list
 | 
						|
}
 | 
						|
 | 
						|
func TestNewAcc(t *testing.T) {
 | 
						|
 | 
						|
	api, control := setup(t)
 | 
						|
	verifyNum := func(num int) {
 | 
						|
		if list := list(control, api, t); len(list) != num {
 | 
						|
			t.Errorf("Expected %d accounts, got %d", num, len(list))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// Testing create and create-deny
 | 
						|
	createAccount(control, api, t)
 | 
						|
	createAccount(control, api, t)
 | 
						|
	failCreateAccount(control, api, t)
 | 
						|
	failCreateAccount(control, api, t)
 | 
						|
	createAccount(control, api, t)
 | 
						|
	failCreateAccount(control, api, t)
 | 
						|
	createAccount(control, api, t)
 | 
						|
	failCreateAccount(control, api, t)
 | 
						|
	verifyNum(4)
 | 
						|
 | 
						|
	// Testing listing:
 | 
						|
	// Listing one Account
 | 
						|
	control <- "1"
 | 
						|
	list, err := api.List(context.Background())
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(list) != 1 {
 | 
						|
		t.Fatalf("List should only show one Account")
 | 
						|
	}
 | 
						|
	// Listing denied
 | 
						|
	control <- "Nope"
 | 
						|
	list, err = api.List(context.Background())
 | 
						|
	if len(list) != 0 {
 | 
						|
		t.Fatalf("List should be empty")
 | 
						|
	}
 | 
						|
	if err != ErrRequestDenied {
 | 
						|
		t.Fatal("Expected deny")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestSignData(t *testing.T) {
 | 
						|
 | 
						|
	api, control := setup(t)
 | 
						|
	//Create two accounts
 | 
						|
	createAccount(control, api, t)
 | 
						|
	createAccount(control, api, t)
 | 
						|
	control <- "1"
 | 
						|
	list, err := api.List(context.Background())
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	a := common.NewMixedcaseAddress(list[0].Address)
 | 
						|
 | 
						|
	control <- "Y"
 | 
						|
	control <- "wrongpassword"
 | 
						|
	h, err := api.Sign(context.Background(), a, []byte("EHLO world"))
 | 
						|
	if h != nil {
 | 
						|
		t.Errorf("Expected nil-data, got %x", h)
 | 
						|
	}
 | 
						|
	if err != keystore.ErrDecrypt {
 | 
						|
		t.Errorf("Expected ErrLocked! %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	control <- "No way"
 | 
						|
	h, err = api.Sign(context.Background(), a, []byte("EHLO world"))
 | 
						|
	if h != nil {
 | 
						|
		t.Errorf("Expected nil-data, got %x", h)
 | 
						|
	}
 | 
						|
	if err != ErrRequestDenied {
 | 
						|
		t.Errorf("Expected ErrRequestDenied! %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	control <- "Y"
 | 
						|
	control <- "apassword"
 | 
						|
	h, err = api.Sign(context.Background(), a, []byte("EHLO world"))
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if h == nil || len(h) != 65 {
 | 
						|
		t.Errorf("Expected 65 byte signature (got %d bytes)", len(h))
 | 
						|
	}
 | 
						|
}
 | 
						|
func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
 | 
						|
	to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
 | 
						|
	gas := hexutil.Uint64(21000)
 | 
						|
	gasPrice := (hexutil.Big)(*big.NewInt(2000000000))
 | 
						|
	value := (hexutil.Big)(*big.NewInt(1e18))
 | 
						|
	nonce := (hexutil.Uint64)(0)
 | 
						|
	data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
 | 
						|
	tx := SendTxArgs{
 | 
						|
		From:     from,
 | 
						|
		To:       &to,
 | 
						|
		Gas:      gas,
 | 
						|
		GasPrice: gasPrice,
 | 
						|
		Value:    value,
 | 
						|
		Data:     &data,
 | 
						|
		Nonce:    nonce}
 | 
						|
	return tx
 | 
						|
}
 | 
						|
 | 
						|
func TestSignTx(t *testing.T) {
 | 
						|
 | 
						|
	var (
 | 
						|
		list      Accounts
 | 
						|
		res, res2 *ethapi.SignTransactionResult
 | 
						|
		err       error
 | 
						|
	)
 | 
						|
 | 
						|
	api, control := setup(t)
 | 
						|
	createAccount(control, api, t)
 | 
						|
	control <- "A"
 | 
						|
	list, err = api.List(context.Background())
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	a := common.NewMixedcaseAddress(list[0].Address)
 | 
						|
 | 
						|
	methodSig := "test(uint)"
 | 
						|
	tx := mkTestTx(a)
 | 
						|
 | 
						|
	control <- "Y"
 | 
						|
	control <- "wrongpassword"
 | 
						|
	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
 | 
						|
	if res != nil {
 | 
						|
		t.Errorf("Expected nil-response, got %v", res)
 | 
						|
	}
 | 
						|
	if err != keystore.ErrDecrypt {
 | 
						|
		t.Errorf("Expected ErrLocked! %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	control <- "No way"
 | 
						|
	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
 | 
						|
	if res != nil {
 | 
						|
		t.Errorf("Expected nil-response, got %v", res)
 | 
						|
	}
 | 
						|
	if err != ErrRequestDenied {
 | 
						|
		t.Errorf("Expected ErrRequestDenied! %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	control <- "Y"
 | 
						|
	control <- "apassword"
 | 
						|
	res, err = api.SignTransaction(context.Background(), tx, &methodSig)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	parsedTx := &types.Transaction{}
 | 
						|
	rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
 | 
						|
	//The tx should NOT be modified by the UI
 | 
						|
	if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
 | 
						|
		t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
 | 
						|
	}
 | 
						|
	control <- "Y"
 | 
						|
	control <- "apassword"
 | 
						|
 | 
						|
	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if !bytes.Equal(res.Raw, res2.Raw) {
 | 
						|
		t.Error("Expected tx to be unmodified by UI")
 | 
						|
	}
 | 
						|
 | 
						|
	//The tx is modified by the UI
 | 
						|
	control <- "M"
 | 
						|
	control <- "apassword"
 | 
						|
 | 
						|
	res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	parsedTx2 := &types.Transaction{}
 | 
						|
	rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
 | 
						|
	//The tx should be modified by the UI
 | 
						|
	if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
 | 
						|
		t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
 | 
						|
	}
 | 
						|
 | 
						|
	if bytes.Equal(res.Raw, res2.Raw) {
 | 
						|
		t.Error("Expected tx to be modified by UI")
 | 
						|
	}
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
func TestAsyncronousResponses(t *testing.T){
 | 
						|
 | 
						|
	//Set up one account
 | 
						|
	api, control := setup(t)
 | 
						|
	createAccount(control, api, t)
 | 
						|
 | 
						|
	// Two transactions, the second one with larger value than the first
 | 
						|
	tx1 := mkTestTx()
 | 
						|
	newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1))
 | 
						|
	tx2 := mkTestTx()
 | 
						|
	tx2.Value = (*hexutil.Big)(newVal)
 | 
						|
 | 
						|
	control <- "W" //wait
 | 
						|
	control <- "Y" //
 | 
						|
	control <- "apassword"
 | 
						|
	control <- "Y" //
 | 
						|
	control <- "apassword"
 | 
						|
 | 
						|
	var err error
 | 
						|
 | 
						|
	h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil)
 | 
						|
	h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil)
 | 
						|
 | 
						|
 | 
						|
	}
 | 
						|
*/
 |