cmd/statusd, geth: VerifyAccountPassword method exposed, closes #151

This commit is contained in:
Victor Farazdagi 2017-05-07 00:53:18 +03:00
parent 2f0c93fd3b
commit 68d4d20d66
5 changed files with 142 additions and 27 deletions

View File

@ -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

View File

@ -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{}

View File

@ -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).

View File

@ -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 {

View File

@ -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)