Make sure that only `personal_sign` and `personal_ecRecover` are exported.

This commit is contained in:
Igor Mandrigin 2018-04-18 13:04:02 +02:00 committed by Igor Mandrigin
parent 6598371dc0
commit f0f55d408f
5 changed files with 137 additions and 26 deletions

View File

@ -134,7 +134,7 @@ func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
b.transactor.SetNetworkID(config.NetworkID)
b.transactor.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
b.personalAPI.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
b.personalAPI.SetRPC(b.statusNode.RPCPrivateClient(), rpc.DefaultCallTimeout)
if err := b.registerHandlers(); err != nil {
b.log.Error("Handler registration failed", "err", err)
}
@ -281,6 +281,7 @@ func (b *StatusBackend) registerHandlers() error {
})
rpcClient.RegisterHandler(params.PersonalSignMethodName, b.PersonalAPI().Sign)
rpcClient.RegisterHandler(params.PersonalRecoverMethodName, b.PersonalAPI().Recover)
return nil
}

View File

@ -79,7 +79,7 @@ func MakeNode(config *params.NodeConfig) (*node.Node, error) {
return nil, fmt.Errorf("%v: %v", ErrLightEthRegistrationFailure, err)
}
} else {
// `personal_sign` and `personal_recover` methods are important to
// `personal_sign` and `personal_ecRecover` methods are important to
// keep DApps working.
// Usually, they are provided by an ETH or a LES service, but when using
// upstream, we don't start any of these, so we need to start our own

View File

@ -30,7 +30,8 @@ const (
ListenAddr = ":0"
// APIModules is a list of modules to expose via any type of RPC (HTTP, IPC, in-proc)
APIModules = "eth,net,web3,shh,shhext,personal"
// we also expose 2 limited personal APIs by overriding them in `api/backend.go`
APIModules = "eth,net,web3,shh,shhext"
// SendTransactionMethodName defines the name for a giving transaction.
SendTransactionMethodName = "eth_sendTransaction"
@ -41,6 +42,9 @@ const (
// PersonalSignMethodName defines the name for `personal.sign` API.
PersonalSignMethodName = "personal_sign"
// PersonalRecoverMethodName defines the name for `personal.recover` API.
PersonalRecoverMethodName = "personal_ecRecover"
// MaxPeers is the maximum number of global peers
MaxPeers = 25

View File

@ -62,6 +62,16 @@ func (api *PublicAPI) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
api.rpcTimeout = timeout
}
// Recover is an implementation of `personal_ecRecover` or `web3.personal.ecRecover` API
func (api *PublicAPI) Recover(context context.Context, rpcParams ...interface{}) (interface{}, error) {
var response interface{}
err := api.rpcClient.CallContextIgnoringLocalHandlers(
context, &response, params.PersonalRecoverMethodName, rpcParams...)
return response, err
}
// Sign is an implementation of `personal_sign` or `web3.personal.sign` API
func (api *PublicAPI) Sign(context context.Context, rpcParams ...interface{}) (interface{}, error) {
metadata, err := newMetadata(rpcParams)

View File

@ -3,6 +3,7 @@ package services
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/ethereum/go-ethereum/accounts/keystore"
@ -12,15 +13,23 @@ import (
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/sign"
e2e "github.com/status-im/status-go/t/e2e"
. "github.com/status-im/status-go/t/utils"
"github.com/stretchr/testify/suite"
. "github.com/status-im/status-go/t/utils"
)
const (
signDataString = "0xBAADBEEF"
accountNotExists = "0x00164ca341326a03b547c05B343b2E21eFAe2400"
// see vendor/github.com/ethereum/go-ethereum/rpc/errors.go:L27
methodNotFoundErrorCode = -32601
)
type rpcError struct {
Code int `json:"code"`
}
type testParams struct {
Title string
EnableUpstream bool
@ -48,6 +57,48 @@ type PersonalSignSuite struct {
upstream bool
}
func (s *PersonalSignSuite) TestRestrictedPersonalAPIs() {
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
s.T().Skip()
return
}
err := s.initTest(s.upstream)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
s.NoError(err)
}()
// These personal APIs should be available
s.testAPIExported("personal_sign", true)
s.testAPIExported("personal_ecRecover", true)
// These personal APIs shouldn't be exported
s.testAPIExported("personal_sendTransaction", false)
s.testAPIExported("personal_unlockAccount", false)
s.testAPIExported("personal_newAccount", false)
s.testAPIExported("personal_lockAccount", false)
s.testAPIExported("personal_listAccounts", false)
s.testAPIExported("personal_importRawKey", false)
}
func (s *PersonalSignSuite) testAPIExported(method string, expectExported bool) {
cmd := fmt.Sprintf(`{"jsonrpc":"2.0", "method": "%s", "params": []}`, method)
result := s.Backend.CallRPC(cmd)
var response struct {
Error *rpcError `json:"error"`
}
s.NoError(json.Unmarshal([]byte(result), &response))
hidden := (response.Error != nil && response.Error.Code == methodNotFoundErrorCode)
s.Equal(expectExported, !hidden,
"method %s should be %s, but it isn't",
method, map[bool]string{true: "exported", false: "hidden"}[expectExported])
}
func (s *PersonalSignSuite) TestPersonalSignSuccess() {
s.testPersonalSign(testParams{
Account: TestConfig.Account1.Address,
@ -128,24 +179,6 @@ func (s *PersonalSignSuite) notificationHandlerNoAccountSelected(account string,
}
}
func (s *PersonalSignSuite) initTest(upstreamEnabled bool) error {
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
s.NoError(err)
nodeConfig.IPCEnabled = false
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
if upstreamEnabled {
networkURL, err := GetRemoteURL()
s.NoError(err)
nodeConfig.UpstreamConfig.Enabled = true
nodeConfig.UpstreamConfig.URL = networkURL
}
return s.Backend.StartNode(nodeConfig)
}
func (s *PersonalSignSuite) notificationHandler(account string, pass string, expectedError error) func(string) {
return func(jsonEvent string) {
envelope := unmarshalEnvelope(jsonEvent)
@ -173,11 +206,11 @@ func (s *PersonalSignSuite) notificationHandler(account string, pass string, exp
}
}
func (s *PersonalSignSuite) testPersonalSign(testParams testParams) {
func (s *PersonalSignSuite) testPersonalSign(testParams testParams) string {
// Test upstream if that's not StatusChain
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
s.T().Skip()
return
return ""
}
if testParams.HandlerFactory == nil {
@ -207,9 +240,11 @@ func (s *PersonalSignSuite) testPersonalSign(testParams testParams) {
result := s.Backend.CallRPC(basicCall)
if testParams.ExpectedError == nil {
s.NotContains(result, "error")
} else {
s.Contains(result, testParams.ExpectedError.Error())
return s.extractResultFromRPCResponse(result)
}
s.Contains(result, testParams.ExpectedError.Error())
return ""
}
func unmarshalEnvelope(jsonEvent string) signal.Envelope {
@ -219,3 +254,64 @@ func unmarshalEnvelope(jsonEvent string) signal.Envelope {
}
return envelope
}
func (s *PersonalSignSuite) TestPersonalRecoverSuccess() {
// 1. Sign
signedData := s.testPersonalSign(testParams{
Account: TestConfig.Account1.Address,
Password: TestConfig.Account1.Password,
})
// Test upstream if that's not StatusChain
if s.upstream && GetNetworkID() == params.StatusChainNetworkID {
s.T().Skip()
return
}
err := s.initTest(s.upstream)
s.NoError(err)
defer func() {
err := s.Backend.StopNode()
s.NoError(err)
}()
// 2. Test recover
basicCall := fmt.Sprintf(
`{"jsonrpc":"2.0","method":"personal_ecRecover","params":["%s", "%s"],"id":67}`,
signDataString,
signedData)
response := s.Backend.CallRPC(basicCall)
result := s.extractResultFromRPCResponse(response)
s.True(strings.EqualFold(result, TestConfig.Account1.Address))
}
func (s *PersonalSignSuite) initTest(upstreamEnabled bool) error {
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
s.NoError(err)
nodeConfig.IPCEnabled = false
nodeConfig.HTTPHost = "" // to make sure that no HTTP interface is started
if upstreamEnabled {
networkURL, err := GetRemoteURL()
s.NoError(err)
nodeConfig.UpstreamConfig.Enabled = true
nodeConfig.UpstreamConfig.URL = networkURL
}
return s.Backend.StartNode(nodeConfig)
}
func (s *PersonalSignSuite) extractResultFromRPCResponse(response string) string {
var r struct {
Result string `json:"result"`
}
s.NoError(json.Unmarshal([]byte(response), &r))
return r.Result
}