493 lines
14 KiB
Go
493 lines
14 KiB
Go
package pairing
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"go.uber.org/multierr"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/status-im/status-go/api"
|
|
"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/signal"
|
|
)
|
|
|
|
type PayloadReceiver interface {
|
|
PayloadLocker
|
|
|
|
// Receive accepts data from an inbound source into the PayloadReceiver's state
|
|
Receive(data []byte) error
|
|
|
|
// Received returns a decrypted and parsed payload from an inbound source
|
|
Received() []byte
|
|
}
|
|
|
|
type PayloadStorer interface {
|
|
Store() error
|
|
}
|
|
|
|
type BasePayloadReceiver struct {
|
|
*PayloadLockPayload
|
|
*PayloadReceived
|
|
|
|
encryptor *PayloadEncryptor
|
|
unmarshaller ProtobufUnmarshaller
|
|
storer PayloadStorer
|
|
|
|
receiveCallback func()
|
|
}
|
|
|
|
func NewBasePayloadReceiver(e *PayloadEncryptor, um ProtobufUnmarshaller, s PayloadStorer, callback func()) *BasePayloadReceiver {
|
|
return &BasePayloadReceiver{
|
|
PayloadLockPayload: &PayloadLockPayload{e},
|
|
PayloadReceived: &PayloadReceived{e},
|
|
encryptor: e,
|
|
unmarshaller: um,
|
|
storer: s,
|
|
receiveCallback: callback,
|
|
}
|
|
}
|
|
|
|
// Receive takes a []byte representing raw data, parses and stores the data
|
|
func (bpr *BasePayloadReceiver) Receive(data []byte) error {
|
|
err := bpr.encryptor.decrypt(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = bpr.unmarshaller.UnmarshalProtobuf(bpr.Received())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = bpr.storer.Store()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if bpr.receiveCallback != nil {
|
|
bpr.receiveCallback()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| AccountPayload
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| AccountPayloadReceiver, AccountPayloadStorer and AccountPayloadMarshaller
|
|
|
|
|
*/
|
|
|
|
// NewAccountPayloadReceiver generates a new and initialised AccountPayload flavoured BasePayloadReceiver
|
|
// AccountPayloadReceiver is responsible for the whole receive and store cycle of an AccountPayload
|
|
func NewAccountPayloadReceiver(e *PayloadEncryptor, p *AccountPayload, config *ReceiverConfig, logger *zap.Logger) (*BasePayloadReceiver, error) {
|
|
l := logger.Named("AccountPayloadManager")
|
|
l.Debug("fired", zap.Any("config", config))
|
|
|
|
e = e.Renew()
|
|
|
|
aps, err := NewAccountPayloadStorer(p, config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewBasePayloadReceiver(e, NewPairingPayloadMarshaller(p, l), aps,
|
|
func() {
|
|
data := AccountData{Account: p.multiaccount, Password: p.password, ChatKey: p.chatKey, KeycardPairings: p.keycardPairings}
|
|
signal.SendLocalPairingEvent(Event{Type: EventReceivedAccount, Action: ActionPairingAccount, Data: data})
|
|
},
|
|
), nil
|
|
}
|
|
|
|
// AccountPayloadStorer is responsible for parsing, validating and storing AccountPayload data
|
|
type AccountPayloadStorer struct {
|
|
*AccountPayload
|
|
multiaccountsDB *multiaccounts.Database
|
|
|
|
keystorePath string
|
|
kdfIterations int
|
|
loggedInKeyUID string
|
|
}
|
|
|
|
func NewAccountPayloadStorer(p *AccountPayload, config *ReceiverConfig) (*AccountPayloadStorer, error) {
|
|
ppr := &AccountPayloadStorer{
|
|
AccountPayload: p,
|
|
}
|
|
|
|
if config == nil {
|
|
return ppr, nil
|
|
}
|
|
|
|
ppr.multiaccountsDB = config.DB
|
|
ppr.kdfIterations = config.KDFIterations
|
|
ppr.keystorePath = config.KeystorePath
|
|
ppr.loggedInKeyUID = config.LoggedInKeyUID
|
|
return ppr, nil
|
|
}
|
|
|
|
func (aps *AccountPayloadStorer) Store() error {
|
|
keyUID := aps.multiaccount.KeyUID
|
|
if aps.loggedInKeyUID != "" && aps.loggedInKeyUID != keyUID {
|
|
return ErrLoggedInKeyUIDConflict
|
|
}
|
|
if aps.loggedInKeyUID == keyUID {
|
|
// skip storing keys if user is logged in with the same key
|
|
return nil
|
|
}
|
|
|
|
err := validateKeys(aps.keys, aps.password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = aps.storeKeys(aps.keystorePath); err != nil && err != ErrKeyFileAlreadyExists {
|
|
return err
|
|
}
|
|
|
|
// skip storing multiaccount if key already exists
|
|
if err == ErrKeyFileAlreadyExists {
|
|
aps.exist = true
|
|
aps.multiaccount, err = aps.multiaccountsDB.GetAccount(keyUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
return aps.storeMultiAccount()
|
|
}
|
|
|
|
func (aps *AccountPayloadStorer) 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 {
|
|
if aps.multiaccount == nil || aps.multiaccount.KeyUID == "" {
|
|
return fmt.Errorf("no known Key UID")
|
|
}
|
|
keyStorePath = filepath.Join(keyStorePath, aps.multiaccount.KeyUID)
|
|
_, err := os.Stat(keyStorePath)
|
|
if os.IsNotExist(err) {
|
|
err := os.MkdirAll(keyStorePath, 0700)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
} else {
|
|
return ErrKeyFileAlreadyExists
|
|
}
|
|
}
|
|
|
|
for name, data := range aps.keys {
|
|
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
|
|
}
|
|
|
|
func (aps *AccountPayloadStorer) storeMultiAccount() error {
|
|
aps.multiaccount.KDFIterations = aps.kdfIterations
|
|
return aps.multiaccountsDB.SaveAccount(*aps.multiaccount)
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| RawMessagePayload
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| RawMessagePayloadReceiver and RawMessageStorer
|
|
|
|
|
*/
|
|
|
|
// NewRawMessagePayloadReceiver generates a new and initialised RawMessagesPayload flavoured BasePayloadReceiver
|
|
// RawMessagePayloadReceiver is responsible for the whole receive and store cycle of a RawMessagesPayload
|
|
func NewRawMessagePayloadReceiver(accountPayload *AccountPayload, e *PayloadEncryptor, backend *api.GethStatusBackend, config *ReceiverConfig) *BasePayloadReceiver {
|
|
e = e.Renew()
|
|
payload := NewRawMessagesPayload()
|
|
|
|
return NewBasePayloadReceiver(e,
|
|
NewRawMessagePayloadMarshaller(payload),
|
|
NewRawMessageStorer(backend, payload, accountPayload, config), nil)
|
|
}
|
|
|
|
type RawMessageStorer struct {
|
|
payload *RawMessagesPayload
|
|
syncRawMessageHandler *SyncRawMessageHandler
|
|
accountPayload *AccountPayload
|
|
nodeConfig *params.NodeConfig
|
|
settingCurrentNetwork string
|
|
deviceType string
|
|
deviceName string
|
|
}
|
|
|
|
func NewRawMessageStorer(backend *api.GethStatusBackend, payload *RawMessagesPayload, accountPayload *AccountPayload, config *ReceiverConfig) *RawMessageStorer {
|
|
return &RawMessageStorer{
|
|
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
|
|
payload: payload,
|
|
accountPayload: accountPayload,
|
|
nodeConfig: config.NodeConfig,
|
|
settingCurrentNetwork: config.SettingCurrentNetwork,
|
|
deviceType: config.DeviceType,
|
|
deviceName: config.DeviceName,
|
|
}
|
|
}
|
|
|
|
func (r *RawMessageStorer) Store() error {
|
|
if r.accountPayload == nil || r.accountPayload.multiaccount == nil {
|
|
return fmt.Errorf("no known multiaccount when storing raw messages")
|
|
}
|
|
return r.syncRawMessageHandler.HandleRawMessage(r.accountPayload, r.nodeConfig, r.settingCurrentNetwork, r.deviceType, r.deviceName, r.payload)
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| InstallationPayload
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| InstallationPayloadReceiver and InstallationPayloadStorer
|
|
|
|
|
*/
|
|
|
|
// NewInstallationPayloadReceiver generates a new and initialised InstallationPayload flavoured BasePayloadReceiver
|
|
// InstallationPayloadReceiver is responsible for the whole receive and store cycle of a RawMessagesPayload specifically
|
|
// for sending / requesting installation data from the Receiver device.
|
|
func NewInstallationPayloadReceiver(e *PayloadEncryptor, backend *api.GethStatusBackend, deviceType string) *BasePayloadReceiver {
|
|
e = e.Renew()
|
|
payload := NewRawMessagesPayload()
|
|
|
|
return NewBasePayloadReceiver(e,
|
|
NewRawMessagePayloadMarshaller(payload),
|
|
NewInstallationPayloadStorer(backend, payload, deviceType), nil)
|
|
}
|
|
|
|
type InstallationPayloadStorer struct {
|
|
payload *RawMessagesPayload
|
|
syncRawMessageHandler *SyncRawMessageHandler
|
|
deviceType string
|
|
backend *api.GethStatusBackend
|
|
}
|
|
|
|
func NewInstallationPayloadStorer(backend *api.GethStatusBackend, payload *RawMessagesPayload, deviceType string) *InstallationPayloadStorer {
|
|
return &InstallationPayloadStorer{
|
|
payload: payload,
|
|
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
|
|
deviceType: deviceType,
|
|
backend: backend,
|
|
}
|
|
}
|
|
|
|
func (r *InstallationPayloadStorer) Store() error {
|
|
messenger := r.backend.Messenger()
|
|
if messenger == nil {
|
|
return fmt.Errorf("messenger is nil when invoke InstallationPayloadRepository#Store()")
|
|
}
|
|
err := messenger.SetInstallationDeviceType(r.deviceType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
installations := GetMessengerInstallationsMap(messenger)
|
|
|
|
err = messenger.HandleSyncRawMessages(r.payload.rawMessages)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if newInstallation := FindNewInstallations(messenger, installations); newInstallation != nil {
|
|
signal.SendLocalPairingEvent(Event{
|
|
Type: EventReceivedInstallation,
|
|
Action: ActionPairingInstallation,
|
|
Data: newInstallation})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| PayloadReceivers
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Funcs for all PayloadReceivers AccountPayloadReceiver, RawMessagePayloadReceiver and InstallationPayloadMounter
|
|
|
|
|
*/
|
|
|
|
func NewPayloadReceivers(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, config *ReceiverConfig) (PayloadReceiver, PayloadReceiver, PayloadMounterReceiver, error) {
|
|
// A new SHARED AccountPayload
|
|
p := new(AccountPayload)
|
|
|
|
ar, err := NewAccountPayloadReceiver(pe, p, config, logger)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
rmr := NewRawMessagePayloadReceiver(p, pe, backend, config)
|
|
imr := NewInstallationPayloadMounterReceiver(pe, backend, config.DeviceType)
|
|
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 := config.KeypairsToImport
|
|
signal.SendLocalPairingEvent(Event{Type: EventReceivedKeystoreFiles, Action: ActionKeystoreFilesTransfer, Data: data})
|
|
},
|
|
), nil
|
|
}
|
|
|
|
type KeystoreFilesPayloadStorer struct {
|
|
*AccountPayload
|
|
|
|
keystorePath string
|
|
loggedInKeyUID string
|
|
expectedKeypairsToImport []string
|
|
expectedKeystoreFilesToReceive []string
|
|
backend *api.GethStatusBackend
|
|
}
|
|
|
|
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,
|
|
expectedKeypairsToImport: config.KeypairsToImport,
|
|
backend: backend,
|
|
}
|
|
|
|
accountService := backend.StatusNode().AccountService()
|
|
|
|
for _, keyUID := range kfps.expectedKeypairsToImport {
|
|
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 {
|
|
messenger := kfps.backend.Messenger()
|
|
if messenger == nil {
|
|
return fmt.Errorf("messenger is nil")
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
for _, keyUID := range kfps.expectedKeypairsToImport {
|
|
err := messenger.MarkKeypairFullyOperable(keyUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|