cmd/statusd, geth: VerifyAccountPassword method exposed, closes #151
This commit is contained in:
parent
2f0c93fd3b
commit
68d4d20d66
|
@ -77,44 +77,25 @@ func RecoverAccount(password, mnemonic *C.char) *C.char {
|
||||||
return C.CString(string(outBytes))
|
return C.CString(string(outBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export VerifyAccountPassword
|
||||||
|
func VerifyAccountPassword(keyPath, address, password *C.char) *C.char {
|
||||||
|
_, err := geth.VerifyAccountPassword(C.GoString(keyPath), C.GoString(address), C.GoString(password))
|
||||||
|
return makeJSONErrorResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
//export Login
|
//export Login
|
||||||
func Login(address, password *C.char) *C.char {
|
func Login(address, password *C.char) *C.char {
|
||||||
// loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
// loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
||||||
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
||||||
err := geth.SelectAccount(C.GoString(address), C.GoString(password))
|
err := geth.SelectAccount(C.GoString(address), C.GoString(password))
|
||||||
|
return makeJSONErrorResponse(err)
|
||||||
errString := ""
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
errString = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := geth.JSONError{
|
|
||||||
Error: errString,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(&out)
|
|
||||||
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export Logout
|
//export Logout
|
||||||
func Logout() *C.char {
|
func Logout() *C.char {
|
||||||
|
|
||||||
// This is equivalent to clearing whisper identities
|
// This is equivalent to clearing whisper identities
|
||||||
err := geth.Logout()
|
err := geth.Logout()
|
||||||
|
return makeJSONErrorResponse(err)
|
||||||
errString := ""
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
errString = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := geth.JSONError{
|
|
||||||
Error: errString,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(&out)
|
|
||||||
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export CompleteTransaction
|
//export CompleteTransaction
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -58,6 +59,10 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
|
||||||
"create main and child accounts",
|
"create main and child accounts",
|
||||||
testCreateChildAccount,
|
testCreateChildAccount,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"verify account password",
|
||||||
|
testVerifyAccountPassword,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"recover account",
|
"recover account",
|
||||||
testRecoverAccount,
|
testRecoverAccount,
|
||||||
|
@ -105,6 +110,36 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testVerifyAccountPassword(t *testing.T) bool {
|
||||||
|
tmpDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir) // nolint: errcheck
|
||||||
|
|
||||||
|
if err = geth.ImportTestAccount(tmpDir, "test-account1.pk"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountFilePath := filepath.Join(tmpDir, "test-account1.pk")
|
||||||
|
response := geth.JSONError{}
|
||||||
|
rawResponse := VerifyAccountPassword(
|
||||||
|
C.CString(accountFilePath),
|
||||||
|
C.CString(testConfig.Account1.Address),
|
||||||
|
C.CString(testConfig.Account1.Password))
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
||||||
|
t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if response.Error != "" {
|
||||||
|
t.Errorf("unexpected error: %s", response.Error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func testGetDefaultConfig(t *testing.T) bool {
|
func testGetDefaultConfig(t *testing.T) bool {
|
||||||
// test Mainnet config
|
// test Mainnet config
|
||||||
nodeConfig := params.NodeConfig{}
|
nodeConfig := params.NodeConfig{}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package geth
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/status-im/status-go/extkeys"
|
"github.com/status-im/status-go/extkeys"
|
||||||
|
@ -125,6 +127,28 @@ func RecoverAccount(password, mnemonic string) (address, pubKey string, err erro
|
||||||
return address, pubKey, nil
|
return address, pubKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
|
||||||
|
// If no error is returned, then account is considered verified.
|
||||||
|
func VerifyAccountPassword(keyPath, address, password string) (*keystore.Key, error) {
|
||||||
|
keyJSON, err := ioutil.ReadFile(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid account key file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := keystore.DecryptKey(keyJSON, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid swap attack
|
||||||
|
addr := common.BytesToAddress(common.FromHex(address))
|
||||||
|
if key.Address != addr {
|
||||||
|
return nil, fmt.Errorf("account mismatch: have %x, want %x", key.Address, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SelectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
// SelectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
||||||
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
||||||
// all previous identities are removed).
|
// all previous identities are removed).
|
||||||
|
|
|
@ -2,12 +2,86 @@ package geth_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/status-im/status-go/geth"
|
"github.com/status-im/status-go/geth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestVerifyAccountPassword(t *testing.T) {
|
||||||
|
tmpDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir) // nolint: errcheck
|
||||||
|
|
||||||
|
if err = geth.ImportTestAccount(tmpDir, "test-account1.pk"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountFilePath := filepath.Join(tmpDir, "test-account1.pk")
|
||||||
|
account1Address := common.BytesToAddress(common.FromHex(testConfig.Account1.Address))
|
||||||
|
account2Address := common.BytesToAddress(common.FromHex(testConfig.Account2.Address))
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
keyPath string
|
||||||
|
address string
|
||||||
|
password string
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"correct address, correct password (decrypt should succeed)",
|
||||||
|
accountFilePath,
|
||||||
|
testConfig.Account1.Address,
|
||||||
|
testConfig.Account1.Password,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"correct address, correct password, invalid key file",
|
||||||
|
filepath.Join(tmpDir, "non-existent-file.pk"),
|
||||||
|
testConfig.Account1.Address,
|
||||||
|
testConfig.Account1.Password,
|
||||||
|
fmt.Errorf("invalid account key file: open %s/non-existent-file.pk: no such file or directory", tmpDir),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"wrong address, correct password",
|
||||||
|
accountFilePath,
|
||||||
|
testConfig.Account2.Address, // wrong address (swap attack)
|
||||||
|
testConfig.Account1.Password,
|
||||||
|
fmt.Errorf("account mismatch: have %x, want %x", account1Address, account2Address),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"correct address, wrong password",
|
||||||
|
accountFilePath,
|
||||||
|
testConfig.Account1.Address,
|
||||||
|
"wrong password", // wrong password
|
||||||
|
errors.New("could not decrypt key with given passphrase"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Log(testCase.name)
|
||||||
|
accountKey, err := geth.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password)
|
||||||
|
if !reflect.DeepEqual(err, testCase.expectedError) {
|
||||||
|
t.Errorf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if accountKey == nil {
|
||||||
|
t.Error("no error reported, but account key is missing")
|
||||||
|
}
|
||||||
|
accountAddress := common.BytesToAddress(common.FromHex(testCase.address))
|
||||||
|
if accountKey.Address != accountAddress {
|
||||||
|
t.Errorf("account mismatch: have %x, want %x", accountKey.Address, accountAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccountsList(t *testing.T) {
|
func TestAccountsList(t *testing.T) {
|
||||||
err := geth.PrepareTestNode()
|
err := geth.PrepareTestNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,3 +21,4 @@ comment on exported function PopulateStaticPeers should be of the form "Populate
|
||||||
comment on exported function AddPeer should be of the form "AddPeer ..." (golint)
|
comment on exported function AddPeer should be of the form "AddPeer ..." (golint)
|
||||||
comment on exported function NotifyNode should be of the form "NotifyNode ..." (golint)
|
comment on exported function NotifyNode should be of the form "NotifyNode ..." (golint)
|
||||||
comment on exported function TriggerTestSignal should be of the form "TriggerTestSignal ..." (golint)
|
comment on exported function TriggerTestSignal should be of the form "TriggerTestSignal ..." (golint)
|
||||||
|
comment on exported function VerifyAccountPassword should be of the form "VerifyAccountPassword ..." (golint)
|
||||||
|
|
Loading…
Reference in New Issue