Login with keycard (#1358)

* add InjectChatAccount to account manager

* add InjectChatAccount to Backend

* add comments to LoginWithKeycard

* add LoginWithKeycard test to lib

* fix export comment to avoid lint errors

* ensure wallet and chat keys are empty before injecting chat key

* rename InjectChatAccount to SetChatAccount

* add TestBackendInjectChatAccount

* stop node in TestBackendInjectChatAccount

* SetChatAccount doesn't return error

* add comment to InjectChatAccount

* fix account test
This commit is contained in:
Andrea Franz 2019-01-24 16:44:46 +01:00 committed by GitHub
parent c8a616688c
commit 929a5de757
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 216 additions and 0 deletions

View file

@ -1,6 +1,7 @@
package account
import (
"crypto/ecdsa"
"encoding/json"
"errors"
"fmt"
@ -14,6 +15,8 @@ import (
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/pborman/uuid"
"github.com/status-im/status-go/extkeys"
)
@ -245,6 +248,25 @@ func (m *Manager) SelectAccount(walletAddress, chatAddress, password string) err
return nil
}
// SetChatAccount initializes selectedChatAccount with privKey
func (m *Manager) SetChatAccount(privKey *ecdsa.PrivateKey) {
m.mu.Lock()
defer m.mu.Unlock()
address := crypto.PubkeyToAddress(privKey.PublicKey)
id := uuid.NewRandom()
key := &keystore.Key{
Id: id,
Address: address,
PrivateKey: privKey,
}
m.selectedChatAccount = &SelectedExtKey{
Address: address,
AccountKey: key,
}
}
// SelectedWalletAccount returns currently selected wallet account
func (m *Manager) SelectedWalletAccount() (*SelectedExtKey, error) {
m.mu.RLock()

View file

@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/golang/mock/gomock"
. "github.com/status-im/status-go/t/utils"
"github.com/stretchr/testify/assert"
@ -300,6 +301,26 @@ func (s *ManagerTestSuite) TestSelectAccount() {
}
}
func (s *ManagerTestSuite) TestSetChatAccount() {
s.accManager.Logout()
privKey, err := crypto.GenerateKey()
s.Require().NoError(err)
address := crypto.PubkeyToAddress(privKey.PublicKey)
s.accManager.SetChatAccount(privKey)
selectedChatAccount, err := s.accManager.SelectedChatAccount()
s.Require().NoError(err)
s.Require().NotNil(selectedChatAccount)
s.Equal(privKey, selectedChatAccount.AccountKey.PrivateKey)
s.Equal(address, selectedChatAccount.Address)
selectedWalletAccount, err := s.accManager.SelectedWalletAccount()
s.Error(err)
s.Nil(selectedWalletAccount)
}
func (s *ManagerTestSuite) TestCreateChildAccount() {
// First, test the negative case where an account is not selected
// and an address is not provided.

View file

@ -9,6 +9,7 @@ import (
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
gethnode "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/enode"
@ -485,6 +486,49 @@ func (b *StatusBackend) SendDataNotification(dataPayloadJSON string, tokens ...s
return err
}
// InjectChatAccount selects the current chat account using chatKeyHex and injects the key into whisper.
func (b *StatusBackend) InjectChatAccount(chatKeyHex, encryptionKeyHex string) error {
b.mu.Lock()
defer b.mu.Unlock()
b.accountManager.Logout()
chatKey, err := ethcrypto.HexToECDSA(chatKeyHex)
if err != nil {
return err
}
b.accountManager.SetChatAccount(chatKey)
chatAccount, err := b.accountManager.SelectedChatAccount()
if err != nil {
return err
}
whisperService, err := b.statusNode.WhisperService()
switch err {
case node.ErrServiceUnknown: // Whisper was never registered
case nil:
if err := whisperService.SelectKeyPair(chatAccount.AccountKey.PrivateKey); err != nil {
return ErrWhisperIdentityInjectionFailure
}
default:
return err
}
if whisperService != nil {
st, err := b.statusNode.ShhExtService()
if err != nil {
return err
}
if err := st.InitProtocol(chatAccount.Address.Hex(), encryptionKeyHex); err != nil {
return err
}
}
return nil
}
func appendIf(condition bool, services []gethnode.ServiceConstructor, service gethnode.ServiceConstructor) []gethnode.ServiceConstructor {
if !condition {
return services

View file

@ -1,11 +1,15 @@
package api
import (
"encoding/hex"
"fmt"
"math/rand"
"sync"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/node"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
@ -180,6 +184,48 @@ func TestBackendAccountsConcurrently(t *testing.T) {
wg.Wait()
}
func TestBackendInjectChatAccount(t *testing.T) {
backend := NewStatusBackend()
config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID)
require.NoError(t, err)
err = backend.StartNode(config)
require.NoError(t, err)
defer func() {
require.NoError(t, backend.StopNode())
}()
chatPrivKey, err := crypto.GenerateKey()
require.NoError(t, err)
encryptionPrivKey, err := crypto.GenerateKey()
require.NoError(t, err)
chatPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(chatPrivKey))
chatPubKeyHex := hexutil.Encode(crypto.FromECDSAPub(&chatPrivKey.PublicKey))
encryptionPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(encryptionPrivKey))
whisperService, err := backend.StatusNode().WhisperService()
require.NoError(t, err)
// public key should not be already in whisper
require.False(t, whisperService.HasKeyPair(chatPubKeyHex), "identity already present in whisper")
// call InjectChatAccount
require.NoError(t, backend.InjectChatAccount(chatPrivKeyHex, encryptionPrivKeyHex))
// public key should now be in whisper
require.True(t, whisperService.HasKeyPair(chatPubKeyHex), "identity not injected into whisper")
// wallet account should not be selected
walletAcc, err := backend.AccountManager().SelectedWalletAccount()
require.Nil(t, walletAcc)
require.Equal(t, account.ErrNoAccountSelected, err)
// selected chat account should have the key injected previously
chatAcc, err := backend.AccountManager().SelectedChatAccount()
require.Nil(t, err)
require.Equal(t, chatPrivKey, chatAcc.AccountKey.PrivateKey)
}
func TestBackendConnectionChangesConcurrently(t *testing.T) {
connections := [...]string{wifi, cellular, unknown}
backend := NewStatusBackend()

View file

@ -327,6 +327,14 @@ func Login(address, password *C.char) *C.char {
return makeJSONResponse(err)
}
// LoginWithKeycard initializes an account with a chat key and encryption key used for PFS.
// It purges all the previous identities from Whisper, and injects the key as shh identity.
//export LoginWithKeycard
func LoginWithKeycard(chatKeyData, encryptionKeyData *C.char) *C.char {
err := statusBackend.InjectChatAccount(C.GoString(chatKeyData), C.GoString(encryptionKeyData))
return makeJSONResponse(err)
}
//Logout is equivalent to clearing whisper identities
//export Logout
func Logout() *C.char {

View file

@ -11,6 +11,7 @@ package main
import "C"
import (
"encoding/hex"
"encoding/json"
"io/ioutil"
"math/big"
@ -26,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/signal"
. "github.com/status-im/status-go/t/utils" //nolint: golint
@ -124,6 +126,10 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
"account select/login",
testAccountSelect,
},
{
"login with keycard",
testLoginWithKeycard,
},
{
"account logout",
testAccountLogout,
@ -717,6 +723,53 @@ func testAccountSelect(t *testing.T) bool { //nolint: gocyclo
return true
}
func testLoginWithKeycard(t *testing.T) bool { //nolint: gocyclo
chatPrivKey, err := crypto.GenerateKey()
if err != nil {
t.Errorf("error generating chat key")
return false
}
chatPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(chatPrivKey))
encryptionPrivKey, err := crypto.GenerateKey()
if err != nil {
t.Errorf("error generating encryption key")
return false
}
encryptionPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(encryptionPrivKey))
whisperService, err := statusBackend.StatusNode().WhisperService()
if err != nil {
t.Errorf("whisper service not running: %v", err)
}
chatPubKeyHex := hexutil.Encode(crypto.FromECDSAPub(&chatPrivKey.PublicKey))
if whisperService.HasKeyPair(chatPubKeyHex) {
t.Error("identity already present in whisper")
return false
}
loginResponse := APIResponse{}
rawResponse := LoginWithKeycard(C.CString(chatPrivKeyHex), C.CString(encryptionPrivKeyHex))
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
t.Errorf("cannot decode LoginWithKeycard response (%s): %v", C.GoString(rawResponse), err)
return false
}
if loginResponse.Error != "" {
t.Errorf("Test failed: could not login with keycard: %v", err)
return false
}
if !whisperService.HasKeyPair(chatPubKeyHex) {
t.Error("identity not present in whisper after logging in with keycard")
return false
}
return true
}
func testAccountLogout(t *testing.T) bool {
whisperService, err := statusBackend.StatusNode().WhisperService()
if err != nil {

View file

@ -1,10 +1,12 @@
package accounts
import (
"encoding/hex"
"errors"
"fmt"
"testing"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/account"
e2e "github.com/status-im/status-go/t/e2e"
. "github.com/status-im/status-go/t/utils"
@ -200,6 +202,26 @@ func (s *AccountsTestSuite) TestSelectAccount() {
s.NoError(s.Backend.SelectAccount(accountInfo2.WalletAddress, accountInfo2.ChatAddress, TestConfig.Account1.Password))
}
func (s *AccountsTestSuite) TestSetChatAccount() {
s.StartTestBackend()
defer s.StopTestBackend()
chatPrivKey, err := crypto.GenerateKey()
s.Require().NoError(err)
chatPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(chatPrivKey))
encryptionPrivKey, err := crypto.GenerateKey()
s.Require().NoError(err)
encryptionPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(encryptionPrivKey))
err = s.Backend.InjectChatAccount(chatPrivKeyHex, encryptionPrivKeyHex)
s.Require().NoError(err)
selectedChatAccount, err := s.Backend.AccountManager().SelectedChatAccount()
s.Require().NoError(err)
s.Equal(chatPrivKey, selectedChatAccount.AccountKey.PrivateKey)
}
func (s *AccountsTestSuite) TestSelectedAccountOnRestart() {
s.StartTestBackend()