signer, clef: remove passwords from responses clef<->ui

This commit is contained in:
Martin Holst Swende 2019-02-21 23:56:02 +01:00
parent bda88d91db
commit 4b0674d5eb
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
10 changed files with 215 additions and 219 deletions

View file

@ -35,8 +35,7 @@ Response to SignDataRequest
Example: Example:
```json ```json
{ {
"approved": true, "approved": true
"Password": "apassword"
} }
``` ```
### SignDataResponse - deny ### SignDataResponse - deny
@ -46,8 +45,7 @@ Response to SignDataRequest
Example: Example:
```json ```json
{ {
"approved": false, "approved": false
"Password": ""
} }
``` ```
### SignTxRequest ### SignTxRequest
@ -89,9 +87,9 @@ Example:
} }
} }
``` ```
### SignDataResponse - approve ### SignTxResponse - approve
Response to SignDataRequest. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction. Response to request to sign a transaction. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
Example: Example:
```json ```json
@ -105,19 +103,26 @@ Example:
"nonce": "0x4", "nonce": "0x4",
"data": "0x04030201" "data": "0x04030201"
}, },
"approved": true, "approved": true
"password": "apassword"
} }
``` ```
### SignDataResponse - deny ### SignTxResponse - deny
Response to SignDataRequest. When denying a request, there's no need to provide the transaction in return Response to SignTxRequest. When denying a request, there's no need to provide the transaction in return
Example: Example:
```json ```json
{ {
"approved": false, "transaction": {
"Password": "" "from": "0x",
"to": null,
"gas": "0x0",
"gasPrice": "0x0",
"value": "0x0",
"nonce": "0x0",
"data": null
},
"approved": false
} }
``` ```
### OnApproved - SignTransactionResult ### OnApproved - SignTransactionResult
@ -164,7 +169,7 @@ Example:
``` ```
### UserInputResponse ### UserInputResponse
Response to SignDataRequest Response to UserInputRequest
Example: Example:
```json ```json
@ -198,7 +203,7 @@ Example:
} }
} }
``` ```
### UserInputResponse ### ListResponse
Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not

View file

