mirror of
https://github.com/status-im/status-go.git
synced 2025-02-24 20:58:26 +00:00
feat: transferring keystore files for selected keypair via local network
There is a desktop app feature where we need to transfer keystore files for selected keypair/s only via local network using a QR code (of course, which are not migrated to a keycard, otherwise we wouldn't need to do that).
This commit is contained in:
parent
d1db60918d
commit
d05ce522f9
@ -1140,6 +1140,36 @@ func InputConnectionStringForBootstrappingAnotherDevice(cs, configJSON string) s
|
|||||||
return makeJSONResponse(err)
|
return makeJSONResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConnectionStringForExportingKeypairsKeystores starts a pairing.SenderServer
|
||||||
|
// then generates a pairing.ConnectionParams. Used when the device is Logged in and therefore has Account keys
|
||||||
|
// and the device might not have a camera, to transfer kestore files of provided key uids.
|
||||||
|
func GetConnectionStringForExportingKeypairsKeystores(configJSON string) string {
|
||||||
|
if configJSON == "" {
|
||||||
|
return makeJSONResponse(fmt.Errorf("no config given, SendingServerConfig is expected"))
|
||||||
|
}
|
||||||
|
|
||||||
|
cs, err := pairing.StartUpKeystoreFilesSenderServer(statusBackend, configJSON)
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputConnectionStringForImportingKeypairsKeystores starts a pairing.ReceiverClient
|
||||||
|
// The given server.ConnectionParams string will determine the server.Mode
|
||||||
|
// Used when the device is Logged in and has Account keys and has a camera to read a QR code
|
||||||
|
//
|
||||||
|
// Example: A mobile device (device with a camera) receiving account data from
|
||||||
|
// a device with a screen (mobile or desktop devices)
|
||||||
|
func InputConnectionStringForImportingKeypairsKeystores(cs, configJSON string) string {
|
||||||
|
if configJSON == "" {
|
||||||
|
return makeJSONResponse(fmt.Errorf("no config given, ReceiverClientConfig is expected"))
|
||||||
|
}
|
||||||
|
|
||||||
|
err := pairing.StartUpKeystoreFilesReceivingClient(statusBackend, cs, configJSON)
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateConnectionString(cs string) string {
|
func ValidateConnectionString(cs string) string {
|
||||||
err := pairing.ValidateConnectionString(cs)
|
err := pairing.ValidateConnectionString(cs)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -482,3 +482,112 @@ func StartUpReceivingClient(backend *api.GethStatusBackend, cs, configJSON strin
|
|||||||
}
|
}
|
||||||
return c.sendInstallationData()
|
return c.sendInstallationData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| ReceiverClient
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
type KeystoreFilesReceiverClient struct {
|
||||||
|
*BaseClient
|
||||||
|
|
||||||
|
keystoreFilesReceiver PayloadReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeystoreFilesReceiverClient(backend *api.GethStatusBackend, c *ConnectionParams, config *KeystoreFilesReceiverClientConfig) (*KeystoreFilesReceiverClient, error) {
|
||||||
|
bc, err := NewBaseClient(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := logutils.ZapLogger().Named("ReceiverClient")
|
||||||
|
pe := NewPayloadEncryptor(c.aesKey)
|
||||||
|
|
||||||
|
kfrc, err := NewKeystoreFilesPayloadReceiver(backend, pe, config.ReceiverConfig, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &KeystoreFilesReceiverClient{
|
||||||
|
BaseClient: bc,
|
||||||
|
keystoreFilesReceiver: kfrc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KeystoreFilesReceiverClient) receiveKeystoreFilesData() error {
|
||||||
|
c.baseAddress.Path = pairingSendAccount
|
||||||
|
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.challengeTaker.DoChallenge(req)
|
||||||
|
if err != nil {
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionKeystoreFilesTransfer})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionKeystoreFilesTransfer})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
err = fmt.Errorf("[client] status not ok when receiving account data, received '%s'", resp.Status)
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionKeystoreFilesTransfer})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionKeystoreFilesTransfer})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionKeystoreFilesTransfer})
|
||||||
|
|
||||||
|
err = c.keystoreFilesReceiver.Receive(payload)
|
||||||
|
if err != nil {
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionKeystoreFilesTransfer})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionKeystoreFilesTransfer})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupKeystoreFilesReceivingClient creates a new ReceiverClient after parsing string inputs
|
||||||
|
func setupKeystoreFilesReceivingClient(backend *api.GethStatusBackend, cs, configJSON string) (*KeystoreFilesReceiverClient, error) {
|
||||||
|
ccp := new(ConnectionParams)
|
||||||
|
err := ccp.FromString(cs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := NewKeystoreFilesReceiverClientConfig()
|
||||||
|
err = json.Unmarshal([]byte(configJSON), conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = validateKeystoreFilesConfig(backend, conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewKeystoreFilesReceiverClient(backend, ccp, conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartUpKeystoreFilesReceivingClient creates a KeystoreFilesReceiverClient and triggers all `receive` calls in sequence to the KeystoreFilesSenderServer
|
||||||
|
func StartUpKeystoreFilesReceivingClient(backend *api.GethStatusBackend, cs, configJSON string) error {
|
||||||
|
c, err := setupKeystoreFilesReceivingClient(backend, cs, configJSON)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.getChallenge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.receiveKeystoreFilesData()
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"gopkg.in/go-playground/validator.v9"
|
"gopkg.in/go-playground/validator.v9"
|
||||||
|
|
||||||
"github.com/status-im/status-go/account/generator"
|
"github.com/status-im/status-go/account/generator"
|
||||||
|
"github.com/status-im/status-go/api"
|
||||||
"github.com/status-im/status-go/eth-node/keystore"
|
"github.com/status-im/status-go/eth-node/keystore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -174,3 +175,82 @@ func emptyDir(dir string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateReceivedKeystoreFiles(expectedKeys []string, keys map[string][]byte, password string) error {
|
||||||
|
if len(expectedKeys) != len(keys) {
|
||||||
|
return fmt.Errorf("one or more keystore files were not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, searchKey := range expectedKeys {
|
||||||
|
found := false
|
||||||
|
for key := range keys {
|
||||||
|
if strings.Contains(key, strings.ToLower(searchKey)) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("one or more expected keystore files are not found among the sent files")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateKeys(keys, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateKeystoreFilesConfig(backend *api.GethStatusBackend, conf interface{}) error {
|
||||||
|
var (
|
||||||
|
loggedInKeyUID string
|
||||||
|
password string
|
||||||
|
numOfKeypairs int
|
||||||
|
keystorePath string
|
||||||
|
)
|
||||||
|
|
||||||
|
switch c := conf.(type) {
|
||||||
|
case *KeystoreFilesSenderServerConfig:
|
||||||
|
loggedInKeyUID = c.SenderConfig.LoggedInKeyUID
|
||||||
|
password = c.SenderConfig.Password
|
||||||
|
numOfKeypairs = len(c.SenderConfig.KeypairsToExport)
|
||||||
|
keystorePath = c.SenderConfig.KeystorePath
|
||||||
|
case *KeystoreFilesReceiverClientConfig:
|
||||||
|
loggedInKeyUID = c.ReceiverConfig.LoggedInKeyUID
|
||||||
|
password = c.ReceiverConfig.Password
|
||||||
|
numOfKeypairs = len(c.ReceiverConfig.KeypairsToImport)
|
||||||
|
keystorePath = c.ReceiverConfig.KeystorePath
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown config type: %v", reflect.TypeOf(conf))
|
||||||
|
}
|
||||||
|
|
||||||
|
accountService := backend.StatusNode().AccountService()
|
||||||
|
if accountService == nil {
|
||||||
|
return fmt.Errorf("cannot resolve accounts service instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !accountService.GetMessenger().HasPairedDevices() {
|
||||||
|
return fmt.Errorf("there are no known paired devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedAccount, err := backend.GetActiveAccount()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedAccount.KeyUID != loggedInKeyUID {
|
||||||
|
return fmt.Errorf("configuration is not meant for the logged in account")
|
||||||
|
}
|
||||||
|
|
||||||
|
if selectedAccount.KeycardPairing == "" {
|
||||||
|
if !accountService.VerifyPassword(password) {
|
||||||
|
return fmt.Errorf("provided password is not correct")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if numOfKeypairs == 0 {
|
||||||
|
return fmt.Errorf("it should be at least a single keypair set a keystore files are transferred for")
|
||||||
|
}
|
||||||
|
|
||||||
|
if keystorePath == "" {
|
||||||
|
return fmt.Errorf("keyStorePath can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -41,6 +41,22 @@ type ReceiverConfig struct {
|
|||||||
LoggedInKeyUID string `json:"-"`
|
LoggedInKeyUID string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesConfig struct {
|
||||||
|
KeystorePath string `json:"keystorePath" validate:"required,keystorepath"`
|
||||||
|
LoggedInKeyUID string `json:"loggedInKeyUid" validate:"required,keyuid"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesSenderConfig struct {
|
||||||
|
KeystoreFilesConfig
|
||||||
|
KeypairsToExport []string `json:"keypairsToExport" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesReceiverConfig struct {
|
||||||
|
KeystoreFilesConfig
|
||||||
|
KeypairsToImport []string `json:"keypairsToImport" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
// Timeout the number of milliseconds after which the pairing server will automatically terminate
|
// Timeout the number of milliseconds after which the pairing server will automatically terminate
|
||||||
Timeout uint `json:"timeout" validate:"omitempty,gte=0"`
|
Timeout uint `json:"timeout" validate:"omitempty,gte=0"`
|
||||||
@ -61,6 +77,11 @@ type SenderServerConfig struct {
|
|||||||
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesSenderServerConfig struct {
|
||||||
|
SenderConfig *KeystoreFilesSenderConfig `json:"senderConfig" validate:"required"`
|
||||||
|
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
type SenderClientConfig struct {
|
type SenderClientConfig struct {
|
||||||
SenderConfig *SenderConfig `json:"senderConfig" validate:"required"`
|
SenderConfig *SenderConfig `json:"senderConfig" validate:"required"`
|
||||||
ClientConfig *ClientConfig `json:"clientConfig"`
|
ClientConfig *ClientConfig `json:"clientConfig"`
|
||||||
@ -71,6 +92,11 @@ type ReceiverClientConfig struct {
|
|||||||
ClientConfig *ClientConfig `json:"clientConfig"`
|
ClientConfig *ClientConfig `json:"clientConfig"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesReceiverClientConfig struct {
|
||||||
|
ReceiverConfig *KeystoreFilesReceiverConfig `json:"receiverConfig" validate:"required"`
|
||||||
|
ClientConfig *ClientConfig `json:"clientConfig"`
|
||||||
|
}
|
||||||
|
|
||||||
type ReceiverServerConfig struct {
|
type ReceiverServerConfig struct {
|
||||||
ReceiverConfig *ReceiverConfig `json:"receiverConfig" validate:"required"`
|
ReceiverConfig *ReceiverConfig `json:"receiverConfig" validate:"required"`
|
||||||
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
||||||
@ -83,6 +109,13 @@ func NewSenderServerConfig() *SenderServerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewKeystoreFilesSenderServerConfig() *KeystoreFilesSenderServerConfig {
|
||||||
|
return &KeystoreFilesSenderServerConfig{
|
||||||
|
SenderConfig: new(KeystoreFilesSenderConfig),
|
||||||
|
ServerConfig: new(ServerConfig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewSenderClientConfig() *SenderClientConfig {
|
func NewSenderClientConfig() *SenderClientConfig {
|
||||||
return &SenderClientConfig{
|
return &SenderClientConfig{
|
||||||
SenderConfig: new(SenderConfig),
|
SenderConfig: new(SenderConfig),
|
||||||
@ -97,6 +130,13 @@ func NewReceiverClientConfig() *ReceiverClientConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewKeystoreFilesReceiverClientConfig() *KeystoreFilesReceiverClientConfig {
|
||||||
|
return &KeystoreFilesReceiverClientConfig{
|
||||||
|
ReceiverConfig: new(KeystoreFilesReceiverConfig),
|
||||||
|
ClientConfig: new(ClientConfig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewReceiverServerConfig() *ReceiverServerConfig {
|
func NewReceiverServerConfig() *ReceiverServerConfig {
|
||||||
return &ReceiverServerConfig{
|
return &ReceiverServerConfig{
|
||||||
ReceiverConfig: new(ReceiverConfig),
|
ReceiverConfig: new(ReceiverConfig),
|
||||||
|
@ -20,6 +20,7 @@ const (
|
|||||||
EventReceivedAccount EventType = "received-account"
|
EventReceivedAccount EventType = "received-account"
|
||||||
EventProcessSuccess EventType = "process-success"
|
EventProcessSuccess EventType = "process-success"
|
||||||
EventProcessError EventType = "process-error"
|
EventProcessError EventType = "process-error"
|
||||||
|
EventReceivedKeystoreFiles EventType = "received-keystore-files"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event is a type for transfer events.
|
// Event is a type for transfer events.
|
||||||
@ -38,6 +39,7 @@ const (
|
|||||||
ActionSyncDevice
|
ActionSyncDevice
|
||||||
ActionPairingInstallation
|
ActionPairingInstallation
|
||||||
ActionPeerDiscovery
|
ActionPeerDiscovery
|
||||||
|
ActionKeystoreFilesTransfer
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountData struct {
|
type AccountData struct {
|
||||||
|
@ -45,13 +45,16 @@ func NewPairingPayloadMarshaller(ap *AccountPayload, logger *zap.Logger) *Accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ppm *AccountPayloadMarshaller) MarshalProtobuf() ([]byte, error) {
|
func (ppm *AccountPayloadMarshaller) MarshalProtobuf() ([]byte, error) {
|
||||||
return proto.Marshal(&protobuf.LocalPairingPayload{
|
lpp := &protobuf.LocalPairingPayload{
|
||||||
Keys: ppm.accountKeysToProtobuf(),
|
Keys: ppm.accountKeysToProtobuf(),
|
||||||
Multiaccount: ppm.multiaccount.ToProtobuf(),
|
|
||||||
Password: ppm.password,
|
Password: ppm.password,
|
||||||
ChatKey: ppm.chatKey,
|
ChatKey: ppm.chatKey,
|
||||||
KeycardPairings: ppm.keycardPairings,
|
KeycardPairings: ppm.keycardPairings,
|
||||||
})
|
}
|
||||||
|
if ppm.multiaccount != nil {
|
||||||
|
lpp.Multiaccount = ppm.multiaccount.ToProtobuf()
|
||||||
|
}
|
||||||
|
return proto.Marshal(lpp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ppm *AccountPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPairingPayload_Key {
|
func (ppm *AccountPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPairingPayload_Key {
|
||||||
@ -79,7 +82,9 @@ func (ppm *AccountPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ppm.accountKeysFromProtobuf(pb.Keys)
|
ppm.accountKeysFromProtobuf(pb.Keys)
|
||||||
|
if pb.Multiaccount != nil {
|
||||||
ppm.multiaccountFromProtobuf(pb.Multiaccount)
|
ppm.multiaccountFromProtobuf(pb.Multiaccount)
|
||||||
|
}
|
||||||
ppm.password = pb.Password
|
ppm.password = pb.Password
|
||||||
ppm.chatKey = pb.ChatKey
|
ppm.chatKey = pb.ChatKey
|
||||||
ppm.keycardPairings = pb.KeycardPairings
|
ppm.keycardPairings = pb.KeycardPairings
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package pairing
|
package pairing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/status-im/status-go/api"
|
"github.com/status-im/status-go/api"
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
"github.com/status-im/status-go/multiaccounts"
|
||||||
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PayloadMounter interface {
|
type PayloadMounter interface {
|
||||||
@ -243,3 +247,93 @@ func NewPayloadMounters(logger *zap.Logger, pe *PayloadEncryptor, backend *api.G
|
|||||||
imr := NewInstallationPayloadMounterReceiver(pe, backend, config.DeviceType)
|
imr := NewInstallationPayloadMounterReceiver(pe, backend, config.DeviceType)
|
||||||
return am, rmm, imr, nil
|
return am, rmm, imr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| KeystoreFilesPayload
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
func NewKeystoreFilesPayloadMounter(backend *api.GethStatusBackend, pe *PayloadEncryptor, config *KeystoreFilesSenderConfig, logger *zap.Logger) (*BasePayloadMounter, error) {
|
||||||
|
l := logger.Named("KeystoreFilesPayloadLoader")
|
||||||
|
l.Debug("fired", zap.Any("config", config))
|
||||||
|
|
||||||
|
pe = pe.Renew()
|
||||||
|
|
||||||
|
// A new SHARED AccountPayload
|
||||||
|
p := new(AccountPayload)
|
||||||
|
kfpl, err := NewKeystoreFilesPayloadLoader(backend, p, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewBasePayloadMounter(
|
||||||
|
kfpl,
|
||||||
|
NewPairingPayloadMarshaller(p, l),
|
||||||
|
pe,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesPayloadLoader struct {
|
||||||
|
*AccountPayload
|
||||||
|
|
||||||
|
keystorePath string
|
||||||
|
loggedInKeyUID string
|
||||||
|
keystoreFilesToTransfer []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeystoreFilesPayloadLoader(backend *api.GethStatusBackend, p *AccountPayload, config *KeystoreFilesSenderConfig) (*KeystoreFilesPayloadLoader, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("empty keystore files sender config")
|
||||||
|
}
|
||||||
|
|
||||||
|
kfpl := &KeystoreFilesPayloadLoader{
|
||||||
|
AccountPayload: p,
|
||||||
|
keystorePath: config.KeystorePath,
|
||||||
|
loggedInKeyUID: config.LoggedInKeyUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
kfpl.password = config.Password
|
||||||
|
|
||||||
|
accountService := backend.StatusNode().AccountService()
|
||||||
|
|
||||||
|
for _, keyUID := range config.KeypairsToExport {
|
||||||
|
kp, err := accountService.GetKeypairByKeyUID(keyUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kp.Type == accounts.KeypairTypeSeed {
|
||||||
|
kfpl.keystoreFilesToTransfer = append(kfpl.keystoreFilesToTransfer, kp.DerivedFrom[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, acc := range kp.Accounts {
|
||||||
|
kfpl.keystoreFilesToTransfer = append(kfpl.keystoreFilesToTransfer, acc.Address.Hex()[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kfpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kfpl *KeystoreFilesPayloadLoader) Load() error {
|
||||||
|
kfpl.keys = make(map[string][]byte)
|
||||||
|
err := loadKeys(kfpl.keys, kfpl.keystorePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new map to filter keys
|
||||||
|
filteredMap := make(map[string][]byte)
|
||||||
|
for _, searchKey := range kfpl.keystoreFilesToTransfer {
|
||||||
|
for key, value := range kfpl.keys {
|
||||||
|
if strings.Contains(key, strings.ToLower(searchKey)) {
|
||||||
|
filteredMap[key] = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kfpl.keys = filteredMap
|
||||||
|
|
||||||
|
return validateKeys(kfpl.keys, kfpl.password)
|
||||||
|
}
|
||||||
|
@ -5,12 +5,14 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/multierr"
|
"go.uber.org/multierr"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/status-im/status-go/api"
|
"github.com/status-im/status-go/api"
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
"github.com/status-im/status-go/multiaccounts"
|
||||||
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
)
|
)
|
||||||
@ -346,3 +348,128 @@ func NewPayloadReceivers(logger *zap.Logger, pe *PayloadEncryptor, backend *api.
|
|||||||
imr := NewInstallationPayloadMounterReceiver(pe, backend, config.DeviceType)
|
imr := NewInstallationPayloadMounterReceiver(pe, backend, config.DeviceType)
|
||||||
return ar, rmr, imr, nil
|
return ar, rmr, imr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| KeystoreFilesPayload
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
func NewKeystoreFilesPayloadReceiver(backend *api.GethStatusBackend, e *PayloadEncryptor, config *KeystoreFilesReceiverConfig, logger *zap.Logger) (*BasePayloadReceiver, error) {
|
||||||
|
l := logger.Named("KeystoreFilesPayloadManager")
|
||||||
|
l.Debug("fired", zap.Any("config", config))
|
||||||
|
|
||||||
|
e = e.Renew()
|
||||||
|
|
||||||
|
// A new SHARED AccountPayload
|
||||||
|
p := new(AccountPayload)
|
||||||
|
|
||||||
|
kfps, err := NewKeystoreFilesPayloadStorer(backend, p, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewBasePayloadReceiver(e, NewPairingPayloadMarshaller(p, l), kfps,
|
||||||
|
func() {
|
||||||
|
data := len(p.keys)
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventReceivedKeystoreFiles, Action: ActionKeystoreFilesTransfer, Data: data})
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeystoreFilesPayloadStorer struct {
|
||||||
|
*AccountPayload
|
||||||
|
|
||||||
|
keystorePath string
|
||||||
|
loggedInKeyUID string
|
||||||
|
expectedKeystoreFilesToReceive []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeystoreFilesPayloadStorer(backend *api.GethStatusBackend, p *AccountPayload, config *KeystoreFilesReceiverConfig) (*KeystoreFilesPayloadStorer, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("empty keystore files receiver config")
|
||||||
|
}
|
||||||
|
|
||||||
|
kfps := &KeystoreFilesPayloadStorer{
|
||||||
|
AccountPayload: p,
|
||||||
|
keystorePath: config.KeystorePath,
|
||||||
|
loggedInKeyUID: config.LoggedInKeyUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
accountService := backend.StatusNode().AccountService()
|
||||||
|
|
||||||
|
for _, keyUID := range config.KeypairsToImport {
|
||||||
|
kp, err := accountService.GetKeypairByKeyUID(keyUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kp.Type == accounts.KeypairTypeSeed {
|
||||||
|
kfps.expectedKeystoreFilesToReceive = append(kfps.expectedKeystoreFilesToReceive, kp.DerivedFrom[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, acc := range kp.Accounts {
|
||||||
|
kfps.expectedKeystoreFilesToReceive = append(kfps.expectedKeystoreFilesToReceive, acc.Address.Hex()[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kfps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kfps *KeystoreFilesPayloadStorer) Store() error {
|
||||||
|
err := validateReceivedKeystoreFiles(kfps.expectedKeystoreFilesToReceive, kfps.keys, kfps.password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return kfps.storeKeys(kfps.keystorePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kfps *KeystoreFilesPayloadStorer) storeKeys(keyStorePath string) error {
|
||||||
|
if keyStorePath == "" {
|
||||||
|
return fmt.Errorf("keyStorePath can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, lastDir := filepath.Split(keyStorePath)
|
||||||
|
|
||||||
|
// If lastDir == keystoreDir we presume we need to create the rest of the keystore path
|
||||||
|
// else we presume the provided keystore is valid
|
||||||
|
if lastDir == keystoreDir {
|
||||||
|
keyStorePath = filepath.Join(keyStorePath, kfps.loggedInKeyUID)
|
||||||
|
_, err := os.Stat(keyStorePath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err := os.MkdirAll(keyStorePath, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, data := range kfps.keys {
|
||||||
|
found := false
|
||||||
|
for _, key := range kfps.expectedKeystoreFilesToReceive {
|
||||||
|
if strings.Contains(name, strings.ToLower(key)) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(filepath.Join(keyStorePath, name), data, 0600)
|
||||||
|
if err != nil {
|
||||||
|
writeErr := fmt.Errorf("failed to write key to path '%s' : %w", filepath.Join(keyStorePath, name), err)
|
||||||
|
// If we get an error on any of the key files attempt to revert
|
||||||
|
err := emptyDir(keyStorePath)
|
||||||
|
if err != nil {
|
||||||
|
// If we get an error when trying to empty the dir combine the write error and empty error
|
||||||
|
emptyDirErr := fmt.Errorf("failed to revert and cleanup storeKeys : %w", err)
|
||||||
|
return multierr.Combine(writeErr, emptyDirErr)
|
||||||
|
}
|
||||||
|
return writeErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -296,3 +296,84 @@ func StartUpReceiverServer(backend *api.GethStatusBackend, configJSON string) (s
|
|||||||
|
|
||||||
return cp.ToString(), nil
|
return cp.ToString(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| type KeystoreFilesSenderServer struct {
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
type KeystoreFilesSenderServer struct {
|
||||||
|
*BaseServer
|
||||||
|
keystoreFilesMounter PayloadMounter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeystoreFilesSenderServer(backend *api.GethStatusBackend, config *KeystoreFilesSenderServerConfig) (*KeystoreFilesSenderServer, error) {
|
||||||
|
logger := logutils.ZapLogger().Named("SenderServer")
|
||||||
|
e := NewPayloadEncryptor(config.ServerConfig.EK)
|
||||||
|
|
||||||
|
bs, err := NewBaseServer(logger, e, config.ServerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kfm, err := NewKeystoreFilesPayloadMounter(backend, e, config.SenderConfig, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &KeystoreFilesSenderServer{
|
||||||
|
BaseServer: bs,
|
||||||
|
keystoreFilesMounter: kfm,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KeystoreFilesSenderServer) startSendingData() error {
|
||||||
|
s.SetHandlers(server.HandlerPatternMap{
|
||||||
|
pairingChallenge: handlePairingChallenge(s.challengeGiver),
|
||||||
|
pairingSendAccount: middlewareChallenge(s.challengeGiver, handleSendAccount(s.GetLogger(), s.keystoreFilesMounter)),
|
||||||
|
})
|
||||||
|
return s.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeFullSenderServer generates a fully configured and randomly seeded KeystoreFilesSenderServer
|
||||||
|
func MakeKeystoreFilesSenderServer(backend *api.GethStatusBackend, config *KeystoreFilesSenderServerConfig) (*KeystoreFilesSenderServer, error) {
|
||||||
|
err := MakeServerConfig(config.ServerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewKeystoreFilesSenderServer(backend, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartUpKeystoreFilesSenderServer generates a KeystoreFilesSenderServer, starts the sending server
|
||||||
|
// and returns the ConnectionParams string to allow a ReceiverClient to make a successful connection.
|
||||||
|
func StartUpKeystoreFilesSenderServer(backend *api.GethStatusBackend, configJSON string) (string, error) {
|
||||||
|
conf := NewKeystoreFilesSenderServerConfig()
|
||||||
|
err := json.Unmarshal([]byte(configJSON), conf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validateKeystoreFilesConfig(backend, conf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := MakeKeystoreFilesSenderServer(backend, conf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ps.startSendingData()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cp, err := ps.MakeConnectionParams()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cp.ToString(), nil
|
||||||
|
}
|
||||||
|
@ -3,7 +3,10 @@ package pairing
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -29,6 +32,7 @@ import (
|
|||||||
"github.com/status-im/status-go/protocol/identity/alias"
|
"github.com/status-im/status-go/protocol/identity/alias"
|
||||||
"github.com/status-im/status-go/protocol/protobuf"
|
"github.com/status-im/status-go/protocol/protobuf"
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"github.com/status-im/status-go/protocol/requests"
|
||||||
|
accservice "github.com/status-im/status-go/services/accounts"
|
||||||
"github.com/status-im/status-go/services/browsers"
|
"github.com/status-im/status-go/services/browsers"
|
||||||
"github.com/status-im/status-go/sqlite"
|
"github.com/status-im/status-go/sqlite"
|
||||||
)
|
)
|
||||||
@ -43,6 +47,10 @@ const (
|
|||||||
ensUsername = "bob.stateofus.eth"
|
ensUsername = "bob.stateofus.eth"
|
||||||
ensChainID = 1
|
ensChainID = 1
|
||||||
publicChatID = "localpairtest"
|
publicChatID = "localpairtest"
|
||||||
|
profileMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon"
|
||||||
|
seedPhraseMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||||
|
path0 = "m/44'/60'/0'/0/0"
|
||||||
|
path1 = "m/44'/60'/0'/0/1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
|
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
|
||||||
@ -68,13 +76,25 @@ func (s *SyncDeviceSuite) SetupTest() {
|
|||||||
s.pairThreeDevicesTmpdir = s.T().TempDir()
|
s.pairThreeDevicesTmpdir = s.T().TempDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SyncDeviceSuite) prepareBackendWithAccount(tmpdir string) *api.GethStatusBackend {
|
func (s *SyncDeviceSuite) prepareBackendWithAccount(mnemonic, tmpdir string) *api.GethStatusBackend {
|
||||||
backend := s.prepareBackendWithoutAccount(tmpdir)
|
backend := s.prepareBackendWithoutAccount(tmpdir)
|
||||||
accountManager := backend.AccountManager()
|
accountManager := backend.AccountManager()
|
||||||
generator := accountManager.AccountsGenerator()
|
accGenerator := accountManager.AccountsGenerator()
|
||||||
generatedAccountInfos, err := generator.GenerateAndDeriveAddresses(12, 1, "", paths)
|
|
||||||
|
var (
|
||||||
|
generatedAccountInfo generator.GeneratedAndDerivedAccountInfo
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if len(mnemonic) > 0 {
|
||||||
|
generatedAccountInfo.GeneratedAccountInfo, err = accGenerator.ImportMnemonic(mnemonic, "")
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
generatedAccountInfo := generatedAccountInfos[0]
|
generatedAccountInfo.Derived, err = accGenerator.DeriveAddresses(generatedAccountInfo.ID, paths)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
} else {
|
||||||
|
generatedAccountInfos, err := accGenerator.GenerateAndDeriveAddresses(12, 1, "", paths)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
generatedAccountInfo = generatedAccountInfos[0]
|
||||||
|
}
|
||||||
account := multiaccounts.Account{
|
account := multiaccounts.Account{
|
||||||
KeyUID: generatedAccountInfo.KeyUID,
|
KeyUID: generatedAccountInfo.KeyUID,
|
||||||
KDFIterations: sqlite.ReducedKDFIterationsNumber,
|
KDFIterations: sqlite.ReducedKDFIterationsNumber,
|
||||||
@ -84,7 +104,7 @@ func (s *SyncDeviceSuite) prepareBackendWithAccount(tmpdir string) *api.GethStat
|
|||||||
err = backend.OpenAccounts()
|
err = backend.OpenAccounts()
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
derivedAddresses := generatedAccountInfo.Derived
|
derivedAddresses := generatedAccountInfo.Derived
|
||||||
_, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, s.password, paths)
|
_, err = accGenerator.StoreDerivedAccounts(generatedAccountInfo.ID, s.password, paths)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
settings, err := defaultSettings(generatedAccountInfo.GeneratedAccountInfo, derivedAddresses, nil)
|
settings, err := defaultSettings(generatedAccountInfo.GeneratedAccountInfo, derivedAddresses, nil)
|
||||||
@ -256,7 +276,7 @@ func (s *SyncDeviceSuite) checkMutualContact(backend *api.GethStatusBackend, con
|
|||||||
|
|
||||||
func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
|
func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
|
||||||
clientTmpDir := filepath.Join(s.clientAsSenderTmpdir, "client")
|
clientTmpDir := filepath.Join(s.clientAsSenderTmpdir, "client")
|
||||||
clientBackend := s.prepareBackendWithAccount(clientTmpDir)
|
clientBackend := s.prepareBackendWithAccount("", clientTmpDir)
|
||||||
serverTmpDir := filepath.Join(s.clientAsSenderTmpdir, "server")
|
serverTmpDir := filepath.Join(s.clientAsSenderTmpdir, "server")
|
||||||
serverBackend := s.prepareBackendWithoutAccount(serverTmpDir)
|
serverBackend := s.prepareBackendWithoutAccount(serverTmpDir)
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -380,7 +400,7 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
|
|||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
serverTmpDir := filepath.Join(s.clientAsReceiverTmpdir, "server")
|
serverTmpDir := filepath.Join(s.clientAsReceiverTmpdir, "server")
|
||||||
serverBackend := s.prepareBackendWithAccount(serverTmpDir)
|
serverBackend := s.prepareBackendWithAccount("", serverTmpDir)
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(s.T(), clientBackend.Logout())
|
require.NoError(s.T(), clientBackend.Logout())
|
||||||
require.NoError(s.T(), serverBackend.Logout())
|
require.NoError(s.T(), serverBackend.Logout())
|
||||||
@ -512,13 +532,13 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
|
|||||||
|
|
||||||
func (s *SyncDeviceSuite) TestPairingThreeDevices() {
|
func (s *SyncDeviceSuite) TestPairingThreeDevices() {
|
||||||
bobTmpDir := filepath.Join(s.pairThreeDevicesTmpdir, "bob")
|
bobTmpDir := filepath.Join(s.pairThreeDevicesTmpdir, "bob")
|
||||||
bobBackend := s.prepareBackendWithAccount(bobTmpDir)
|
bobBackend := s.prepareBackendWithAccount("", bobTmpDir)
|
||||||
bobMessenger := bobBackend.Messenger()
|
bobMessenger := bobBackend.Messenger()
|
||||||
_, err := bobMessenger.Start()
|
_, err := bobMessenger.Start()
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
alice1TmpDir := filepath.Join(s.pairThreeDevicesTmpdir, "alice1")
|
alice1TmpDir := filepath.Join(s.pairThreeDevicesTmpdir, "alice1")
|
||||||
alice1Backend := s.prepareBackendWithAccount(alice1TmpDir)
|
alice1Backend := s.prepareBackendWithAccount("", alice1TmpDir)
|
||||||
alice1Messenger := alice1Backend.Messenger()
|
alice1Messenger := alice1Backend.Messenger()
|
||||||
_, err = alice1Messenger.Start()
|
_, err = alice1Messenger.Start()
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
@ -527,7 +547,7 @@ func (s *SyncDeviceSuite) TestPairingThreeDevices() {
|
|||||||
alice2Backend := s.prepareBackendWithoutAccount(alice2TmpDir)
|
alice2Backend := s.prepareBackendWithoutAccount(alice2TmpDir)
|
||||||
|
|
||||||
alice3TmpDir := filepath.Join(s.pairThreeDevicesTmpdir, "alice3")
|
alice3TmpDir := filepath.Join(s.pairThreeDevicesTmpdir, "alice3")
|
||||||
alice3Backend := s.prepareBackendWithAccount(alice3TmpDir)
|
alice3Backend := s.prepareBackendWithAccount("", alice3TmpDir)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
require.NoError(s.T(), bobBackend.Logout())
|
require.NoError(s.T(), bobBackend.Logout())
|
||||||
@ -688,3 +708,160 @@ func buildTestMessage(chat *protocol.Chat) *common.Message {
|
|||||||
|
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SyncDeviceSuite) getSeedPhraseKeypairForTest(backend *api.GethStatusBackend, server bool) *accounts.Keypair {
|
||||||
|
generatedAccount, err := backend.AccountManager().AccountsGenerator().ImportMnemonic(seedPhraseMnemonic, "")
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
generatedDerivedAccs, err := backend.AccountManager().AccountsGenerator().DeriveAddresses(generatedAccount.ID, []string{path0, path1})
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
seedPhraseKp := &accounts.Keypair{
|
||||||
|
KeyUID: generatedAccount.KeyUID,
|
||||||
|
Name: "SeedPhraseImported",
|
||||||
|
Type: accounts.KeypairTypeSeed,
|
||||||
|
DerivedFrom: generatedAccount.Address,
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for path, ga := range generatedDerivedAccs {
|
||||||
|
acc := &accounts.Account{
|
||||||
|
Address: types.HexToAddress(ga.Address),
|
||||||
|
KeyUID: generatedAccount.KeyUID,
|
||||||
|
Wallet: false,
|
||||||
|
Chat: false,
|
||||||
|
Type: accounts.AccountTypeSeed,
|
||||||
|
Path: path,
|
||||||
|
PublicKey: types.HexBytes(ga.PublicKey),
|
||||||
|
Name: fmt.Sprintf("Acc_%d", i),
|
||||||
|
Operable: accounts.AccountFullyOperable,
|
||||||
|
Emoji: fmt.Sprintf("Emoji_%d", i),
|
||||||
|
ColorID: "blue",
|
||||||
|
}
|
||||||
|
if !server {
|
||||||
|
acc.Operable = accounts.AccountNonOperable
|
||||||
|
}
|
||||||
|
seedPhraseKp.Accounts = append(seedPhraseKp.Accounts, acc)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return seedPhraseKp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SyncDeviceSuite) TestTransferringKeystoreFiles() {
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
serverTmpDir := filepath.Join(s.clientAsReceiverTmpdir, "server")
|
||||||
|
serverBackend := s.prepareBackendWithAccount(profileMnemonic, serverTmpDir)
|
||||||
|
|
||||||
|
clientTmpDir := filepath.Join(s.clientAsReceiverTmpdir, "client")
|
||||||
|
clientBackend := s.prepareBackendWithAccount(profileMnemonic, clientTmpDir)
|
||||||
|
defer func() {
|
||||||
|
require.NoError(s.T(), clientBackend.Logout())
|
||||||
|
require.NoError(s.T(), serverBackend.Logout())
|
||||||
|
}()
|
||||||
|
|
||||||
|
serverBackend.Messenger().SetLocalPairing(true)
|
||||||
|
clientBackend.Messenger().SetLocalPairing(true)
|
||||||
|
|
||||||
|
serverActiveAccount, err := serverBackend.GetActiveAccount()
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
clientActiveAccount, err := clientBackend.GetActiveAccount()
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
require.True(s.T(), serverActiveAccount.KeyUID == clientActiveAccount.KeyUID)
|
||||||
|
|
||||||
|
serverSeedPhraseKp := s.getSeedPhraseKeypairForTest(serverBackend, true)
|
||||||
|
serverAccountsAPI := serverBackend.StatusNode().AccountService().APIs()[1].Service.(*accservice.API)
|
||||||
|
err = serverAccountsAPI.ImportMnemonic(ctx, seedPhraseMnemonic, s.password)
|
||||||
|
require.NoError(s.T(), err, "importing mnemonic for new keypair on server")
|
||||||
|
err = serverAccountsAPI.AddKeypair(ctx, s.password, serverSeedPhraseKp)
|
||||||
|
require.NoError(s.T(), err, "saving seed phrase keypair on server with keystore files created")
|
||||||
|
|
||||||
|
clientSeedPhraseKp := s.getSeedPhraseKeypairForTest(serverBackend, true)
|
||||||
|
clientAccountsAPI := clientBackend.StatusNode().AccountService().APIs()[1].Service.(*accservice.API)
|
||||||
|
err = clientAccountsAPI.SaveKeypair(ctx, clientSeedPhraseKp)
|
||||||
|
require.NoError(s.T(), err, "saving seed phrase keypair on client without keystore files")
|
||||||
|
|
||||||
|
containsKeystoreFile := func(directory, key string) bool {
|
||||||
|
files, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if strings.Contains(file.Name(), strings.ToLower(key)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check server - server should contain keystore files for imported seed phrase
|
||||||
|
serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir, serverActiveAccount.KeyUID)
|
||||||
|
require.True(s.T(), containsKeystoreFile(serverKeystorePath, serverSeedPhraseKp.DerivedFrom[2:]))
|
||||||
|
for _, acc := range serverSeedPhraseKp.Accounts {
|
||||||
|
require.True(s.T(), containsKeystoreFile(serverKeystorePath, acc.Address.String()[2:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check client - client should not contain keystore files for imported seed phrase
|
||||||
|
clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir, clientActiveAccount.KeyUID)
|
||||||
|
require.False(s.T(), containsKeystoreFile(clientKeystorePath, clientSeedPhraseKp.DerivedFrom[2:]))
|
||||||
|
for _, acc := range clientSeedPhraseKp.Accounts {
|
||||||
|
require.False(s.T(), containsKeystoreFile(clientKeystorePath, acc.Address.String()[2:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare sender
|
||||||
|
var config = KeystoreFilesSenderServerConfig{
|
||||||
|
SenderConfig: &KeystoreFilesSenderConfig{
|
||||||
|
KeystoreFilesConfig: KeystoreFilesConfig{
|
||||||
|
KeystorePath: serverKeystorePath,
|
||||||
|
LoggedInKeyUID: serverActiveAccount.KeyUID,
|
||||||
|
Password: s.password,
|
||||||
|
},
|
||||||
|
KeypairsToExport: []string{serverSeedPhraseKp.KeyUID},
|
||||||
|
},
|
||||||
|
ServerConfig: new(ServerConfig),
|
||||||
|
}
|
||||||
|
configBytes, err := json.Marshal(config)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
cs, err := StartUpKeystoreFilesSenderServer(serverBackend, string(configBytes))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
// prepare receiver
|
||||||
|
clientPayloadSourceConfig := KeystoreFilesReceiverClientConfig{
|
||||||
|
ReceiverConfig: &KeystoreFilesReceiverConfig{
|
||||||
|
KeystoreFilesConfig: KeystoreFilesConfig{
|
||||||
|
KeystorePath: clientKeystorePath,
|
||||||
|
LoggedInKeyUID: clientActiveAccount.KeyUID,
|
||||||
|
Password: s.password,
|
||||||
|
},
|
||||||
|
KeypairsToImport: []string{serverSeedPhraseKp.KeyUID},
|
||||||
|
},
|
||||||
|
ClientConfig: new(ClientConfig),
|
||||||
|
}
|
||||||
|
clientConfigBytes, err := json.Marshal(clientPayloadSourceConfig)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
err = StartUpKeystoreFilesReceivingClient(clientBackend, cs, string(clientConfigBytes))
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
|
||||||
|
// check client - client should contain keystore files for imported seed phrase
|
||||||
|
accountManager := clientBackend.AccountManager()
|
||||||
|
accGenerator := accountManager.AccountsGenerator()
|
||||||
|
require.True(s.T(), containsKeystoreFile(clientKeystorePath, clientSeedPhraseKp.DerivedFrom[2:]))
|
||||||
|
for _, acc := range clientSeedPhraseKp.Accounts {
|
||||||
|
require.True(s.T(), containsKeystoreFile(clientKeystorePath, acc.Address.String()[2:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// reinit keystore on client
|
||||||
|
require.NoError(s.T(), accountManager.InitKeystore(clientKeystorePath))
|
||||||
|
|
||||||
|
// check keystore on client
|
||||||
|
genAccInfo, err := accGenerator.LoadAccount(clientSeedPhraseKp.DerivedFrom, s.password)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
require.Equal(s.T(), clientSeedPhraseKp.KeyUID, genAccInfo.KeyUID)
|
||||||
|
for _, acc := range clientSeedPhraseKp.Accounts {
|
||||||
|
genAccInfo, err := accGenerator.LoadAccount(acc.Address.String(), s.password)
|
||||||
|
require.NoError(s.T(), err)
|
||||||
|
require.Equal(s.T(), acc.Address.String(), genAccInfo.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -82,3 +82,12 @@ func (s *Service) GetSettings() (settings.Settings, error) {
|
|||||||
func (s *Service) GetMessenger() *protocol.Messenger {
|
func (s *Service) GetMessenger() *protocol.Messenger {
|
||||||
return s.messenger
|
return s.messenger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) VerifyPassword(password string) bool {
|
||||||
|
address, err := s.db.GetChatAddress()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err = s.manager.VerifyAccountPassword(s.config.KeyStoreDir, address.Hex(), password)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user