signer: implement audit logging

This commit is contained in:
Martin Holst Swende 2018-02-02 10:12:28 +01:00
parent c9eb319137
commit 82dd1080b0
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
4 changed files with 126 additions and 50 deletions

View file

@ -53,6 +53,29 @@ type Metadata struct {
Scheme string `json:"scheme"` Scheme string `json:"scheme"`
} }
func MetadataFromContext(ctx context.Context) Metadata {
m := Metadata{"NA", "NA", "NA"}
if v := ctx.Value("remote"); v != nil {
m.Remote = v.(string)
}
if v := ctx.Value("scheme"); v != nil {
m.Scheme = v.(string)
}
if v := ctx.Value("local"); v != nil {
m.Local = v.(string)
}
return m
}
func (m Metadata) String() string {
s, err := json.Marshal(m)
if err == nil {
return string(s)
}
return err.Error()
}
// types for the requests/response types // types for the requests/response types
type ( type (
// SignTxRequest contains info about a Transaction to sign // SignTxRequest contains info about a Transaction to sign
@ -129,6 +152,16 @@ func (ew errorWrapper) String() string {
return fmt.Sprintf("%s\n%s", ew.msg, ew.err) return fmt.Sprintf("%s\n%s", ew.msg, ew.err)
} }
type ExternalAPI interface {
List(ctx context.Context) (Accounts, error)
New(ctx context.Context) (accounts.Account, error)
SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error)
Export(ctx context.Context, addr common.Address) (json.RawMessage, error)
Import(ctx context.Context, keyJSON json.RawMessage) (Account, error)
}
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI // SignerUI specifies what method a UI needs to implement to be able to be used as a UI
// for the signer // for the signer
type SignerUI interface { type SignerUI interface {
@ -188,21 +221,6 @@ func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abi
return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, *abidb} return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, *abidb}
} }
func metaData(ctx context.Context) Metadata {
m := Metadata{"NA", "NA", "NA"}
if v := ctx.Value("remote"); v != nil {
m.Remote = v.(string)
}
if v := ctx.Value("scheme"); v != nil {
m.Scheme = v.(string)
}
if v := ctx.Value("local"); v != nil {
m.Local = v.(string)
}
return m
}
// List returns the set of wallet this signer manages. Each wallet can contain // List returns the set of wallet this signer manages. Each wallet can contain
// multiple accounts. // multiple accounts.
func (api *SignerAPI) List(ctx context.Context) (Accounts, error) { func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
@ -215,7 +233,7 @@ func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
} }
} }
result, err := api.ui.ApproveListing(&ListRequest{Accounts: accs, Meta: metaData(ctx)}) result, err := api.ui.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -234,7 +252,7 @@ func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
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")
} }
resp, err := api.ui.ApproveNewAccount(&NewAccountRequest{metaData(ctx)}) resp, err := api.ui.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)})
if err != nil { if err != nil {
return accounts.Account{}, err return accounts.Account{}, err
@ -307,7 +325,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Mixedcase
result SignTxResponse result SignTxResponse
) )
req := SignTxRequest{Transaction: args, From: from, Meta: metaData(ctx)} req := SignTxRequest{Transaction: args, From: from, Meta: MetadataFromContext(ctx)}
data := args.Data data := args.Data
if len(data) > 3 { if len(data) > 3 {
@ -383,7 +401,7 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, da
// We make the request prior to looking up if we actually have the account, to prevent // We make the request prior to looking up if we actually have the account, to prevent
// account-enumeration via the API // account-enumeration via the API
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: metaData(ctx)} req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)}
res, err := api.ui.ApproveSignData(req) res, err := api.ui.ApproveSignData(req)
if err != nil { if err != nil {
@ -454,7 +472,7 @@ func signHash(data []byte) ([]byte, string) {
// Export returns encrypted private key associated with the given address in web3 keystore format. // Export returns encrypted private key associated with the given address in web3 keystore format.
func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
res, err := api.ui.ApproveExport(&ExportRequest{Address: addr, Meta: metaData(ctx)}) res, err := api.ui.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)})
if err != nil { if err != nil {
return nil, err return nil, err
@ -487,7 +505,7 @@ func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Acco
return Account{}, errors.New("password based accounts not supported") return Account{}, errors.New("password based accounts not supported")
} }
res, err := api.ui.ApproveImport(&ImportRequest{Meta: metaData(ctx)}) res, err := api.ui.ApproveImport(&ImportRequest{Meta: MetadataFromContext(ctx)})
if err != nil { if err != nil {
return Account{}, err return Account{}, err

View file

@ -1,27 +1,82 @@
package main package main
import ( import (
"bufio" "context"
"fmt"
"io"
"time"
"github.com/ethereum/go-ethereum/rpc" "encoding/json"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
) )
type AuditLogger struct { type AuditLogger struct {
writer *bufio.Writer log log.Logger
api ExternalAPI
} }
func (l AuditLogger) Store(record *rpc.RPCInvocationRecord) { func (l *AuditLogger) List(ctx context.Context) (Accounts, error) {
l.writer.WriteString(fmt.Sprintf("%v\n%v\n", time.Now().Format(time.RFC3339), record.Method))
for i, arg := range record.Args { l.log.Info("Called list", "interface", "http")
l.writer.WriteString(fmt.Sprintf("\t%d: %v\n", i, arg)) return l.api.List(ctx)
}
l.writer.WriteString(fmt.Sprintf("%v\n", record.Response))
l.writer.Flush()
} }
func NewAuditLogger(writer io.Writer) *AuditLogger { func (l *AuditLogger) New(ctx context.Context) (accounts.Account, error) {
return &AuditLogger{bufio.NewWriter(writer)} return l.api.New(ctx)
}
func (l *AuditLogger) SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) {
l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"from", from.String(), "tx", args.String(),
"methodSelector", methodSelector)
b, e := l.api.SignTransaction(ctx, from, args, methodSelector)
l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
}
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", common.Bytes2Hex(data))
b, e := l.api.Sign(ctx, addr, data)
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
}
func (l *AuditLogger) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"data", common.Bytes2Hex(data))
a, e := l.api.EcRecover(ctx, data, sig)
l.log.Info("EcRecover", "type", "response", "addr", a.String(), "error", e)
return a, e
}
func (l *AuditLogger) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
l.log.Info("Export", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.Hex())
j, e := l.api.Export(ctx, addr)
// In this case, we don't actually log the json-response, which may be extra sensitive
l.log.Info("Export", "type", "response", "json response size", len(j), "error", e)
return j, e
}
func (l *AuditLogger) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) {
// Don't actually log the json contents
l.log.Info("Import", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"keyJSON size", len(keyJSON))
a, e := l.api.Import(ctx, keyJSON)
l.log.Info("Import", "type", "response", "addr", a.String(), "error", e)
return a, e
}
func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) {
l := log.New("api", "signer")
handler, err := log.FileHandler(path, log.LogfmtFormat())
if err != nil {
return nil, err
}
l.SetHandler(handler)
l.Info("Configured", "audit log", path)
return &AuditLogger{l, api}, nil
} }