@ -119,7 +119,7 @@ var (
ruleFlag = cli.StringFlag{ ruleFlag = cli.StringFlag{
Name: "rules", Name: "rules",
Usage: "Enable rule-engine", Usage: "Enable rule-engine",
Value: "rules.json", Value: "",
} }
stdiouiFlag = cli.BoolFlag{ stdiouiFlag = cli.BoolFlag{
Name: "stdio-ui", Name: "stdio-ui",
@ -372,16 +372,13 @@ func signer(c *cli.Context) error {
var ( var (
api core.ExternalAPI api core.ExternalAPI
pwStorage storage.Storage = &storage.NoStorage{}
) )
configDir := c.GlobalString(configdirFlag.Name) configDir := c.GlobalString(configdirFlag.Name)
if stretchedKey, err := readMasterKey(c, ui); err != nil { if stretchedKey, err := readMasterKey(c, ui); err != nil {
log.Info("No master seed provided, rules disabled", "error", err) log.Info("No master seed provided, rules disabled", "error", err)
} else { } else {
if err != nil {
utils.Fatalf(err.Error())
}
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
// Generate domain specific keys // Generate domain specific keys
@ -390,24 +387,24 @@ func signer(c *cli.Context) error {
confkey := crypto.Keccak256([]byte("config"), stretchedKey) confkey := crypto.Keccak256([]byte("config"), stretchedKey)
// Initialize the encrypted storages // Initialize the encrypted storages
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
//Do we have a rule-file? //Do we have a rule-file?
ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name)) if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" {
ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFile))
if err != nil { if err != nil {
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile") log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
} else { } else {
hasher := sha256.New() shasum := sha256.Sum256(ruleJS)
hasher.Write(ruleJS) foundShaSum := hex.EncodeToString(shasum[:])
shasum := hasher.Sum(nil)
storedShasum := configStorage.Get("ruleset_sha256") storedShasum := configStorage.Get("ruleset_sha256")
if storedShasum != hex.EncodeToString(shasum) { if storedShasum != foundShaSum {
log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum) log.Info("Could not validate ruleset hash, rules not enabled", "got", foundShaSum, "expected", storedShasum)
} else { } else {
// Initialize rules // Initialize rules
ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage) ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage)
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
@ -417,6 +414,7 @@ func signer(c *cli.Context) error {
} }
} }
} }
}
var ( var (
chainId = c.GlobalInt64(chainIdFlag.Name) chainId = c.GlobalInt64(chainIdFlag.Name)
ksLoc = c.GlobalString(keystoreFlag.Name) ksLoc = c.GlobalString(keystoreFlag.Name)
@ -427,7 +425,7 @@ func signer(c *cli.Context) error {
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc, log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
"light-kdf", lightKdf, "advanced", advanced) "light-kdf", lightKdf, "advanced", advanced)
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf) am := core.StartClefAccountManager(ksLoc, nousb, lightKdf)
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced) apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
// Establish the bidirectional communication, by creating a new UI backend and registering // Establish the bidirectional communication, by creating a new UI backend and registering
// it with the UI. // it with the UI.
@ -798,7 +796,7 @@ func GenDoc(ctx *cli.Context) {
} }
{ // Sign plain text response { // Sign plain text response
add("SignDataResponse - approve", "Response to SignDataRequest", add("SignDataResponse - approve", "Response to SignDataRequest",
&core.SignDataResponse{Password: "apassword", Approved: true}) &core.SignDataResponse{Approved: true})
add("SignDataResponse - deny", "Response to SignDataRequest", add("SignDataResponse - deny", "Response to SignDataRequest",
&core.SignDataResponse{}) &core.SignDataResponse{})
} }
@ -833,9 +831,9 @@ func GenDoc(ctx *cli.Context) {
} }
{ // Sign tx response { // Sign tx response
data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01}) data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
add("SignDataResponse - approve", "Response to SignDataRequest. This response needs to contain the `transaction`"+ add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
", because the UI is free to make modifications to the transaction.", ", because the UI is free to make modifications to the transaction.",
&core.SignTxResponse{Password: "apassword", Approved: true, &core.SignTxResponse{Approved: true,
Transaction: core.SendTxArgs{ Transaction: core.SendTxArgs{
Data: &data, Data: &data,
Nonce: 0x4, Nonce: 0x4,
@ -846,9 +844,9 @@ func GenDoc(ctx *cli.Context) {
Gas: 1000, Gas: 1000,
Input: nil, Input: nil,
}}) }})
add("SignDataResponse - deny", "Response to SignDataRequest. When denying a request, there's no need to "+ add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
"provide the transaction in return", "provide the transaction in return",
&core.SignDataResponse{}) &core.SignTxResponse{})
} }
{ // WHen a signed tx is ready to go out { // WHen a signed tx is ready to go out
desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" + desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
@ -874,7 +872,7 @@ func GenDoc(ctx *cli.Context) {
{ // User input { // User input
add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)", add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"}) &core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
add("UserInputResponse", "Response to SignDataRequest", add("UserInputResponse", "Response to UserInputRequest",
&core.UserInputResponse{Text: "The textual response from user"}) &core.UserInputResponse{Text: "The textual response from user"})
} }
{ // List request { // List request
@ -888,7 +886,7 @@ func GenDoc(ctx *cli.Context) {
{b, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}}, {b, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
}) })
add("UserInputResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+ add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not", "Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
&core.ListResponse{ &core.ListResponse{
Accounts: []accounts.Account{ Accounts: []accounts.Account{

View file

@ -21,8 +21,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/signer/storage"
"math/big" "math/big"
"reflect" "reflect"
"strings"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
@ -97,6 +99,7 @@ type SignerAPI struct {
UI UIClientAPI UI UIClientAPI
validator *Validator validator *Validator
rejectMode bool rejectMode bool
credentials storage.Storage
} }
// Metadata about a request // Metadata about a request
@ -231,11 +234,11 @@ var ErrRequestDenied = errors.New("Request denied")
// key that is generated when a new Account is created. // key that is generated when a new Account is created.
// noUSB disables USB support that is required to support hardware devices such as // noUSB disables USB support that is required to support hardware devices such as
// ledger and trezor. // ledger and trezor.
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool) *SignerAPI { func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool, credentials storage.Storage) *SignerAPI {
if advancedMode { if advancedMode {
log.Info("Clef is in advanced mode: will warn instead of reject") log.Info("Clef is in advanced mode: will warn instead of reject")
} }
signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode} signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode, credentials}
if !noUSB { if !noUSB {
signer.startUSBListener() signer.startUSBListener()
} }
@ -356,24 +359,27 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) {
if len(be) == 0 { if len(be) == 0 {
return common.Address{}, errors.New("password based accounts not supported") return common.Address{}, errors.New("password based accounts not supported")
} }
var ( if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil {
resp NewAccountResponse
err error
)
// Three retries to get a valid password
for i := 0; i < 3; i++ {
resp, err = api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)})
if err != nil {
return common.Address{}, err return common.Address{}, err
} } else if !resp.Approved {
if !resp.Approved {
return common.Address{}, ErrRequestDenied return common.Address{}, ErrRequestDenied
} }
if pwErr := ValidatePasswordFormat(resp.Password); pwErr != nil {
// Three retries to get a valid password
for i := 0; i < 3; i++ {
resp, err := api.UI.OnInputRequired(UserInputRequest{
"New account password",
fmt.Sprintf("Please enter a password for the new account to be created (attempt %d of 3)", i),
true})
if err != nil {
log.Warn("error obtaining password", "attempt", i, "error", err)
continue
}
if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil {
api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr)) api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
} else { } else {
// No error // No error
acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Password) acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text)
return acc.Address, err return acc.Address, err
} }
} }
@ -427,6 +433,24 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
return modified return modified
} }
func (api *SignerAPI) lookupPassword(address common.Address) string {
return api.credentials.Get(strings.ToLower(address.String()))
}
func (api *SignerAPI) lookupOrQueryPassword(address common.Address, title, prompt string) (string, error) {
if pw := api.lookupPassword(address); pw != "" {
return pw, nil
} else {
pwResp, err := api.UI.OnInputRequired(UserInputRequest{title, prompt, true})
if err != nil {
log.Warn("error obtaining password", "error", err)
// We'll not forward the error here, in case the error contains info about the response from the UI,
// which could leak the password if it was malformed json or something
return "", errors.New("internal error")
}
return pwResp.Text, nil
}
}
// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form // SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form
func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
var ( var (
@ -470,9 +494,14 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
} }
// Convert fields into a real transaction // Convert fields into a real transaction
var unsignedTx = result.Transaction.toTransaction() var unsignedTx = result.Transaction.toTransaction()
// Get the password for the transaction
pw, err := api.lookupOrQueryPassword(acc.Address, "Account password",
fmt.Sprintf("Please enter the password for account %s", acc.Address.String()))
if err != nil {
return nil, err
}
// The one to sign is the one that was returned from the UI // The one to sign is the one that was returned from the UI
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID) signedTx, err := wallet.SignTxWithPassphrase(acc, pw, unsignedTx, api.chainID)
if err != nil { if err != nil {
api.UI.ShowError(err.Error()) api.UI.ShowError(err.Error())
return nil, err return nil, err

View file

@ -19,8 +19,8 @@ package core
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/signer/storage"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
@ -38,47 +38,45 @@ import (
) )
//Used for testing //Used for testing
type HeadlessUI struct { type headlessUi struct {
controller chan string approveCh chan string // to send approve/deny
inputCh chan string // to send password
} }
func (ui *HeadlessUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) { func (ui *headlessUi) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
return UserInputResponse{}, errors.New("not implemented") input := <-ui.inputCh
return UserInputResponse{Text: input}, nil
} }
func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) { func (ui *headlessUi) OnSignerStartup(info StartupInfo) {}
} func (ui *headlessUi) RegisterUIServer(api *UIServerAPI) {}
func (ui *HeadlessUI) RegisterUIServer(api *UIServerAPI) { func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {}
}
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) { func (ui *headlessUi) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
fmt.Printf("OnApproved()\n")
}
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { switch <-ui.approveCh {
switch <-ui.controller {
case "Y": case "Y":
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil return SignTxResponse{request.Transaction, true}, nil
case "M": //Modify case "M": // modify
// The headless UI always modifies the transaction
old := big.Int(request.Transaction.Value) old := big.Int(request.Transaction.Value)
newVal := big.NewInt(0).Add(&old, big.NewInt(1)) newVal := big.NewInt(0).Add(&old, big.NewInt(1))
request.Transaction.Value = hexutil.Big(*newVal) request.Transaction.Value = hexutil.Big(*newVal)
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil return SignTxResponse{request.Transaction, true}, nil
default: default:
return SignTxResponse{request.Transaction, false, ""}, nil return SignTxResponse{request.Transaction, false}, nil
} }
} }
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { func (ui *headlessUi) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
if "Y" == <-ui.controller { approved := "Y" == <-ui.approveCh
return SignDataResponse{true, <-ui.controller}, nil return SignDataResponse{approved}, nil
}
return SignDataResponse{false, ""}, nil
} }
func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) { func (ui *headlessUi) ApproveListing(request *ListRequest) (ListResponse, error) {
switch <-ui.controller { approval := <-ui.approveCh
//fmt.Printf("approval %s\n", approval)
switch approval {
case "A": case "A":
return ListResponse{request.Accounts}, nil return ListResponse{request.Accounts}, nil
case "1": case "1":
@ -90,19 +88,19 @@ func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error)
} }
} }
func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { func (ui *headlessUi) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
if "Y" == <-ui.controller { if "Y" == <-ui.approveCh {
return NewAccountResponse{true, <-ui.controller}, nil return NewAccountResponse{true}, nil
} }
return NewAccountResponse{false, ""}, nil return NewAccountResponse{false}, nil
} }
func (ui *HeadlessUI) ShowError(message string) { func (ui *headlessUi) ShowError(message string) {
//stdout is used by communication //stdout is used by communication
fmt.Fprintln(os.Stderr, message) fmt.Fprintln(os.Stderr, message)
} }
func (ui *HeadlessUI) ShowInfo(message string) { func (ui *headlessUi) ShowInfo(message string) {
//stdout is used by communication //stdout is used by communication
fmt.Fprintln(os.Stderr, message) fmt.Fprintln(os.Stderr, message)
} }
@ -119,25 +117,20 @@ func tmpDirName(t *testing.T) string {
return d return d
} }
func setup(t *testing.T) (*SignerAPI, chan string) { func setup(t *testing.T) (*SignerAPI, *headlessUi) {
controller := make(chan string, 20)
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json") db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatal(err.Error())
} }
var ( ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
ui = &HeadlessUI{controller} am := StartClefAccountManager(tmpDirName(t), true, true)
am = StartClefAccountManager(tmpDirName(t), true, true) api := NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
api = NewSignerAPI(am, 1337, true, ui, db, true) return api, ui
)
return api, controller
}
func createAccount(control chan string, api *SignerAPI, t *testing.T) {
control <- "Y" }
control <- "a_long_password" func createAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
ui.approveCh <- "Y"
ui.inputCh <- "a_long_password"
_, err := api.New(context.Background()) _, err := api.New(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -146,14 +139,13 @@ func createAccount(control chan string, api *SignerAPI, t *testing.T) {
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
} }
func failCreateAccountWithPassword(control chan string, api *SignerAPI, password string, t *testing.T) { func failCreateAccountWithPassword(ui *headlessUi, api *SignerAPI, password string, t *testing.T) {
control <- "Y" ui.approveCh <- "Y"
control <- password // We will be asked three times to provide a suitable password
control <- "Y" ui.inputCh <- password
control <- password ui.inputCh <- password
control <- "Y" ui.inputCh <- password
control <- password
addr, err := api.New(context.Background()) addr, err := api.New(context.Background())
if err == nil { if err == nil {
@ -164,8 +156,8 @@ func failCreateAccountWithPassword(control chan string, api *SignerAPI, password
} }
} }
func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) { func failCreateAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
control <- "N" ui.approveCh <- "N"
addr, err := api.New(context.Background()) addr, err := api.New(context.Background())
if err != ErrRequestDenied { if err != ErrRequestDenied {
t.Fatal(err) t.Fatal(err)
@ -175,19 +167,20 @@ func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) {
} }
} }
func list(control chan string, api *SignerAPI, t *testing.T) []common.Address { func list(ui *headlessUi, api *SignerAPI, t *testing.T) ([]common.Address, error) {
control <- "A" ui.approveCh <- "A"
list, err := api.List(context.Background()) return api.List(context.Background())
if err != nil {
t.Fatal(err)
}
return list
} }
func TestNewAcc(t *testing.T) { func TestNewAcc(t *testing.T) {
api, control := setup(t) api, control := setup(t)
verifyNum := func(num int) { verifyNum := func(num int) {
if list := list(control, api, t); len(list) != num { list, err := list(control, api, t)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if len(list) != num {
t.Errorf("Expected %d accounts, got %d", num, len(list)) t.Errorf("Expected %d accounts, got %d", num, len(list))
} }
} }
@ -200,18 +193,16 @@ func TestNewAcc(t *testing.T) {
failCreateAccount(control, api, t) failCreateAccount(control, api, t)
createAccount(control, api, t) createAccount(control, api, t)
failCreateAccount(control, api, t) failCreateAccount(control, api, t)
verifyNum(4) verifyNum(4)
// Fail to create this, due to bad password // Fail to create this, due to bad password
failCreateAccountWithPassword(control, api, "short", t) failCreateAccountWithPassword(control, api, "short", t)
failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t) failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
verifyNum(4) verifyNum(4)
// Testing listing: // Testing listing:
// Listing one Account // Listing one Account
control <- "1" control.approveCh <- "1"
list, err := api.List(context.Background()) list, err := api.List(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -220,7 +211,7 @@ func TestNewAcc(t *testing.T) {
t.Fatalf("List should only show one Account") t.Fatalf("List should only show one Account")
} }
// Listing denied // Listing denied
control <- "Nope" control.approveCh <- "Nope"
list, err = api.List(context.Background()) list, err = api.List(context.Background())
if len(list) != 0 { if len(list) != 0 {
t.Fatalf("List should be empty") t.Fatalf("List should be empty")
@ -257,7 +248,7 @@ func TestSignTx(t *testing.T) {
api, control := setup(t) api, control := setup(t)
createAccount(control, api, t) createAccount(control, api, t)
control <- "A" control.approveCh <- "A"
list, err = api.List(context.Background()) list, err = api.List(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -267,8 +258,8 @@ func TestSignTx(t *testing.T) {
methodSig := "test(uint)" methodSig := "test(uint)"
tx := mkTestTx(a) tx := mkTestTx(a)
control <- "Y" control.approveCh <- "Y"
control <- "wrongpassword" control.inputCh <- "wrongpassword"
res, err = api.SignTransaction(context.Background(), tx, &methodSig) res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if res != nil { if res != nil {
t.Errorf("Expected nil-response, got %v", res) t.Errorf("Expected nil-response, got %v", res)
@ -276,7 +267,7 @@ func TestSignTx(t *testing.T) {
if err != keystore.ErrDecrypt { if err != keystore.ErrDecrypt {
t.Errorf("Expected ErrLocked! %v", err) t.Errorf("Expected ErrLocked! %v", err)
} }
control <- "No way" control.approveCh <- "No way"
res, err = api.SignTransaction(context.Background(), tx, &methodSig) res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if res != nil { if res != nil {
t.Errorf("Expected nil-response, got %v", res) t.Errorf("Expected nil-response, got %v", res)
@ -284,8 +275,9 @@ func TestSignTx(t *testing.T) {
if err != ErrRequestDenied { if err != ErrRequestDenied {
t.Errorf("Expected ErrRequestDenied! %v", err) t.Errorf("Expected ErrRequestDenied! %v", err)
} }
control <- "Y" // Sign with correct password
control <- "a_long_password" control.approveCh <- "Y"
control.inputCh <- "a_long_password"
res, err = api.SignTransaction(context.Background(), tx, &methodSig) res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil { if err != nil {
@ -298,8 +290,8 @@ func TestSignTx(t *testing.T) {
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
} }
control <- "Y" control.approveCh <- "Y"
control <- "a_long_password" control.inputCh <- "a_long_password"
res2, err = api.SignTransaction(context.Background(), tx, &methodSig) res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil { if err != nil {
@ -310,8 +302,8 @@ func TestSignTx(t *testing.T) {
} }
//The tx is modified by the UI //The tx is modified by the UI
control <- "M" control.approveCh <- "M"
control <- "a_long_password" control.inputCh <- "a_long_password"
res2, err = api.SignTransaction(context.Background(), tx, &methodSig) res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil { if err != nil {
@ -329,31 +321,3 @@ func TestSignTx(t *testing.T) {
} }
} }
/*
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 <- "a_long_password"
control <- "Y" //
control <- "a_long_password"
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)
}
*/

View file

@ -156,9 +156,9 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
showMetadata(request.Meta) showMetadata(request.Meta)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
if !ui.confirm() { if !ui.confirm() {
return SignTxResponse{request.Transaction, false, ""}, nil return SignTxResponse{request.Transaction, false}, nil
} }
return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil return SignTxResponse{request.Transaction, true}, nil
} }
// ApproveSignData prompt the user for confirmation to request to sign data // ApproveSignData prompt the user for confirmation to request to sign data
@ -178,9 +178,9 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta) showMetadata(request.Meta)
if !ui.confirm() { if !ui.confirm() {
return SignDataResponse{false, ""}, nil return SignDataResponse{false}, nil
} }
return SignDataResponse{true, ui.readPassword()}, nil return SignDataResponse{true}, nil
} }
// ApproveListing prompt the user for confirmation to list accounts // ApproveListing prompt the user for confirmation to list accounts
@ -217,9 +217,9 @@ func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccou
fmt.Printf("and the address is returned to the external caller\n\n") fmt.Printf("and the address is returned to the external caller\n\n")
showMetadata(request.Meta) showMetadata(request.Meta)
if !ui.confirm() { if !ui.confirm() {
return NewAccountResponse{false, ""}, nil return NewAccountResponse{false}, nil
} }
return NewAccountResponse{true, ui.readPassword()}, nil return NewAccountResponse{true}, nil
} }
// ShowError displays error message to user // ShowError displays error message to user

View file

@ -139,8 +139,14 @@ func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, l
if err != nil { if err != nil {
return nil, err return nil, err
} }
pw, err := api.lookupOrQueryPassword(account.Address,
"Password for signing",
fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex()))
if err != nil {
return nil, err
}
// Sign the data with the wallet // Sign the data with the wallet
signature, err := wallet.SignDataWithPassphrase(account, res.Password, req.ContentType, req.Rawdata) signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -179,15 +179,15 @@ func TestSignData(t *testing.T) {
//Create two accounts //Create two accounts
createAccount(control, api, t) createAccount(control, api, t)
createAccount(control, api, t) createAccount(control, api, t)
control <- "1" control.approveCh <- "1"
list, err := api.List(context.Background()) list, err := api.List(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
a := common.NewMixedcaseAddress(list[0]) a := common.NewMixedcaseAddress(list[0])
control <- "Y" control.approveCh <- "Y"
control <- "wrongpassword" control.inputCh <- "wrongpassword"
signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if signature != nil { if signature != nil {
t.Errorf("Expected nil-data, got %x", signature) t.Errorf("Expected nil-data, got %x", signature)
@ -195,7 +195,7 @@ func TestSignData(t *testing.T) {
if err != keystore.ErrDecrypt { if err != keystore.ErrDecrypt {
t.Errorf("Expected ErrLocked! '%v'", err) t.Errorf("Expected ErrLocked! '%v'", err)
} }
control <- "No way" control.approveCh <- "No way"
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if signature != nil { if signature != nil {
t.Errorf("Expected nil-data, got %x", signature) t.Errorf("Expected nil-data, got %x", signature)
@ -204,8 +204,8 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected ErrRequestDenied! '%v'", err) t.Errorf("Expected ErrRequestDenied! '%v'", err)
} }
// text/plain // text/plain
control <- "Y" control.approveCh <- "Y"
control <- "a_long_password" control.inputCh <- "a_long_password"
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world"))) signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -214,8 +214,8 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature)) t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
} }
// data/typed // data/typed
control <- "Y" control.approveCh <- "Y"
control <- "a_long_password" control.inputCh <- "a_long_password"
signature, err = api.SignTypedData(context.Background(), a, typedData) signature, err = api.SignTypedData(context.Background(), a, typedData)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -22,7 +22,6 @@ import (
"os" "os"
"strings" "strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/signer/core" "github.com/ethereum/go-ethereum/signer/core"
@ -42,7 +41,7 @@ func consoleOutput(call otto.FunctionCall) otto.Value {
for _, argument := range call.ArgumentList { for _, argument := range call.ArgumentList {
output = append(output, fmt.Sprintf("%v", argument)) output = append(output, fmt.Sprintf("%v", argument))
} }
fmt.Fprintln(os.Stdout, strings.Join(output, " ")) fmt.Fprintln(os.Stderr, strings.Join(output, " "))
return otto.Value{} return otto.Value{}
} }
@ -51,15 +50,13 @@ func consoleOutput(call otto.FunctionCall) otto.Value {
type rulesetUI struct { type rulesetUI struct {
next core.UIClientAPI // The next handler, for manual processing next core.UIClientAPI // The next handler, for manual processing
storage storage.Storage storage storage.Storage
credentials storage.Storage
jsRules string // The rules to use jsRules string // The rules to use
} }
func NewRuleEvaluator(next core.UIClientAPI, jsbackend, credentialsBackend storage.Storage) (*rulesetUI, error) { func NewRuleEvaluator(next core.UIClientAPI, jsbackend storage.Storage) (*rulesetUI, error) {
c := &rulesetUI{ c := &rulesetUI{
next: next, next: next,
storage: jsbackend, storage: jsbackend,
credentials: credentialsBackend,
jsRules: "", jsRules: "",
} }
@ -153,18 +150,12 @@ func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse,
if approved { if approved {
return core.SignTxResponse{ return core.SignTxResponse{
Transaction: request.Transaction, Transaction: request.Transaction,
Approved: true, Approved: true},
Password: r.lookupPassword(request.Transaction.From.Address()),
},
nil nil
} }
return core.SignTxResponse{Approved: false}, err return core.SignTxResponse{Approved: false}, err
} }
func (r *rulesetUI) lookupPassword(address common.Address) string {
return r.credentials.Get(strings.ToLower(address.String()))
}
func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
jsonreq, err := json.Marshal(request) jsonreq, err := json.Marshal(request)
approved, err := r.checkApproval("ApproveSignData", jsonreq, err) approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
@ -173,9 +164,9 @@ func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDat
return r.next.ApproveSignData(request) return r.next.ApproveSignData(request)
} }
if approved { if approved {
return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil return core.SignDataResponse{Approved: true}, nil
} }
return core.SignDataResponse{Approved: false, Password: ""}, err return core.SignDataResponse{Approved: false}, err
} }
// OnInputRequired not handled by rules // OnInputRequired not handled by rules

View file

@ -84,11 +84,11 @@ func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
} }
func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil return core.SignTxResponse{Transaction: request.Transaction, Approved: false}, nil
} }
func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
return core.SignDataResponse{Approved: false, Password: ""}, nil return core.SignDataResponse{Approved: false}, nil
} }
func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
@ -96,7 +96,7 @@ func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse
} }
func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
return core.NewAccountResponse{Approved: false, Password: ""}, nil return core.NewAccountResponse{Approved: false}, nil
} }
func (alwaysDenyUI) ShowError(message string) { func (alwaysDenyUI) ShowError(message string) {
@ -112,7 +112,7 @@ func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
} }
func initRuleEngine(js string) (*rulesetUI, error) { func initRuleEngine(js string) (*rulesetUI, error) {
r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage())
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create js engine: %v", err) return nil, fmt.Errorf("failed to create js engine: %v", err)
} }
@ -248,8 +248,7 @@ func TestForwarding(t *testing.T) {
js := "" js := ""
ui := &dummyUI{make([]string, 0)} ui := &dummyUI{make([]string, 0)}
jsBackend := storage.NewEphemeralStorage() jsBackend := storage.NewEphemeralStorage()
credBackend := storage.NewEphemeralStorage() r, err := NewRuleEvaluator(ui, jsBackend)
r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
if err != nil { if err != nil {
t.Fatalf("Failed to create js engine: %v", err) t.Fatalf("Failed to create js engine: %v", err)
} }
@ -567,7 +566,7 @@ func TestContextIsCleared(t *testing.T) {
} }
` `
ui := &dontCallMe{t} ui := &dontCallMe{t}
r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage())
if err != nil { if err != nil {
t.Fatalf("Failed to create js engine: %v", err) t.Fatalf("Failed to create js engine: %v", err)
} }

