cmd/signer: add json 4byte directory, remove passwords from api

This commit is contained in:
Martin Holst Swende 2017-11-29 22:32:29 +01:00
parent a601b38e40
commit f88559e09f
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
7 changed files with 109 additions and 39 deletions

1
cmd/signer/4byte.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -17,11 +17,13 @@
package main package main
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"strings"
"bytes"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"io/ioutil"
"strings"
) )
type decodedArgument struct { type decodedArgument struct {
@ -97,17 +99,40 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
} }
encoded, err = abispec.Pack(method.Name, gotypedArguments...) encoded, err = abispec.Pack(method.Name, gotypedArguments...)
if !bytes.Equal(encoded, calldata){ if !bytes.Equal(encoded, calldata) {
exp := common.Bytes2Hex(encoded) exp := common.Bytes2Hex(encoded)
was := common.Bytes2Hex(calldata) was := common.Bytes2Hex(calldata)
return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. %v \nWant %s\nHave %s", decoded,was, exp) return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. %v \nWant %s\nHave %s", decoded, was, exp)
} }
return &decoded, nil return &decoded, nil
} }
func lookupABI(id []byte) (string, error){ type abiDb struct {
if len(id) != 4{ db map[string]string
}
func NewAbiDBFromFile(path string) (*abiDb, error) {
raw, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
db := new(abiDb)
json.Unmarshal(raw, &db.db)
return db, nil
}
// LookupABI checks the given 4byte-sequence against the known ABI methods.
// OBS: This method does not validate the match, it's assumed the caller will do so
func (db *abiDb) LookupABI(id []byte) (string, error) {
if len(id) != 4 {
return "", fmt.Errorf("Expected 4-byte id, got %d", len(id)) return "", fmt.Errorf("Expected 4-byte id, got %d", len(id))
} }
return `[{"type":"function","name":"send","inputs":[{"name":"a","type":"uint256"}]}]`, nil sig := common.ToHex(id)
} if key, exists := db.db[sig]; exists {
return key, nil
}
return "", fmt.Errorf("Signature %v not found", sig)
}
func (db *abiDb) Size() int{
return len(db.db)
}

View file