View file

@ -115,23 +115,26 @@ func main() {
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb")) log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
var ( var (
api ExternalAPI
listener net.Listener
server = rpc.NewServer() server = rpc.NewServer()
)
api = NewSignerAPI( api_impl := 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),
ui, db, ui, db,
c.Bool(utils.LightKDFFlag.Name)) c.Bool(utils.LightKDFFlag.Name))
listener net.Listener
) api = api_impl
// Audit logging // Audit logging
if logfile := c.String("auditlog"); logfile != "" { if logfile := c.String("auditlog"); logfile != "" {
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) api, err = NewAuditLogger(logfile, api_impl)
if err != nil { if err != nil {
utils.Fatalf("Could not open %v for audit logging", logfile) utils.Fatalf(err.Error())
} }
server.SetAuditLogger(NewAuditLogger(f))
log.Info("Audit logs configured", "file", logfile) log.Info("Audit logs configured", "file", logfile)
} }
// register signer API with server // register signer API with server
@ -156,7 +159,7 @@ func main() {
if c.Bool("stdio-ui-test") { if c.Bool("stdio-ui-test") {
log.Info("Performing UI test") log.Info("Performing UI test")
go testExternalUI(api) go testExternalUI(api_impl)
} }
rpc.NewHTTPServer(cors, server).Serve(listener) rpc.NewHTTPServer(cors, server).Serve(listener)