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:
parent
c8a616688c
commit
929a5de757
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in New Issue