@ -53,7 +53,6 @@ func TestCalldataDecoding(t *testing.T) {
// From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
// contains a bool with illegal values // contains a bool with illegal values
"a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
} { } {
_, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata)
if err == nil { if err == nil {

View file

@ -40,6 +40,8 @@ type SignerAPI struct {
chainID *big.Int chainID *big.Int
am *accounts.Manager am *accounts.Manager
ui SignerUI ui SignerUI
abidb abiDb
audit *auditlogger
} }
// Metadata about the request // Metadata about the request
@ -171,7 +173,7 @@ func (ui *HeadlessUI) ShowInfo(message string) {
// 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(chainID int64, ksLocation string, noUSB bool, ui SignerUI) *SignerAPI { func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abidb *abiDb, auditlog string) *SignerAPI {
var backends []accounts.Backend var backends []accounts.Backend
// support password based accounts // support password based accounts
@ -195,8 +197,11 @@ func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI) *Si
log.Debug("Trezor support enabled") log.Debug("Trezor support enabled")
} }
} }
var al *auditlogger
return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui} if auditlog != ""{
al = &auditlogger{auditlog}
}
return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, *abidb, al}
} }
func metaData(ctx context.Context) Metadata { func metaData(ctx context.Context) Metadata {
@ -220,15 +225,15 @@ func (api *SignerAPI) List(ctx context.Context) ([]Account, error) {
ch := make(chan ListResponse, 1) ch := make(chan ListResponse, 1)
var accounts []Account var accs []Account
for _, wallet := range api.am.Wallets() { for _, wallet := range api.am.Wallets() {
for _, acc := range wallet.Accounts() { for _, acc := range wallet.Accounts() {
acc := Account{Typ: "account", URL: wallet.URL(), Address: acc.Address} acc := Account{Typ: "account", URL: wallet.URL(), Address: acc.Address}
accounts = append(accounts, acc) accs = append(accs, acc)
} }
} }
api.ui.ApproveListing(&ListRequest{accounts: accounts}, metaData(ctx), ch) api.ui.ApproveListing(&ListRequest{accounts: accs}, metaData(ctx), ch)
if result := <-ch; result.accounts != nil { if result := <-ch; result.accounts != nil {
return result.accounts, nil return result.accounts, nil
} }
@ -238,7 +243,7 @@ func (api *SignerAPI) List(ctx context.Context) ([]Account, error) {
// New creates a new password protected account. The private key is protected with // New creates a new password protected account. The private key is protected with
// the given password. Users are responsible to backup the private key that is stored // the given password. Users are responsible to backup the private key that is stored
// in the keystore location thas was specified when this API was created. // in the keystore location thas was specified when this API was created.
func (api *SignerAPI) New(ctx context.Context, passphrase string) (accounts.Account, error) { func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
be := api.am.Backends(keystore.KeyStoreType) be := api.am.Backends(keystore.KeyStoreType)
if len(be) == 0 { if len(be) == 0 {
return accounts.Account{}, errors.New("password based accounts not supported") return accounts.Account{}, errors.New("password based accounts not supported")
@ -247,14 +252,14 @@ func (api *SignerAPI) New(ctx context.Context, passphrase string) (accounts.Acco
api.ui.ApproveNewAccount(&NewAccountRequest{}, metaData(ctx), ch) api.ui.ApproveNewAccount(&NewAccountRequest{}, metaData(ctx), ch)
if resp := <-ch; resp.approved { if resp := <-ch; resp.approved {
return be[0].(*keystore.KeyStore).NewAccount(passphrase) return be[0].(*keystore.KeyStore).NewAccount(resp.pw)
} }
return accounts.Account{}, fmt.Errorf("Request denied") return accounts.Account{}, fmt.Errorf("Request denied")
} }
// SignTransaction signs the given transaction and returns it in an RLP encoded form // SignTransaction signs the given transaction and returns it in an RLP encoded form
// that can be posted to `eth_sendRawTransaction`. // that can be posted to `eth_sendRawTransaction`.
func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address, passwd string, args TransactionArg) (hexutil.Bytes, error) { func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address, args TransactionArg) (hexutil.Bytes, error) {
acc := accounts.Account{Address: from} acc := accounts.Account{Address: from}
wallet, err := api.am.Find(acc) wallet, err := api.am.Find(acc)
@ -274,7 +279,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
var abidef string var abidef string
// Try to make sense of the data // Try to make sense of the data
abidef, err = lookupABI(tx.Data()[:4]) abidef, err = api.abidb.LookupABI(tx.Data()[:4])
if err != nil { if err != nil {
req.callinfo = errorWrapper{"Warning! Could not locate ABI", err} req.callinfo = errorWrapper{"Warning! Could not locate ABI", err}
} else { } else {
@ -293,7 +298,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
if result.hash != tx.Hash() { if result.hash != tx.Hash() {
return nil, fmt.Errorf("Transaction hash mismatch") return nil, fmt.Errorf("Transaction hash mismatch")
} }
signedTx, err := wallet.SignTxWithPassphrase(acc, passwd, tx, api.chainID) signedTx, err := wallet.SignTxWithPassphrase(acc, result.pw, tx, api.chainID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -313,7 +318,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
// The key used to calculate the signature is decrypted with the given password. // The key used to calculate the signature is decrypted with the given password.
// //
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, passwd string, data hexutil.Bytes) (hexutil.Bytes, error) { func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
// Look up the wallet containing the requested signer // Look up the wallet containing the requested signer
account := accounts.Account{Address: addr} account := accounts.Account{Address: addr}
@ -327,10 +332,10 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, passwd stri
api.ui.ApproveSignData(&SignDataRequest{account: account, rawdata: data, message: msg, hash: sighash}, metaData(ctx), ch) api.ui.ApproveSignData(&SignDataRequest{account: account, rawdata: data, message: msg, hash: sighash}, metaData(ctx), ch)
if (<-ch).approved { if res := <-ch; res.approved {
// Assemble sign the data with the wallet // Assemble sign the data with the wallet
signature, err := wallet.SignHashWithPassphrase(account, passwd, sighash) signature, err := wallet.SignHashWithPassphrase(account, res.pw, sighash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -409,7 +414,7 @@ func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.Raw
// Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be // Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be
// in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful // in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful
// decryption it will encrypt the key with the given newPassphrase and store it in the keystore. // decryption it will encrypt the key with the given newPassphrase and store it in the keystore.
func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage, passphrase, newPassphrase string) (Account, error) { func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) {
be := api.am.Backends(keystore.KeyStoreType) be := api.am.Backends(keystore.KeyStoreType)
if len(be) == 0 { if len(be) == 0 {
@ -421,7 +426,7 @@ func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage, passp
api.ui.ApproveImport(&ImportRequest{}, metaData(ctx), ch) api.ui.ApproveImport(&ImportRequest{}, metaData(ctx), ch)
if resp := <-ch; resp.approved { if resp := <-ch; resp.approved {
acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, passphrase, newPassphrase) acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, resp.oldPassword, resp.newPassword)
if err != nil { if err != nil {
return Account{}, err return Account{}, err
} }

23
cmd/signer/auditlog.go Normal file
View file

@ -0,0 +1,23 @@
package main
import (
"os"
"github.com/ethereum/go-ethereum/log"
)
type auditlogger struct{
filename string
}
func (al auditlogger) append(text string) error{
f, err := os.OpenFile(al.filename, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Crit("Failed to open audit log","err", err)
return err
}
defer f.Close()
if _, err = f.WriteString(text); err != nil {
log.Crit("Failed to write to audit log","err", err)
return err
}
}

View file

@ -86,7 +86,7 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch
fmt.Printf("to: %v\n", request.transaction.To().Hex()) fmt.Printf("to: %v\n", request.transaction.To().Hex())
fmt.Printf("from: %v\n", request.from.Address.Hex()) fmt.Printf("from: %v\n", request.from.Address.Hex())
fmt.Printf("value: %v wei\n", weival) fmt.Printf("value: %v wei\n", weival)
if len(request.transaction.Data()) > 0{ if len(request.transaction.Data()) > 0 {
fmt.Printf("data: %v\n", common.Bytes2Hex(request.transaction.Data())) fmt.Printf("data: %v\n", common.Bytes2Hex(request.transaction.Data()))
} }
if request.callinfo != nil { if request.callinfo != nil {

View file

@ -19,11 +19,11 @@
package main package main
import ( import (
"fmt"
"io" "io"
"net"
"os" "os"
"path/filepath" "path/filepath"
"fmt"
"net"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -61,7 +61,17 @@ func main() {
cli.IntFlag{ cli.IntFlag{
Name: "rpcport", Name: "rpcport",
Usage: "HTTP-RPC server listening port", Usage: "HTTP-RPC server listening port",
Value: node.DefaultHTTPPort+5, Value: node.DefaultHTTPPort + 5,
},
cli.StringFlag{
Name: "4bytedb",
Usage: "File containing 4byte-identifiers",
Value: "./4byte.json",
},
cli.StringFlag{
Name: "auditlog",
Usage: "File used to emit audit logs. Set to '' to disable",
Value: "audit.log",
}, },
} }
@ -69,15 +79,22 @@ func main() {
// Set up the logger to print everything and the random generator // Set up the logger to print everything and the random generator
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true)))) log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
db, err := NewAbiDBFromFile(c.String("4bytedb"))
if err != nil {
utils.Fatalf(err.Error())
}
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
var ( var (
server = rpc.NewServer() server = rpc.NewServer()
api = NewSignerAPI( api = NewSignerAPI(
c.Int64(utils.NetworkIdFlag.Name), c.Int64(utils.NetworkIdFlag.Name),
c.String("keystore"), c.String("keystore"),
c.Bool(utils.NoUSBFlag.Name), c.Bool(utils.NoUSBFlag.Name),
NewCommandlineUI()) NewCommandlineUI(), db,
c.String("auditlog"))
listener net.Listener listener net.Listener
err error //err error
) )
// register signer API with server // register signer API with server
@ -107,9 +124,9 @@ func main() {
// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/ // curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
// Make transaction // Make transaction
// send(0x12) // safeSend(0x12)
// a52c101e0000000000000000000000000000000000000000000000000000000000000012 // 4401a6e40000000000000000000000000000000000000000000000000000000000000012
// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813","pw",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0xa52c101e0000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/ // curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813","pw",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/
type rwc struct { type rwc struct {
io.Reader io.Reader