View file

@ -17,10 +17,6 @@
package storage package storage
import (
"fmt"
)
type Storage interface { type Storage interface {
// Put stores a value by key. 0-length keys results in no-op // Put stores a value by key. 0-length keys results in no-op
Put(key, value string) Put(key, value string)
@ -39,7 +35,7 @@ func (s *EphemeralStorage) Put(key, value string) {
if len(key) == 0 { if len(key) == 0 {
return return
} }
fmt.Printf("storage: put %v -> %v\n", key, value) //fmt.Printf("storage: put %v -> %v\n", key, value)
s.data[key] = value s.data[key] = value
} }
@ -47,7 +43,7 @@ func (s *EphemeralStorage) Get(key string) string {
if len(key) == 0 { if len(key) == 0 {
return "" return ""
} }
fmt.Printf("storage: get %v\n", key) //fmt.Printf("storage: get %v\n", key)
if v, exist := s.data[key]; exist { if v, exist := s.data[key]; exist {
return v return v
} }
@ -60,3 +56,11 @@ func NewEphemeralStorage() Storage {
} }
return s return s
} }
// NoStorage is a dummy construct which doesn't remember anything you tell it
type NoStorage struct{}
func (s *NoStorage) Put(key, value string) {}
func (s *NoStorage) Get(key string) string {
return ""
}