Improved Local Pairing Separation of Concerns (#3248)

* Moved all configs into config.go

* Completed build out of new config structures

* Completed SenderClient process flow

* Completed sync data Mounter and client integration

* Completed installation data Mounter and client integration

* House keeping, small refactor to match conventions.

PayloadEncryptor is passed by value and used as a pointer to the instance value and not a shared pointer.

* Reintroduced explicit Mounter field type

* Completed ReceiverClient structs and flows

* Finished BaseClient function parity with old acc

* Integrated new Clients into tests

Solved some test breaks caused by encryptors sharing pointers to their managed payloads

* Built out SenderServer and ReceiverServer structs

With all associated functions and integrated with endpoints.

* Updated tests to handle new Server types

* Added docs and additional refinement

* Renamed some files to better match the content of those files

* Added json tags to config fields that were missing explicit tags.

* fix tests relating to payload locking

* Addressing feedback from @ilmotta

* Addressed feedback from @qfrank
This commit is contained in:
Samuel Hawksby-Robinson 2023-03-23 11:44:15 +00:00 committed by GitHub
parent 7bc03e22f7
commit 7cd7430d31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1996 additions and 1486 deletions

View File

@ -1 +1 @@
0.139.1 0.140.0

2
go.mod
View File

@ -83,6 +83,7 @@ require (
github.com/waku-org/go-waku v0.5.2-0.20230308135126-4b52983fc483 github.com/waku-org/go-waku v0.5.2-0.20230308135126-4b52983fc483
github.com/yeqown/go-qrcode/v2 v2.2.1 github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1 github.com/yeqown/go-qrcode/writer/standard v1.2.1
go.uber.org/multierr v1.8.0
) )
require ( require (
@ -251,7 +252,6 @@ require (
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect
go.uber.org/dig v1.15.0 // indirect go.uber.org/dig v1.15.0 // indirect
go.uber.org/fx v1.18.2 // indirect go.uber.org/fx v1.18.2 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/mod v0.7.0 // indirect golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.4.0 // indirect golang.org/x/net v0.4.0 // indirect

View File

@ -1008,7 +1008,7 @@ func GenerateImages(filepath string, aX, aY, bX, bY int) string {
return string(data) return string(data)
} }
// GetConnectionStringForBeingBootstrapped starts a pairing.Receiving pairing.PairingServer // GetConnectionStringForBeingBootstrapped starts a pairing.ReceiverServer
// then generates a pairing.ConnectionParams. Used when the device is Logged out or has no Account keys // then generates a pairing.ConnectionParams. Used when the device is Logged out or has no Account keys
// and the device has no camera to read a QR code with // and the device has no camera to read a QR code with
// //
@ -1017,14 +1017,14 @@ func GetConnectionStringForBeingBootstrapped(configJSON string) string {
if configJSON == "" { if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected")) return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected"))
} }
cs, err := pairing.StartUpPairingServer(statusBackend, pairing.Receiving, configJSON) cs, err := pairing.StartUpReceiverServer(statusBackend, pairing.Receiving, configJSON)
if err != nil { if err != nil {
return makeJSONResponse(err) return makeJSONResponse(err)
} }
return cs return cs
} }
// GetConnectionStringForBootstrappingAnotherDevice starts a pairing.Sending pairing.Server // GetConnectionStringForBootstrappingAnotherDevice starts a pairing.SenderServer
// then generates a pairing.ConnectionParams. Used when the device is Logged in and therefore has Account keys // 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 // and the device might not have a camera
// //
@ -1032,34 +1032,45 @@ func GetConnectionStringForBeingBootstrapped(configJSON string) string {
// sending account data to a mobile (device with camera) // sending account data to a mobile (device with camera)
func GetConnectionStringForBootstrappingAnotherDevice(configJSON string) string { func GetConnectionStringForBootstrappingAnotherDevice(configJSON string) string {
if configJSON == "" { if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected")) return makeJSONResponse(fmt.Errorf("no config given, SendingServerConfig is expected"))
} }
cs, err := pairing.StartUpPairingServer(statusBackend, pairing.Sending, configJSON) cs, err := pairing.StartUpSenderServer(statusBackend, pairing.Sending, configJSON)
if err != nil { if err != nil {
return makeJSONResponse(err) return makeJSONResponse(err)
} }
return cs return cs
} }
// InputConnectionStringForBootstrapping starts a pairing.Client // InputConnectionStringForBootstrapping starts a pairing.ReceiverClient
// The given server.ConnectionParams string will determine the server.Mode // The given server.ConnectionParams string will determine the server.Mode
// //
// server.Mode = server.Sending // server.Mode = server.Sending
// Used when the device is Logged in and therefore has Account keys and the has a camera to read a QR code
//
// Example: A mobile (device with camera) sending account data to a desktop device (device without camera)
//
// server.Mode = server.Receiving
// Used when the device is Logged out or has no Account keys and has a camera to read a QR code // Used when the device is Logged out or has no Account keys and has a camera to read a QR code
// //
// Example: A mobile device (device with a camera) receiving account data from // Example: A mobile device (device with a camera) receiving account data from
// a device with a screen (mobile or desktop devices) // a device with a screen (mobile or desktop devices)
func InputConnectionStringForBootstrapping(cs, configJSON string) string { func InputConnectionStringForBootstrapping(cs, configJSON string) string {
if configJSON == "" { if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, PayloadSourceConfig is expected")) return makeJSONResponse(fmt.Errorf("no config given, ReceiverClientConfig is expected"))
} }
err := pairing.StartUpPairingClient(statusBackend, cs, configJSON) err := pairing.StartUpReceivingClient(statusBackend, cs, configJSON)
return makeJSONResponse(err)
}
// InputConnectionStringForBootstrappingAnotherDevice starts a pairing.SendingClient
// The given server.ConnectionParams string will determine the server.Mode
//
// server.Mode = server.Receiving
// Used when the device is Logged in and therefore has Account keys and the has a camera to read a QR code
//
// Example: A mobile (device with camera) sending account data to a desktop device (device without camera)
func InputConnectionStringForBootstrappingAnotherDevice(cs, configJSON string) string {
if configJSON == "" {
return makeJSONResponse(fmt.Errorf("no config given, SenderClientConfig is expected"))
}
err := pairing.StartUpSendingClient(statusBackend, cs, configJSON)
return makeJSONResponse(err) return makeJSONResponse(err)
} }

View File

@ -310,7 +310,7 @@ type NodeConfig struct {
// NetworkID sets network to use for selecting peers to connect to // NetworkID sets network to use for selecting peers to connect to
NetworkID uint64 `json:"NetworkId" validate:"required"` NetworkID uint64 `json:"NetworkId" validate:"required"`
RootDataDir string `json:"-"` RootDataDir string `json:",omitempty"`
// DataDir is the file system folder the node should use for any data storage needs. // DataDir is the file system folder the node should use for any data storage needs.
DataDir string `validate:"required"` DataDir string `validate:"required"`

View File

@ -18,6 +18,9 @@ import (
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
) )
// TODO Reconcile duplicate function here and in server/certs.go
// https://github.com/status-im/status-go/issues/3300
func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int { func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int {
h := sha256.New() h := sha256.New()
h.Write(append(pk.D.Bytes(), append(pk.Y.Bytes(), pk.X.Bytes()...)...)) h.Write(append(pk.D.Bytes(), append(pk.Y.Bytes(), pk.X.Bytes()...)...))

View File

@ -2,9 +2,9 @@ package pairing
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/json"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"io" "io"
@ -20,21 +20,26 @@ import (
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
) )
type Client struct { /*
*http.Client |--------------------------------------------------------------------------
PayloadManager | BaseClient
rawMessagePayloadManager *RawMessagePayloadManager |--------------------------------------------------------------------------
installationPayloadManager *InstallationPayloadManager |
|
|
*/
baseAddress *url.URL // BaseClient is responsible for lower level pairing.Client functionality common to dependent Client types
certPEM []byte type BaseClient struct {
serverPK *ecdsa.PublicKey *http.Client
serverMode Mode
serverCert *x509.Certificate serverCert *x509.Certificate
encryptor *PayloadEncryptor
baseAddress *url.URL
serverChallenge []byte serverChallenge []byte
} }
func NewPairingClient(backend *api.GethStatusBackend, c *ConnectionParams, config *AccountPayloadManagerConfig) (*Client, error) { // NewBaseClient returns a fully qualified BaseClient from the given ConnectionParams
func NewBaseClient(c *ConnectionParams) (*BaseClient, error) {
u, err := c.URL() u, err := c.URL()
if err != nil { if err != nil {
return nil, err return nil, err
@ -44,10 +49,12 @@ func NewPairingClient(backend *api.GethStatusBackend, c *ConnectionParams, confi
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = verifyCert(serverCert, c.publicKey) err = verifyCert(serverCert, c.publicKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert.Raw}) certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert.Raw})
rootCAs, err := x509.SystemCertPool() rootCAs, err := x509.SystemCertPool()
@ -71,117 +78,116 @@ func NewPairingClient(backend *api.GethStatusBackend, c *ConnectionParams, confi
return nil, err return nil, err
} }
logger := logutils.ZapLogger().Named("Client") return &BaseClient{
pm, err := NewAccountPayloadManager(c.aesKey, config, logger) Client: &http.Client{Transport: tr, Jar: cj},
if err != nil { serverCert: serverCert,
return nil, err encryptor: NewPayloadEncryptor(c.aesKey),
} baseAddress: u,
rmpm, err := NewRawMessagePayloadManager(logger, pm.accountPayload, c.aesKey, backend, config.GetNodeConfig(), config.GetSettingCurrentNetwork(), config.GetDeviceType())
if err != nil {
return nil, err
}
ipm, err := NewInstallationPayloadManager(logger, c.aesKey, backend, config.GetDeviceType())
if err != nil {
return nil, err
}
return &Client{
Client: &http.Client{Transport: tr, Jar: cj},
baseAddress: u,
certPEM: certPem,
serverCert: serverCert,
serverPK: c.publicKey,
serverMode: c.serverMode,
PayloadManager: pm,
rawMessagePayloadManager: rmpm,
installationPayloadManager: ipm,
}, nil }, nil
} }
func (c *Client) PairAccount() error { // getChallenge makes a call to the identified Server and receives a [32]byte challenge
switch c.serverMode { func (c *BaseClient) getChallenge() error {
case Receiving: c.baseAddress.Path = pairingChallenge
return c.sendAccountData() resp, err := c.Get(c.baseAddress.String())
case Sending:
err := c.getChallenge()
if err != nil {
return err
}
return c.receiveAccountData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
func (c *Client) PairSyncDevice() error {
switch c.serverMode {
case Receiving:
return c.sendSyncDeviceData()
case Sending:
return c.receiveSyncDeviceData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
// PairInstallation transfer installation data from receiver to sender.
// installation data from sender to receiver already processed within PairSyncDevice method on the receiver side.
func (c *Client) PairInstallation() error {
switch c.serverMode {
case Receiving:
return c.receiveInstallationData()
case Sending:
return c.sendInstallationData()
default:
return fmt.Errorf("unrecognised server mode '%d'", c.serverMode)
}
}
func (c *Client) sendInstallationData() error {
err := c.installationPayloadManager.Mount()
if err != nil { if err != nil {
return err return err
} }
c.baseAddress.Path = pairingReceiveInstallation
req, err := http.NewRequest(http.MethodPost, c.baseAddress.String(), bytes.NewBuffer(c.installationPayloadManager.ToSend()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/octet-stream")
if c.serverChallenge != nil {
ec, err := c.PayloadManager.EncryptPlain(c.serverChallenge)
if err != nil {
return err
}
req.Header.Set(sessionChallenge, base58.Encode(ec))
}
resp, err := c.Do(req)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not okay when sending installation data, status: %s", resp.Status) return fmt.Errorf("[client] status not ok when getting challenge, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation}) c.serverChallenge, err = ioutil.ReadAll(resp.Body)
return err
}
// doChallenge checks if there is a serverChallenge and encrypts the challenge using the shared AES key
func (c *BaseClient) doChallenge(req *http.Request) error {
if c.serverChallenge != nil {
ec, err := c.encryptor.encryptPlain(c.serverChallenge)
if err != nil {
return err
}
req.Header.Set(sessionChallenge, base58.Encode(ec))
}
return nil return nil
} }
func (c *Client) sendSyncDeviceData() error { /*
err := c.rawMessagePayloadManager.Mount() |--------------------------------------------------------------------------
| SenderClient
|--------------------------------------------------------------------------
|
| With AccountPayloadMounter, RawMessagePayloadMounter and InstallationPayloadMounterReceiver
|
*/
// SenderClient is responsible for sending pairing data to a ReceiverServer
type SenderClient struct {
*BaseClient
accountMounter PayloadMounter
rawMessageMounter *RawMessagePayloadMounter
installationMounter *InstallationPayloadMounterReceiver
}
// NewSenderClient returns a fully qualified SenderClient created with the incoming parameters
func NewSenderClient(backend *api.GethStatusBackend, c *ConnectionParams, config *SenderClientConfig) (*SenderClient, error) {
logger := logutils.ZapLogger().Named("SenderClient")
pe := NewPayloadEncryptor(c.aesKey)
bc, err := NewBaseClient(c)
if err != nil {
return nil, err
}
am, rmm, imr, err := NewPayloadMounters(logger, pe, backend, config.SenderConfig)
if err != nil {
return nil, err
}
return &SenderClient{
BaseClient: bc,
accountMounter: am,
rawMessageMounter: rmm,
installationMounter: imr,
}, nil
}
func (c *SenderClient) sendAccountData() error {
err := c.accountMounter.Mount()
if err != nil { if err != nil {
return err return err
} }
c.baseAddress.Path = pairingSyncDeviceReceive c.baseAddress.Path = pairingReceiveAccount
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.rawMessagePayloadManager.ToSend())) resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.accountMounter.ToSend()))
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not ok when sending account data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
c.accountMounter.LockPayload()
return nil
}
func (c *SenderClient) sendSyncDeviceData() error {
err := c.rawMessageMounter.Mount()
if err != nil {
return err
}
c.baseAddress.Path = pairingReceiveSyncDevice
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.rawMessageMounter.ToSend()))
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err return err
@ -197,7 +203,7 @@ func (c *Client) sendSyncDeviceData() error {
return nil return nil
} }
func (c *Client) receiveInstallationData() error { func (c *SenderClient) receiveInstallationData() error {
c.baseAddress.Path = pairingSendInstallation c.baseAddress.Path = pairingSendInstallation
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil) req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil { if err != nil {
@ -223,7 +229,7 @@ func (c *Client) receiveInstallationData() error {
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
err = c.installationPayloadManager.Receive(payload) err = c.installationMounter.Receive(payload)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation}) signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation})
return err return err
@ -232,89 +238,94 @@ func (c *Client) receiveInstallationData() error {
return nil return nil
} }
func (c *Client) receiveSyncDeviceData() error { // setupSendingClient creates a new SenderClient after parsing string inputs
c.baseAddress.Path = pairingSyncDeviceSend func setupSendingClient(backend *api.GethStatusBackend, cs, configJSON string) (*SenderClient, error) {
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil) ccp := new(ConnectionParams)
err := ccp.FromString(cs)
if err != nil { if err != nil {
return err return nil, err
} }
if c.serverChallenge != nil { conf := NewSenderClientConfig()
ec, err := c.PayloadManager.EncryptPlain(c.serverChallenge) err = json.Unmarshal([]byte(configJSON), conf)
if err != nil {
return err
}
req.Header.Set(sessionChallenge, base58.Encode(ec))
}
resp, err := c.Do(req)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) return nil, err
return err
} }
if resp.StatusCode != http.StatusOK { conf.SenderConfig.DB = backend.GetMultiaccountDB()
err = fmt.Errorf("[client] status not ok when receiving sync device data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
payload, err := io.ReadAll(resp.Body) return NewSenderClient(backend, ccp, conf)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
err = c.rawMessagePayloadManager.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice})
return nil
} }
func (c *Client) sendAccountData() error { // StartUpSendingClient creates a SenderClient and triggers all `send` calls in sequence to the ReceiverServer
err := c.Mount() func StartUpSendingClient(backend *api.GethStatusBackend, cs, configJSON string) error {
c, err := setupSendingClient(backend, cs, configJSON)
if err != nil { if err != nil {
return err return err
} }
err = c.sendAccountData()
c.baseAddress.Path = pairingReceiveAccount
resp, err := c.Post(c.baseAddress.String(), "application/octet-stream", bytes.NewBuffer(c.PayloadManager.ToSend()))
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err return err
} }
err = c.sendSyncDeviceData()
if resp.StatusCode != http.StatusOK { if err != nil {
err = fmt.Errorf("[client] status not ok when sending account data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err return err
} }
return c.receiveInstallationData()
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
c.PayloadManager.LockPayload()
return nil
} }
func (c *Client) receiveAccountData() error { /*
|--------------------------------------------------------------------------
| ReceiverClient
|--------------------------------------------------------------------------
|
| With AccountPayloadReceiver, RawMessagePayloadReceiver, InstallationPayloadMounterReceiver
|
*/
// ReceiverClient is responsible for accepting pairing data to a SenderServer
type ReceiverClient struct {
*BaseClient
accountReceiver PayloadReceiver
rawMessageReceiver *RawMessagePayloadReceiver
installationReceiver *InstallationPayloadMounterReceiver
}
// NewReceiverClient returns a fully qualified ReceiverClient created with the incoming parameters
func NewReceiverClient(backend *api.GethStatusBackend, c *ConnectionParams, config *ReceiverClientConfig) (*ReceiverClient, error) {
bc, err := NewBaseClient(c)
if err != nil {
return nil, err
}
logger := logutils.ZapLogger().Named("ReceiverClient")
pe := NewPayloadEncryptor(c.aesKey)
ar, rmr, imr, err := NewPayloadReceivers(logger, pe, backend, config.ReceiverConfig)
if err != nil {
return nil, err
}
return &ReceiverClient{
BaseClient: bc,
accountReceiver: ar,
rawMessageReceiver: rmr,
installationReceiver: imr,
}, nil
}
func (c *ReceiverClient) receiveAccountData() error {
c.baseAddress.Path = pairingSendAccount c.baseAddress.Path = pairingSendAccount
req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil) req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil { if err != nil {
return err return err
} }
if c.serverChallenge != nil { err = c.doChallenge(req)
ec, err := c.PayloadManager.EncryptPlain(c.serverChallenge) if err != nil {
if err != nil { signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
return err return err
}
req.Header.Set(sessionChallenge, base58.Encode(ec))
} }
resp, err := c.Do(req) resp, err := c.Do(req)
@ -336,7 +347,7 @@ func (c *Client) receiveAccountData() error {
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
err = c.PayloadManager.Receive(payload) err = c.accountReceiver.Receive(payload)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount})
return err return err
@ -345,56 +356,118 @@ func (c *Client) receiveAccountData() error {
return nil return nil
} }
func (c *Client) getChallenge() error { func (c *ReceiverClient) receiveSyncDeviceData() error {
c.baseAddress.Path = pairingChallenge c.baseAddress.Path = pairingSendSyncDevice
resp, err := c.Get(c.baseAddress.String()) req, err := http.NewRequest(http.MethodGet, c.baseAddress.String(), nil)
if err != nil { if err != nil {
return err return err
} }
err = c.doChallenge(req)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
resp, err := c.Do(req)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("[client] status not ok when getting challenge, received '%s'", resp.Status) err = fmt.Errorf("[client] status not ok when receiving sync device data, received '%s'", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
} }
c.serverChallenge, err = ioutil.ReadAll(resp.Body) payload, err := io.ReadAll(resp.Body)
return err if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
err = c.rawMessageReceiver.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice})
return nil
} }
func StartUpPairingClient(backend *api.GethStatusBackend, cs, configJSON string) error { func (c *ReceiverClient) sendInstallationData() error {
c, err := setupClient(backend, cs, configJSON) err := c.installationReceiver.Mount()
if err != nil { if err != nil {
return err return err
} }
err = c.PairAccount()
c.baseAddress.Path = pairingReceiveInstallation
req, err := http.NewRequest(http.MethodPost, c.baseAddress.String(), bytes.NewBuffer(c.installationReceiver.ToSend()))
if err != nil { if err != nil {
return err return err
} }
err = c.PairSyncDevice() req.Header.Set("Content-Type", "application/octet-stream")
err = c.doChallenge(req)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err return err
} }
return c.PairInstallation()
resp, err := c.Do(req)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("[client] status not okay when sending installation data, status: %s", resp.Status)
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
return err
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
return nil
} }
func setupClient(backend *api.GethStatusBackend, cs string, configJSON string) (*Client, error) { // setupReceivingClient creates a new ReceiverClient after parsing string inputs
func setupReceivingClient(backend *api.GethStatusBackend, cs, configJSON string) (*ReceiverClient, error) {
ccp := new(ConnectionParams) ccp := new(ConnectionParams)
err := ccp.FromString(cs) err := ccp.FromString(cs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
conf, err := NewPayloadSourceForClient(configJSON, ccp.serverMode) conf := NewReceiverClientConfig()
err = json.Unmarshal([]byte(configJSON), conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
accountPayloadManagerConfig := &AccountPayloadManagerConfig{DB: backend.GetMultiaccountDB(), PayloadSourceConfig: conf} conf.ReceiverConfig.DB = backend.GetMultiaccountDB()
if ccp.serverMode == Sending {
updateLoggedInKeyUID(accountPayloadManagerConfig, backend) return NewReceiverClient(backend, ccp, conf)
} }
c, err := NewPairingClient(backend, ccp, accountPayloadManagerConfig)
if err != nil { // StartUpReceivingClient creates a ReceiverClient and triggers all `receive` calls in sequence to the SenderServer
return nil, err func StartUpReceivingClient(backend *api.GethStatusBackend, cs, configJSON string) error {
} c, err := setupReceivingClient(backend, cs, configJSON)
return c, nil if err != nil {
return err
}
err = c.getChallenge()
if err != nil {
return err
}
err = c.receiveAccountData()
if err != nil {
return err
}
err = c.receiveSyncDeviceData()
if err != nil {
return err
}
return c.sendInstallationData()
} }

View File

@ -1,10 +1,61 @@
package pairing package pairing
import "github.com/status-im/status-go/api" import (
"os"
"path/filepath"
func updateLoggedInKeyUID(accountPayloadManagerConfig *AccountPayloadManagerConfig, backend *api.GethStatusBackend) { "github.com/status-im/status-go/account/generator"
activeAccount, _ := backend.GetActiveAccount() "github.com/status-im/status-go/eth-node/keystore"
if activeAccount != nil { )
accountPayloadManagerConfig.LoggedInKeyUID = activeAccount.KeyUID
func validateKeys(keys map[string][]byte, password string) error {
for _, key := range keys {
k, err := keystore.DecryptKey(key, password)
if err != nil {
return err
}
err = generator.ValidateKeystoreExtendedKey(k)
if err != nil {
return err
}
} }
return nil
}
func emptyDir(dir string) error {
// Open the directory
d, err := os.Open(dir)
if err != nil {
return err
}
defer d.Close()
// Get all the directory entries
entries, err := d.Readdir(-1)
if err != nil {
return err
}
// Remove all the files and directories
for _, entry := range entries {
name := entry.Name()
if name == "." || name == ".." {
continue
}
path := filepath.Join(dir, name)
if entry.IsDir() {
err = os.RemoveAll(path)
if err != nil {
return err
}
} else {
err = os.Remove(path)
if err != nil {
return err
}
}
}
return nil
} }

View File

@ -6,7 +6,6 @@ import (
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"encoding/asn1" "encoding/asn1"
"math/big" "math/big"
"net" "net"
"testing" "testing"
@ -90,7 +89,8 @@ type TestPairingServerComponents struct {
EphemeralAES []byte EphemeralAES []byte
OutboundIP net.IP OutboundIP net.IP
Cert tls.Certificate Cert tls.Certificate
PS *Server SS *SenderServer
RS *ReceiverServer
} }
func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing.T) { func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing.T) {
@ -113,12 +113,16 @@ func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing
tpsc.Cert, _, err = GenerateCertFromKey(tpsc.EphemeralPK, time.Now(), tpsc.OutboundIP.String()) tpsc.Cert, _, err = GenerateCertFromKey(tpsc.EphemeralPK, time.Now(), tpsc.OutboundIP.String())
require.NoError(t, err) require.NoError(t, err)
tpsc.PS, err = NewPairingServer(nil, &Config{ sc := &ServerConfig{
PK: &tpsc.EphemeralPK.PublicKey, PK: &tpsc.EphemeralPK.PublicKey,
EK: tpsc.EphemeralAES, EK: tpsc.EphemeralAES,
Cert: &tpsc.Cert, Cert: &tpsc.Cert,
Hostname: tpsc.OutboundIP.String(), Hostname: tpsc.OutboundIP.String(),
AccountPayloadManagerConfig: &AccountPayloadManagerConfig{}}) }
tpsc.SS, err = NewSenderServer(nil, &SenderServerConfig{ServerConfig: sc, SenderConfig: &SenderConfig{}})
require.NoError(t, err)
tpsc.RS, err = NewReceiverServer(nil, &ReceiverServerConfig{ServerConfig: sc, ReceiverConfig: &ReceiverConfig{}})
require.NoError(t, err) require.NoError(t, err)
} }
@ -130,22 +134,33 @@ func (tlc *TestLoggerComponents) SetupLoggerComponents() {
tlc.Logger = logutils.ZapLogger() tlc.Logger = logutils.ZapLogger()
} }
type MockEncryptOnlyPayloadManager struct { type MockPayloadReceiver struct {
*PayloadEncryptionManager encryptor *PayloadEncryptor
} }
func NewMockEncryptOnlyPayloadManager(aesKey []byte) (*MockEncryptOnlyPayloadManager, error) { func NewMockPayloadReceiver(aesKey []byte) *MockPayloadReceiver {
pem, err := NewPayloadEncryptionManager(aesKey, logutils.ZapLogger()) return &MockPayloadReceiver{NewPayloadEncryptor(aesKey)}
if err != nil {
return nil, err
}
return &MockEncryptOnlyPayloadManager{
pem,
}, nil
} }
func (m *MockEncryptOnlyPayloadManager) Mount() error { func (m *MockPayloadReceiver) Receive(data []byte) error {
return m.encryptor.decrypt(data)
}
func (m *MockPayloadReceiver) Received() []byte {
return m.encryptor.getDecrypted()
}
func (m *MockPayloadReceiver) LockPayload() {}
type MockPayloadMounter struct {
encryptor *PayloadEncryptor
}
func NewMockPayloadMounter(aesKey []byte) *MockPayloadMounter {
return &MockPayloadMounter{NewPayloadEncryptor(aesKey)}
}
func (m *MockPayloadMounter) Mount() error {
// Make a random payload // Make a random payload
data := make([]byte, 32) data := make([]byte, 32)
_, err := rand.Read(data) _, err := rand.Read(data)
@ -153,9 +168,13 @@ func (m *MockEncryptOnlyPayloadManager) Mount() error {
return err return err
} }
return m.Encrypt(data) return m.encryptor.encrypt(data)
} }
func (m *MockEncryptOnlyPayloadManager) Receive(data []byte) error { func (m *MockPayloadMounter) ToSend() []byte {
return m.Decrypt(data) return m.encryptor.getEncrypted()
}
func (m *MockPayloadMounter) LockPayload() {
m.encryptor.lockPayload()
} }

100
server/pairing/config.go Normal file
View File

@ -0,0 +1,100 @@
package pairing
import (
"crypto/ecdsa"
"crypto/tls"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/params"
)
type SenderConfig struct {
// SenderConfig.KeystorePath must end with keyUID
KeystorePath string `json:"keystorePath"`
// DeviceType SendPairInstallation need this information
DeviceType string `json:"deviceType"`
KeyUID string `json:"keyUID"`
Password string `json:"password"`
DB *multiaccounts.Database `json:"-"`
}
type ReceiverConfig struct {
NodeConfig *params.NodeConfig `json:"nodeConfig"`
// ReceiverConfig.KeystorePath must not end with keyUID (because keyUID is not known yet)
KeystorePath string `json:"keystorePath"`
// DeviceType SendPairInstallation need this information
DeviceType string `json:"deviceType"`
KDFIterations int `json:"kdfIterations"`
// SettingCurrentNetwork corresponding to field current_network from table settings, so that we can override current network from sender
SettingCurrentNetwork string `json:"settingCurrentNetwork"`
DB *multiaccounts.Database `json:"-"`
LoggedInKeyUID string `json:"-"`
}
type ServerConfig struct {
// Timeout the number of milliseconds after which the pairing server will automatically terminate
Timeout uint `json:"timeout"`
// Connection fields, not json (un)marshalled
// Required for the server, but MUST NOT come from client
PK *ecdsa.PublicKey `json:"-"`
EK []byte `json:"-"`
Cert *tls.Certificate `json:"-"`
Hostname string `json:"-"`
Mode Mode `json:"-"`
}
type ClientConfig struct{}
type SenderServerConfig struct {
SenderConfig *SenderConfig `json:"senderConfig"`
ServerConfig *ServerConfig `json:"serverConfig"`
}
type SenderClientConfig struct {
SenderConfig *SenderConfig `json:"senderConfig"`
ClientConfig *ClientConfig `json:"clientConfig"`
}
type ReceiverClientConfig struct {
ReceiverConfig *ReceiverConfig `json:"receiverConfig"`
ClientConfig *ClientConfig `json:"clientConfig"`
}
type ReceiverServerConfig struct {
ReceiverConfig *ReceiverConfig `json:"receiverConfig"`
ServerConfig *ServerConfig `json:"serverConfig"`
}
func NewSenderServerConfig() *SenderServerConfig {
return &SenderServerConfig{
SenderConfig: new(SenderConfig),
ServerConfig: new(ServerConfig),
}
}
func NewSenderClientConfig() *SenderClientConfig {
return &SenderClientConfig{
SenderConfig: new(SenderConfig),
ClientConfig: new(ClientConfig),
}
}
func NewReceiverClientConfig() *ReceiverClientConfig {
return &ReceiverClientConfig{
ReceiverConfig: new(ReceiverConfig),
ClientConfig: new(ClientConfig),
}
}
func NewReceiverServerConfig() *ReceiverServerConfig {
return &ReceiverServerConfig{
ReceiverConfig: new(ReceiverConfig),
ServerConfig: new(ServerConfig),
}
}

View File

@ -69,6 +69,9 @@ func (cp *ConnectionParams) ToString() string {
ek := base58.Encode(cp.aesKey) ek := base58.Encode(cp.aesKey)
m := base58.Encode(new(big.Int).SetInt64(int64(cp.serverMode)).Bytes()) m := base58.Encode(new(big.Int).SetInt64(int64(cp.serverMode)).Bytes())
// TODO remove server mode from the connection string, rely on specific function calls rather than algorithmic orchestration
// https://github.com/status-im/status-go/issues/3301
return fmt.Sprintf("%s%s:%s:%s:%s:%s:%s", connectionStringID, v, ip, p, k, ek, m) return fmt.Sprintf("%s%s:%s:%s:%s:%s:%s", connectionStringID, v, ip, p, k, ek, m)
} }
@ -178,7 +181,7 @@ func (cp *ConnectionParams) validateAESKey() error {
func (cp *ConnectionParams) validateServerMode() error { func (cp *ConnectionParams) validateServerMode() error {
switch cp.serverMode { switch cp.serverMode {
case Receiving, Sending: case 0, Receiving, Sending:
return nil return nil
default: default:
return fmt.Errorf("invalid server mode '%d'", cp.serverMode) return fmt.Errorf("invalid server mode '%d'", cp.serverMode)

View File

@ -22,7 +22,7 @@ type ConnectionParamsSuite struct {
TestCertComponents TestCertComponents
TestLoggerComponents TestLoggerComponents
server *Server server *BaseServer
} }
func (s *ConnectionParamsSuite) SetupSuite() { func (s *ConnectionParamsSuite) SetupSuite() {
@ -37,7 +37,7 @@ func (s *ConnectionParamsSuite) SetupSuite() {
err = bs.SetPort(1337) err = bs.SetPort(1337)
s.Require().NoError(err) s.Require().NoError(err)
s.server = &Server{ s.server = &BaseServer{
Server: bs, Server: bs,
pk: &s.PK.PublicKey, pk: &s.PK.PublicKey,
ek: s.AES, ek: s.AES,

View File

@ -29,7 +29,7 @@ type Event struct {
type Action int type Action int
const ( const (
ActionConnect = iota + 1 ActionConnect Action = iota + 1
ActionPairingAccount ActionPairingAccount
ActionSyncDevice ActionSyncDevice
ActionPairingInstallation ActionPairingInstallation

View File

@ -9,18 +9,17 @@ import (
"github.com/btcsuite/btcutil/base58" "github.com/btcsuite/btcutil/base58"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
) )
const ( const (
// Handler routes for pairing // Handler routes for pairing
pairingBase = "/pairing" pairingBase = "/pairing"
pairingChallenge = pairingBase + "/challenge"
pairingSendAccount = pairingBase + "/sendAccount" pairingSendAccount = pairingBase + "/sendAccount"
pairingReceiveAccount = pairingBase + "/receiveAccount" pairingReceiveAccount = pairingBase + "/receiveAccount"
pairingChallenge = pairingBase + "/challenge" pairingSendSyncDevice = pairingBase + "/sendSyncDevice"
pairingSyncDeviceSend = pairingBase + "/sendSyncDevice" pairingReceiveSyncDevice = pairingBase + "/receiveSyncDevice"
pairingSyncDeviceReceive = pairingBase + "/receiveSyncDevice"
pairingSendInstallation = pairingBase + "/sendInstallation" pairingSendInstallation = pairingBase + "/sendInstallation"
pairingReceiveInstallation = pairingBase + "/receiveInstallation" pairingReceiveInstallation = pairingBase + "/receiveInstallation"
@ -29,145 +28,172 @@ const (
sessionBlocked = "blocked" sessionBlocked = "blocked"
) )
func handleReceiveAccount(ps *Server) http.HandlerFunc { // Account handling
func handleReceiveAccount(hs HandlerServer, pr PayloadReceiver) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
logger := ps.GetLogger() logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body) payload, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("handleReceiveAccount io.ReadAll(r.Body)", zap.Error(err)) logger.Error("handleReceiveAccount io.ReadAll(r.Body)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
err = ps.PayloadManager.Receive(payload) err = pr.Receive(payload)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("ps.PayloadManager.Receive(payload)", zap.Error(err)) logger.Error("handleReceiveAccount pr.Receive(payload)", zap.Error(err), zap.Binary("payload", payload))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingAccount}) signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingAccount})
} }
} }
func handleReceiveInstallation(ps *Server) http.HandlerFunc { func handleSendAccount(hs HandlerServer, pm PayloadMounter) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
logger := ps.GetLogger() logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body) w.Header().Set("Content-Type", "application/octet-stream")
err := pm.Mount()
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("handleReceiveInstallation io.ReadAll(r.Body)", zap.Error(err)) logger.Error("handleSendAccount pm.Mount()", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
err = ps.installationPayloadManager.Receive(payload) _, err = w.Write(pm.ToSend())
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("ps.installationPayloadManager.Receive(payload)", zap.Error(err)) logger.Error("handleSendAccount w.Write(pm.ToSend())", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingInstallation}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
pm.LockPayload()
} }
} }
func handleParingSyncDeviceReceive(ps *Server) http.HandlerFunc { // Device sync handling
func handleParingSyncDeviceReceive(hs HandlerServer, pr PayloadReceiver) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
logger := ps.GetLogger() logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
payload, err := io.ReadAll(r.Body) payload, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("handleParingSyncDeviceReceive io.ReadAll(r.Body)", zap.Error(err)) logger.Error("handleParingSyncDeviceReceive io.ReadAll(r.Body)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
err = ps.rawMessagePayloadManager.Receive(payload) err = pr.Receive(payload)
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("ps.rawMessagePayloadManager.Receive(payload)", zap.Error(err)) logger.Error("handleParingSyncDeviceReceive pr.Receive(payload)", zap.Error(err), zap.Binary("payload", payload))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionSyncDevice})
} }
} }
func handleSendAccount(ps *Server) http.HandlerFunc { func handlePairingSyncDeviceSend(hs HandlerServer, pm PayloadMounter) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingAccount})
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
_, err := w.Write(ps.PayloadManager.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingAccount})
logger.Error("w.Write(ps.PayloadManager.ToSend())", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingAccount})
ps.PayloadManager.LockPayload()
}
}
func handleSendInstallation(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
logger := ps.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
err := ps.installationPayloadManager.Mount()
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("ps.installationPayloadManager.Mount()", zap.Error(err))
return
}
_, err = w.Write(ps.installationPayloadManager.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("w.Write(ps.installationPayloadManager.ToSend())", zap.Error(err))
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
ps.installationPayloadManager.LockPayload()
}
}
func handlePairingSyncDeviceSend(ps *Server) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionSyncDevice})
logger := ps.GetLogger() logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
err := ps.rawMessagePayloadManager.Mount() err := pm.Mount()
if err != nil { if err != nil {
// maybe better to use a new event type here instead of EventTransferError? // maybe better to use a new event type here instead of EventTransferError?
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("ps.rawMessagePayloadManager.Mount()", zap.Error(err)) logger.Error("handlePairingSyncDeviceSend pm.Mount()", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
_, err = w.Write(ps.rawMessagePayloadManager.ToSend()) _, err = w.Write(pm.ToSend())
if err != nil { if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionSyncDevice})
logger.Error("w.Write(ps.rawMessagePayloadManager.ToSend())", zap.Error(err)) logger.Error("handlePairingSyncDeviceSend w.Write(pm.ToSend())", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice}) signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionSyncDevice})
ps.rawMessagePayloadManager.LockPayload() pm.LockPayload()
} }
} }
func challengeMiddleware(ps *Server, next http.Handler) http.HandlerFunc { // Installation data handling
logger := ps.GetLogger()
func handleReceiveInstallation(hs HandlerServer, pmr PayloadMounterReceiver) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge) payload, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err)) signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("handleReceiveInstallation io.ReadAll(r.Body)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
err = pmr.Receive(payload)
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventProcessError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("handleReceiveInstallation pmr.Receive(payload)", zap.Error(err), zap.Binary("payload", payload))
http.Error(w, "error", http.StatusInternalServerError)
return
}
signal.SendLocalPairingEvent(Event{Type: EventProcessSuccess, Action: ActionPairingInstallation})
}
}
func handleSendInstallation(hs HandlerServer, pmr PayloadMounterReceiver) http.HandlerFunc {
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionPairingInstallation})
logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
err := pmr.Mount()
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("handleSendInstallation pmr.Mount()", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
_, err = w.Write(pmr.ToSend())
if err != nil {
signal.SendLocalPairingEvent(Event{Type: EventTransferError, Error: err.Error(), Action: ActionPairingInstallation})
logger.Error("handleSendInstallation w.Write(pmr.ToSend())", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return
}
signal.SendLocalPairingEvent(Event{Type: EventTransferSuccess, Action: ActionPairingInstallation})
pmr.LockPayload()
}
}
// Challenge middleware and handling
func middlewareChallenge(hs HandlerServer, next http.Handler) http.HandlerFunc {
logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) {
s, err := hs.GetCookieStore().Get(r, sessionChallenge)
if err != nil {
logger.Error("middlewareChallenge: hs.GetCookieStore().Get(r, sessionChallenge)", zap.Error(err), zap.String("sessionChallenge", sessionChallenge))
http.Error(w, "error", http.StatusInternalServerError) http.Error(w, "error", http.StatusInternalServerError)
return return
} }
@ -185,9 +211,9 @@ func challengeMiddleware(ps *Server, next http.Handler) http.HandlerFunc {
return return
} }
c, err := common.Decrypt(base58.Decode(pc), ps.ek) c, err := hs.DecryptPlain(base58.Decode(pc))
if err != nil { if err != nil {
logger.Error("c, err := common.Decrypt(rc, ps.ek)", zap.Error(err)) logger.Error("middlewareChallenge: c, err := hs.DecryptPlain(base58.Decode(pc))", zap.Error(err), zap.String("pc", pc))
http.Error(w, "error", http.StatusInternalServerError) http.Error(w, "error", http.StatusInternalServerError)
return return
} }
@ -205,7 +231,7 @@ func challengeMiddleware(ps *Server, next http.Handler) http.HandlerFunc {
s.Values[sessionBlocked] = true s.Values[sessionBlocked] = true
err = s.Save(r, w) err = s.Save(r, w)
if err != nil { if err != nil {
logger.Error("err = s.Save(r, w)", zap.Error(err)) logger.Error("middlewareChallenge: err = s.Save(r, w)", zap.Error(err))
} }
http.Error(w, "forbidden", http.StatusForbidden) http.Error(w, "forbidden", http.StatusForbidden)
@ -216,12 +242,13 @@ func challengeMiddleware(ps *Server, next http.Handler) http.HandlerFunc {
} }
} }
func handlePairingChallenge(ps *Server) http.HandlerFunc { func handlePairingChallenge(hs HandlerServer) http.HandlerFunc {
logger := ps.GetLogger() logger := hs.GetLogger()
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
s, err := ps.cookieStore.Get(r, sessionChallenge) s, err := hs.GetCookieStore().Get(r, sessionChallenge)
if err != nil { if err != nil {
logger.Error("ps.cookieStore.Get(r, pairingStoreChallenge)", zap.Error(err)) logger.Error("handlePairingChallenge: hs.GetCookieStore().Get(r, sessionChallenge)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
@ -231,14 +258,16 @@ func handlePairingChallenge(ps *Server) http.HandlerFunc {
challenge = make([]byte, 64) challenge = make([]byte, 64)
_, err = rand.Read(challenge) _, err = rand.Read(challenge)
if err != nil { if err != nil {
logger.Error("_, err = rand.Read(auth)", zap.Error(err)) logger.Error("handlePairingChallenge: _, err = rand.Read(challenge)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
s.Values[sessionChallenge] = challenge s.Values[sessionChallenge] = challenge
err = s.Save(r, w) err = s.Save(r, w)
if err != nil { if err != nil {
logger.Error("err = s.Save(r, w)", zap.Error(err)) logger.Error("handlePairingChallenge: err = s.Save(r, w)", zap.Error(err))
http.Error(w, "error", http.StatusInternalServerError)
return return
} }
} }
@ -246,7 +275,7 @@ func handlePairingChallenge(ps *Server) http.HandlerFunc {
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
_, err = w.Write(challenge) _, err = w.Write(challenge)
if err != nil { if err != nil {
logger.Error("_, err = w.Write(challenge)", zap.Error(err)) logger.Error("handlePairingChallenge: _, err = w.Write(challenge)", zap.Error(err))
return return
} }
} }

View File

@ -0,0 +1,41 @@
package pairing
import (
"github.com/gorilla/sessions"
"go.uber.org/zap"
)
// PayloadMounterReceiver represents a struct that can:
// - mount payload data from a PayloadRepository or a PayloadLoader into memory (PayloadMounter.Mount)
// - prepare data to be sent encrypted (PayloadMounter.ToSend) via some transport
// - receive and prepare encrypted transport data (PayloadReceiver.Receive) to be stored
// - prepare the received (PayloadReceiver.Received) data to be stored to a PayloadRepository or a PayloadStorer
type PayloadMounterReceiver interface {
PayloadMounter
PayloadReceiver
}
// PayloadRepository represents a struct that can both load and store data to an internally managed data store
type PayloadRepository interface {
PayloadLoader
PayloadStorer
}
type PayloadLocker interface {
// LockPayload prevents future excess to outbound safe and received data
LockPayload()
}
type HandlerServer interface {
GetLogger() *zap.Logger
GetCookieStore() *sessions.CookieStore
DecryptPlain([]byte) ([]byte, error)
}
type ProtobufMarshaler interface {
MarshalProtobuf() ([]byte, error)
}
type ProtobufUnmarshaler interface {
UnmarshalProtobuf([]byte) error
}

View File

@ -0,0 +1,94 @@
package pairing
import (
"crypto/rand"
"github.com/status-im/status-go/protocol/common"
)
// EncryptionPayload represents the plain text and encrypted text of payload data
type EncryptionPayload struct {
plain []byte
encrypted []byte
locked bool
}
func (ep *EncryptionPayload) lock() {
ep.locked = true
}
// TODO resolve the many cases of other structs simply wrapping their encryptor rather than embedding the functionality
// https://github.com/status-im/status-go/issues/3302
// PayloadEncryptor is responsible for encrypting and decrypting payload data
type PayloadEncryptor struct {
aesKey []byte
payload *EncryptionPayload
}
func NewPayloadEncryptor(aesKey []byte) *PayloadEncryptor {
return &PayloadEncryptor{
aesKey,
new(EncryptionPayload),
}
}
// Renew regenerates the whole PayloadEncryptor and returns the new instance, only the aesKey is preserved
func (pem *PayloadEncryptor) Renew() *PayloadEncryptor {
return &PayloadEncryptor{
aesKey: pem.aesKey,
payload: new(EncryptionPayload),
}
}
// encryptPlain encrypts any given plain text using the internal AES key and returns the encrypted value
// This function is different to Encrypt as the internal EncryptionPayload.encrypted value is not set
func (pem *PayloadEncryptor) encryptPlain(plaintext []byte) ([]byte, error) {
return common.Encrypt(plaintext, pem.aesKey, rand.Reader)
}
// decryptPlain decrypts any given plain text using the internal AES key and returns the encrypted value
// This function is different to Decrypt as the internal EncryptionPayload.plain value is not set
func (pem *PayloadEncryptor) decryptPlain(plaintext []byte) ([]byte, error) {
return common.Decrypt(plaintext, pem.aesKey)
}
func (pem *PayloadEncryptor) encrypt(data []byte) error {
ep, err := common.Encrypt(data, pem.aesKey, rand.Reader)
if err != nil {
return err
}
pem.payload.plain = data
pem.payload.encrypted = ep
return nil
}
func (pem *PayloadEncryptor) decrypt(data []byte) error {
pd, err := common.Decrypt(data, pem.aesKey)
if err != nil {
return err
}
pem.payload.encrypted = data
pem.payload.plain = pd
return nil
}
func (pem *PayloadEncryptor) getEncrypted() []byte {
if pem.payload.locked {
return nil
}
return pem.payload.encrypted
}
func (pem *PayloadEncryptor) getDecrypted() []byte {
if pem.payload.locked {
return nil
}
return pem.payload.plain
}
func (pem *PayloadEncryptor) lockPayload() {
pem.payload.lock()
}

View File

@ -0,0 +1,123 @@
package pairing
import (
"errors"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/protocol/protobuf"
)
const keystoreDir = "keystore"
var (
// TODO add validation on config to ensure required fields have valid values
// https://github.com/status-im/status-go/issues/3303
ErrKeyFileAlreadyExists = errors.New("key file already exists")
ErrKeyUIDEmptyAsSender = errors.New("keyUID must be provided as sender")
ErrNodeConfigNilAsReceiver = errors.New("node config must be provided as receiver")
ErrLoggedInKeyUIDConflict = errors.New("logged in keyUID not same as keyUID in payload")
)
// AccountPayload represents the payload structure a Server handles
type AccountPayload struct {
keys map[string][]byte
multiaccount *multiaccounts.Account
password string
//flag if account already exist before sync account
exist bool
}
// AccountPayloadMarshaller is responsible for marshalling and unmarshalling Server payload data
type AccountPayloadMarshaller struct {
logger *zap.Logger
*AccountPayload
}
func NewPairingPayloadMarshaller(ap *AccountPayload, logger *zap.Logger) *AccountPayloadMarshaller {
return &AccountPayloadMarshaller{logger: logger, AccountPayload: ap}
}
func (ppm *AccountPayloadMarshaller) MarshalProtobuf() ([]byte, error) {
return proto.Marshal(&protobuf.LocalPairingPayload{
Keys: ppm.accountKeysToProtobuf(),
Multiaccount: ppm.multiaccount.ToProtobuf(),
Password: ppm.password,
})
}
func (ppm *AccountPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPairingPayload_Key {
var keys []*protobuf.LocalPairingPayload_Key
for name, data := range ppm.keys {
keys = append(keys, &protobuf.LocalPairingPayload_Key{Name: name, Data: data})
}
return keys
}
func (ppm *AccountPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
l := ppm.logger.Named("UnmarshalProtobuf()")
l.Debug("fired")
pb := new(protobuf.LocalPairingPayload)
err := proto.Unmarshal(data, pb)
l.Debug(
"after protobuf.LocalPairingPayload",
zap.Any("pb", pb),
zap.Any("pb.Multiaccount", pb.Multiaccount),
zap.Any("pb.Keys", pb.Keys),
)
if err != nil {
return err
}
ppm.accountKeysFromProtobuf(pb.Keys)
ppm.multiaccountFromProtobuf(pb.Multiaccount)
ppm.password = pb.Password
return nil
}
func (ppm *AccountPayloadMarshaller) accountKeysFromProtobuf(pbKeys []*protobuf.LocalPairingPayload_Key) {
l := ppm.logger.Named("accountKeysFromProtobuf()")
l.Debug("fired")
if ppm.keys == nil {
ppm.keys = make(map[string][]byte)
}
for _, key := range pbKeys {
ppm.keys[key.Name] = key.Data
}
l.Debug(
"after for _, key := range pbKeys",
zap.Any("pbKeys", pbKeys),
zap.Any("accountPayloadMarshaller.keys", ppm.keys),
)
}
func (ppm *AccountPayloadMarshaller) multiaccountFromProtobuf(pbMultiAccount *protobuf.MultiAccount) {
ppm.multiaccount = new(multiaccounts.Account)
ppm.multiaccount.FromProtobuf(pbMultiAccount)
}
// InstallationPayloadMounterReceiver represents an InstallationPayload Repository
type InstallationPayloadMounterReceiver struct {
*InstallationPayloadMounter
*InstallationPayloadReceiver
}
func NewInstallationPayloadMounterReceiver(logger *zap.Logger, encryptor *PayloadEncryptor, backend *api.GethStatusBackend, deviceType string) *InstallationPayloadMounterReceiver {
l := logger.Named("InstallationPayloadMounterReceiver")
return &InstallationPayloadMounterReceiver{
NewInstallationPayloadMounter(l, encryptor, backend, deviceType),
NewInstallationPayloadReceiver(l, encryptor, backend, deviceType),
}
}
func (i *InstallationPayloadMounterReceiver) LockPayload() {
i.InstallationPayloadMounter.LockPayload()
i.InstallationPayloadReceiver.LockPayload()
}

View File

@ -44,8 +44,8 @@ type PayloadMarshallerSuite struct {
teardown func() teardown func()
config1 *AccountPayloadManagerConfig config1 *SenderConfig
config2 *AccountPayloadManagerConfig config2 *ReceiverConfig
} }
func setupTestDB(t *testing.T) (*multiaccounts.Database, func()) { func setupTestDB(t *testing.T) (*multiaccounts.Database, func()) {
@ -68,9 +68,9 @@ func makeKeystores(t *testing.T) (string, string, func()) {
emptyKeyStoreDir, err := os.MkdirTemp(os.TempDir(), "accounts_empty") emptyKeyStoreDir, err := os.MkdirTemp(os.TempDir(), "accounts_empty")
require.NoError(t, err) require.NoError(t, err)
keyStoreDir = filepath.Join(keyStoreDir, "keystore", keyUID) keyStoreDir = filepath.Join(keyStoreDir, keystoreDir, keyUID)
// TODO test case where the keystore dir does not yet exist because the device is new // TODO test case where the keystore dir does not yet exist because the device is new
emptyKeyStoreDir = filepath.Join(emptyKeyStoreDir, "keystore") emptyKeyStoreDir = filepath.Join(emptyKeyStoreDir, keystoreDir)
err = os.MkdirAll(keyStoreDir, 0777) err = os.MkdirAll(keyStoreDir, 0777)
require.NoError(t, err) require.NoError(t, err)
@ -132,26 +132,16 @@ func (pms *PayloadMarshallerSuite) SetupTest() {
err := db1.SaveAccount(expected) err := db1.SaveAccount(expected)
pms.Require().NoError(err) pms.Require().NoError(err)
pms.config1 = &AccountPayloadManagerConfig{ pms.config1 = &SenderConfig{
DB: db1, DB: db1,
PayloadSourceConfig: &PayloadSourceConfig{ KeystorePath: keystore1,
KeystorePath: keystore1, KeyUID: keyUID,
PayloadSourceSenderConfig: &PayloadSourceSenderConfig{ Password: password,
KeyUID: keyUID,
Password: password,
},
},
} }
pms.config2 = &AccountPayloadManagerConfig{ pms.config2 = &ReceiverConfig{
DB: db2, DB: db2,
PayloadSourceConfig: &PayloadSourceConfig{ KeystorePath: keystore2,
KeystorePath: keystore2,
PayloadSourceSenderConfig: &PayloadSourceSenderConfig{
KeyUID: keyUID,
Password: password,
},
},
} }
} }
@ -163,13 +153,13 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_LoadPayloads() {
// Make a Payload // Make a Payload
pp := new(AccountPayload) pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1 // Make and Load() PairingPayloadRepository 1
ppr, err := NewAccountPayloadRepository(pp, pms.config1) ppr, err := NewAccountPayloadLoader(pp, pms.config1)
pms.Require().NoError(err) pms.Require().NoError(err)
err = ppr.LoadFromSource() err = ppr.Load()
pms.Require().NoError(err) pms.Require().NoError(err)
// TEST PairingPayloadRepository 1 LoadFromSource() // TEST PairingPayloadRepository 1 Load()
pms.Require().Len(ppr.keys, 2) pms.Require().Len(ppr.keys, 2)
pms.Require().Len(ppr.keys[utils.GetAccount1PKFile()], 489) pms.Require().Len(ppr.keys[utils.GetAccount1PKFile()], 489)
pms.Require().Len(ppr.keys[utils.GetAccount2PKFile()], 489) pms.Require().Len(ppr.keys[utils.GetAccount2PKFile()], 489)
@ -197,17 +187,17 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_MarshalToProtobuf() {
// Make a Payload // Make a Payload
pp := new(AccountPayload) pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1 // Make and Load() PairingPayloadRepository 1
ppr, err := NewAccountPayloadRepository(pp, pms.config1) ppr, err := NewAccountPayloadLoader(pp, pms.config1)
pms.Require().NoError(err) pms.Require().NoError(err)
err = ppr.LoadFromSource() err = ppr.Load()
pms.Require().NoError(err) pms.Require().NoError(err)
// Make and Load PairingPayloadMarshaller 1 // Make and Load() PairingPayloadMarshaller 1
ppm := NewPairingPayloadMarshaller(pp, pms.Logger) ppm := NewPairingPayloadMarshaller(pp, pms.Logger)
// TEST PairingPayloadMarshaller 1 MarshalToProtobuf() // TEST PairingPayloadMarshaller 1 MarshalProtobuf()
pb, err := ppm.MarshalToProtobuf() pb, err := ppm.MarshalProtobuf()
pms.Require().NoError(err) pms.Require().NoError(err)
pms.Require().Len(pb, 1384) pms.Require().Len(pb, 1384)
@ -227,16 +217,16 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_UnmarshalProtobuf() {
// Make a Payload // Make a Payload
pp := new(AccountPayload) pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1 // Make and Load() PairingPayloadRepository 1
ppr, err := NewAccountPayloadRepository(pp, pms.config1) ppr, err := NewAccountPayloadLoader(pp, pms.config1)
pms.Require().NoError(err) pms.Require().NoError(err)
err = ppr.LoadFromSource() err = ppr.Load()
pms.Require().NoError(err) pms.Require().NoError(err)
// Make and Load PairingPayloadMarshaller 1 // Make and Load() PairingPayloadMarshaller 1
ppm := NewPairingPayloadMarshaller(pp, pms.Logger) ppm := NewPairingPayloadMarshaller(pp, pms.Logger)
pb, err := ppm.MarshalToProtobuf() pb, err := ppm.MarshalProtobuf()
pms.Require().NoError(err) pms.Require().NoError(err)
// Make a Payload // Make a Payload
@ -281,16 +271,16 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_StorePayloads() {
// Make a Payload // Make a Payload
pp := new(AccountPayload) pp := new(AccountPayload)
// Make and LoadFromSource PairingPayloadRepository 1 // Make and Load() PairingPayloadRepository 1
ppr, err := NewAccountPayloadRepository(pp, pms.config1) ppr, err := NewAccountPayloadLoader(pp, pms.config1)
pms.Require().NoError(err) pms.Require().NoError(err)
err = ppr.LoadFromSource() err = ppr.Load()
pms.Require().NoError(err) pms.Require().NoError(err)
// Make and Load PairingPayloadMarshaller 1 // Make and Load() PairingPayloadMarshaller 1
ppm := NewPairingPayloadMarshaller(pp, pms.Logger) ppm := NewPairingPayloadMarshaller(pp, pms.Logger)
pb, err := ppm.MarshalToProtobuf() pb, err := ppm.MarshalProtobuf()
pms.Require().NoError(err) pms.Require().NoError(err)
// Make a Payload // Make a Payload
@ -302,14 +292,14 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_StorePayloads() {
err = ppm2.UnmarshalProtobuf(pb) err = ppm2.UnmarshalProtobuf(pb)
pms.Require().NoError(err) pms.Require().NoError(err)
// Make and Load PairingPayloadRepository 2 // Make and Load() PairingPayloadRepository 2
ppr2, err := NewAccountPayloadRepository(pp2, pms.config2) ppr2, err := NewAccountPayloadStorer(pp2, pms.config2)
require.NoError(pms.T(), err) require.NoError(pms.T(), err)
err = ppr2.StoreToSource() err = ppr2.Store()
pms.Require().NoError(err) pms.Require().NoError(err)
// TEST PairingPayloadRepository 2 StoreToSource() // TEST PairingPayloadRepository 2 Store()
keys := getFiles(pms.T(), filepath.Join(pms.config2.KeystorePath, pms.config2.KeyUID)) keys := getFiles(pms.T(), filepath.Join(pms.config2.KeystorePath, keyUID))
pms.Require().Len(keys, 2) pms.Require().Len(keys, 2)
pms.Require().Len(keys[utils.GetAccount1PKFile()], 489) pms.Require().Len(keys[utils.GetAccount1PKFile()], 489)
@ -344,8 +334,7 @@ func (pms *PayloadMarshallerSuite) TestPayloadMarshaller_LockPayload() {
_, err := rand.Read(AESKey) _, err := rand.Read(AESKey)
pms.Require().NoError(err) pms.Require().NoError(err)
pm, err := NewMockEncryptOnlyPayloadManager(AESKey) pm := NewMockPayloadMounter(AESKey)
pms.Require().NoError(err)
err = pm.Mount() err = pm.Mount()
pms.Require().NoError(err) pms.Require().NoError(err)

View File

@ -1,835 +0,0 @@
package pairing
import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"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/multiaccounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/signal"
)
var (
ErrKeyFileAlreadyExists = errors.New("key file already exists")
ErrKeyUIDEmptyAsSender = errors.New("keyUID must be provided as sender")
ErrNodeConfigNilAsReceiver = errors.New("node config must be provided as receiver")
ErrPayloadSourceConfigBothSet = errors.New("payloadSourceSenderConfig and payloadSourceReceiverConfig cannot be both set")
ErrLoggedInKeyUIDConflict = errors.New("logged in keyUID not same as keyUID in payload")
)
// PayloadManager is the interface for PayloadManagers and wraps the basic functions for fulfilling payload management
type PayloadManager interface {
// Mount Loads the payload into the PayloadManager's state
Mount() error
// Receive stores data from an inbound source into the PayloadManager's state
Receive(data []byte) error
// ToSend returns an outbound safe (encrypted) payload
ToSend() []byte
// Received returns a decrypted and parsed payload from an inbound source
Received() []byte
// ResetPayload resets all payloads the PayloadManager has in its state
ResetPayload()
// EncryptPlain encrypts the given plaintext using internal key(s)
EncryptPlain(plaintext []byte) ([]byte, error)
// LockPayload prevents future excess to outbound safe and received data
LockPayload()
}
type PayloadSourceSenderConfig struct {
KeyUID string `json:"keyUID"`
Password string `json:"password"`
}
type PayloadSourceReceiverConfig struct {
KDFIterations int `json:"kdfIterations"`
NodeConfig *params.NodeConfig
// this field already exists within params.NodeConfig, but it doesn't support json marshalling, so we need to duplicate it here
RootDataDir string
// corresponding to field current_network from table settings, so that we can override current network from sender
SettingCurrentNetwork string
}
// PayloadSourceConfig represents location and access data of the pairing payload
// ONLY available from the application client
type PayloadSourceConfig struct {
// required for sender and receiver, there are some different cases:
// 1. for sender, KeystorePath must end with keyUID
// 2. for receiver, KeystorePath must not end with keyUID (because keyUID is not known yet)
KeystorePath string `json:"keystorePath"`
// required for sender and receiver, SendPairInstallation need this information
DeviceType string `json:"deviceType"`
*PayloadSourceSenderConfig
*PayloadSourceReceiverConfig
// Timeout the number of milliseconds after which the pairing server will automatically terminate
Timeout uint `json:"timeout"`
}
type payloadSourceUnmarshalCallback func(conf *PayloadSourceConfig) (*PayloadSourceConfig, error)
func NewPayloadSourceForClient(configJSON string, mode Mode) (*PayloadSourceConfig, error) {
return unmarshalPayloadSourceConfig(configJSON, func(conf *PayloadSourceConfig) (*PayloadSourceConfig, error) {
if mode == Sending && conf.NodeConfig == nil {
return nil, ErrNodeConfigNilAsReceiver
}
if mode == Receiving && conf.KeyUID == "" {
return nil, ErrKeyUIDEmptyAsSender
}
return updateRootDataDirToNodeConfig(conf)
})
}
func NewPayloadSourceForServer(configJSON string, mode Mode) (*PayloadSourceConfig, error) {
return unmarshalPayloadSourceConfig(configJSON, func(conf *PayloadSourceConfig) (*PayloadSourceConfig, error) {
if mode == Sending && conf.KeyUID == "" {
return nil, ErrKeyUIDEmptyAsSender
}
if mode == Receiving && conf.NodeConfig == nil {
return nil, ErrNodeConfigNilAsReceiver
}
return updateRootDataDirToNodeConfig(conf)
})
}
func updateRootDataDirToNodeConfig(conf *PayloadSourceConfig) (*PayloadSourceConfig, error) {
if conf.PayloadSourceReceiverConfig != nil && conf.PayloadSourceReceiverConfig.NodeConfig != nil {
conf.NodeConfig.RootDataDir = conf.RootDataDir
}
return conf, nil
}
func unmarshalPayloadSourceConfig(configJSON string, successCallback payloadSourceUnmarshalCallback) (*PayloadSourceConfig, error) {
var conf = PayloadSourceConfig{}
err := json.Unmarshal([]byte(configJSON), &conf)
if err != nil {
return nil, err
}
return successCallback(&conf)
}
// AccountPayloadManagerConfig represents the initialisation parameters required for a AccountPayloadManager
type AccountPayloadManagerConfig struct {
DB *multiaccounts.Database
*PayloadSourceConfig
// only used for the receiver side
LoggedInKeyUID string
}
func (a *AccountPayloadManagerConfig) GetNodeConfig() *params.NodeConfig {
if a.PayloadSourceConfig != nil && a.PayloadSourceConfig.PayloadSourceReceiverConfig != nil {
return a.NodeConfig
}
return nil
}
func (a *AccountPayloadManagerConfig) GetSettingCurrentNetwork() string {
if a.PayloadSourceConfig != nil && a.PayloadSourceConfig.PayloadSourceReceiverConfig != nil {
return a.SettingCurrentNetwork
}
return ""
}
func (a *AccountPayloadManagerConfig) GetDeviceType() string {
if a.PayloadSourceConfig != nil {
return a.DeviceType
}
return ""
}
func (a *AccountPayloadManagerConfig) GetPayloadSourceSenderConfig() *PayloadSourceSenderConfig {
if a.PayloadSourceConfig != nil && a.PayloadSourceConfig.PayloadSourceSenderConfig != nil {
return a.PayloadSourceSenderConfig
}
return nil
}
func (a *AccountPayloadManagerConfig) GetPayloadSourceReceiverConfig() *PayloadSourceReceiverConfig {
if a.PayloadSourceConfig != nil && a.PayloadSourceConfig.PayloadSourceReceiverConfig != nil {
return a.PayloadSourceReceiverConfig
}
return nil
}
func (a *AccountPayloadManagerConfig) GetKeystorePath() string {
if a.PayloadSourceConfig != nil {
return a.KeystorePath
}
return ""
}
func (a *AccountPayloadManagerConfig) GetTimeout() uint {
if a.PayloadSourceConfig != nil {
return a.Timeout
}
return 0
}
// AccountPayloadManager is responsible for the whole lifecycle of a AccountPayload
type AccountPayloadManager struct {
logger *zap.Logger
accountPayload *AccountPayload
*PayloadEncryptionManager
accountPayloadMarshaller *AccountPayloadMarshaller
payloadRepository PayloadRepository
}
// NewAccountPayloadManager generates a new and initialised AccountPayloadManager
func NewAccountPayloadManager(aesKey []byte, config *AccountPayloadManagerConfig, logger *zap.Logger) (*AccountPayloadManager, error) {
l := logger.Named("AccountPayloadManager")
l.Debug("fired", zap.Binary("aesKey", aesKey), zap.Any("config", config))
pem, err := NewPayloadEncryptionManager(aesKey, l)
if err != nil {
return nil, err
}
// A new SHARED AccountPayload
p := new(AccountPayload)
accountPayloadRepository, err := NewAccountPayloadRepository(p, config)
if err != nil {
return nil, err
}
return &AccountPayloadManager{
logger: l,
accountPayload: p,
PayloadEncryptionManager: pem,
accountPayloadMarshaller: NewPairingPayloadMarshaller(p, l),
payloadRepository: accountPayloadRepository,
}, nil
}
// Mount loads and prepares the payload to be stored in the AccountPayloadManager's state ready for later access
func (apm *AccountPayloadManager) Mount() error {
l := apm.logger.Named("Mount()")
l.Debug("fired")
err := apm.payloadRepository.LoadFromSource()
if err != nil {
return err
}
l.Debug("after LoadFromSource")
pb, err := apm.accountPayloadMarshaller.MarshalToProtobuf()
if err != nil {
return err
}
l.Debug(
"after MarshalToProtobuf",
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.keys", apm.accountPayloadMarshaller.keys),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.multiaccount", apm.accountPayloadMarshaller.multiaccount),
zap.String("accountPayloadMarshaller.accountPayloadMarshaller.password", apm.accountPayloadMarshaller.password),
zap.Binary("pb", pb),
)
return apm.Encrypt(pb)
}
// Receive takes a []byte representing raw data, parses and stores the data
func (apm *AccountPayloadManager) Receive(data []byte) error {
l := apm.logger.Named("Receive()")
l.Debug("fired")
err := apm.Decrypt(data)
if err != nil {
return err
}
l.Debug("after Decrypt")
err = apm.accountPayloadMarshaller.UnmarshalProtobuf(apm.Received())
if err != nil {
return err
}
l.Debug(
"after UnmarshalProtobuf",
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.keys", apm.accountPayloadMarshaller.keys),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.multiaccount", apm.accountPayloadMarshaller.multiaccount),
zap.String("accountPayloadMarshaller.accountPayloadMarshaller.password", apm.accountPayloadMarshaller.password),
zap.Binary("accountPayloadMarshaller.Received()", apm.Received()),
)
signal.SendLocalPairingEvent(Event{Type: EventReceivedAccount, Action: ActionPairingAccount, Data: apm.accountPayload.multiaccount})
return apm.payloadRepository.StoreToSource()
}
// ResetPayload resets all payload state managed by the AccountPayloadManager
func (apm *AccountPayloadManager) ResetPayload() {
apm.accountPayload.ResetPayload()
apm.PayloadEncryptionManager.ResetPayload()
}
// EncryptionPayload represents the plain text and encrypted text of payload data
type EncryptionPayload struct {
plain []byte
encrypted []byte
locked bool
}
func (ep *EncryptionPayload) lock() {
ep.locked = true
}
// PayloadEncryptionManager is responsible for encrypting and decrypting payload data
type PayloadEncryptionManager struct {
logger *zap.Logger
aesKey []byte
toSend *EncryptionPayload
received *EncryptionPayload
}
func NewPayloadEncryptionManager(aesKey []byte, logger *zap.Logger) (*PayloadEncryptionManager, error) {
return &PayloadEncryptionManager{logger.Named("PayloadEncryptionManager"), aesKey, new(EncryptionPayload), new(EncryptionPayload)}, nil
}
// EncryptPlain encrypts any given plain text using the internal AES key and returns the encrypted value
// This function is different to Encrypt as the internal EncryptionPayload.encrypted value is not set
func (pem *PayloadEncryptionManager) EncryptPlain(plaintext []byte) ([]byte, error) {
l := pem.logger.Named("EncryptPlain()")
l.Debug("fired")
return common.Encrypt(plaintext, pem.aesKey, rand.Reader)
}
func (pem *PayloadEncryptionManager) Encrypt(data []byte) error {
l := pem.logger.Named("Encrypt()")
l.Debug("fired")
ep, err := common.Encrypt(data, pem.aesKey, rand.Reader)
if err != nil {
return err
}
pem.toSend.plain = data
pem.toSend.encrypted = ep
l.Debug(
"after common.Encrypt",
zap.Binary("data", data),
zap.Binary("pem.aesKey", pem.aesKey),
zap.Binary("ep", ep),
)
return nil
}
func (pem *PayloadEncryptionManager) Decrypt(data []byte) error {
l := pem.logger.Named("Decrypt()")
l.Debug("fired")
pd, err := common.Decrypt(data, pem.aesKey)
l.Debug(
"after common.Decrypt(data, pem.aesKey)",
zap.Binary("data", data),
zap.Binary("pem.aesKey", pem.aesKey),
zap.Binary("pd", pd),
zap.Error(err),
)
if err != nil {
return err
}
pem.received.encrypted = data
pem.received.plain = pd
return nil
}
func (pem *PayloadEncryptionManager) ToSend() []byte {
if pem.toSend.locked {
return nil
}
return pem.toSend.encrypted
}
func (pem *PayloadEncryptionManager) Received() []byte {
if pem.toSend.locked {
return nil
}
return pem.received.plain
}
func (pem *PayloadEncryptionManager) ResetPayload() {
pem.toSend = new(EncryptionPayload)
pem.received = new(EncryptionPayload)
}
func (pem *PayloadEncryptionManager) LockPayload() {
l := pem.logger.Named("LockPayload")
l.Debug("fired")
pem.toSend.lock()
pem.received.lock()
}
// AccountPayload represents the payload structure a Server handles
type AccountPayload struct {
keys map[string][]byte
multiaccount *multiaccounts.Account
password string
//flag if account already exist before sync account
exist bool
}
func (ap *AccountPayload) ResetPayload() {
*ap = AccountPayload{}
}
// AccountPayloadMarshaller is responsible for marshalling and unmarshalling Server payload data
type AccountPayloadMarshaller struct {
logger *zap.Logger
*AccountPayload
}
func NewPairingPayloadMarshaller(ap *AccountPayload, logger *zap.Logger) *AccountPayloadMarshaller {
return &AccountPayloadMarshaller{logger: logger, AccountPayload: ap}
}
func (ppm *AccountPayloadMarshaller) MarshalToProtobuf() ([]byte, error) {
return proto.Marshal(&protobuf.LocalPairingPayload{
Keys: ppm.accountKeysToProtobuf(),
Multiaccount: ppm.multiaccount.ToProtobuf(),
Password: ppm.password,
})
}
func (ppm *AccountPayloadMarshaller) accountKeysToProtobuf() []*protobuf.LocalPairingPayload_Key {
var keys []*protobuf.LocalPairingPayload_Key
for name, data := range ppm.keys {
keys = append(keys, &protobuf.LocalPairingPayload_Key{Name: name, Data: data})
}
return keys
}
func (ppm *AccountPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
l := ppm.logger.Named("UnmarshalProtobuf()")
l.Debug("fired")
pb := new(protobuf.LocalPairingPayload)
err := proto.Unmarshal(data, pb)
l.Debug(
"after protobuf.LocalPairingPayload",
zap.Any("pb", pb),
zap.Any("pb.Multiaccount", pb.Multiaccount),
zap.Any("pb.Keys", pb.Keys),
)
if err != nil {
return err
}
ppm.accountKeysFromProtobuf(pb.Keys)
ppm.multiaccountFromProtobuf(pb.Multiaccount)
ppm.password = pb.Password
return nil
}
func (ppm *AccountPayloadMarshaller) accountKeysFromProtobuf(pbKeys []*protobuf.LocalPairingPayload_Key) {
l := ppm.logger.Named("accountKeysFromProtobuf()")
l.Debug("fired")
if ppm.keys == nil {
ppm.keys = make(map[string][]byte)
}
for _, key := range pbKeys {
ppm.keys[key.Name] = key.Data
}
l.Debug(
"after for _, key := range pbKeys",
zap.Any("pbKeys", pbKeys),
zap.Any("accountPayloadMarshaller.keys", ppm.keys),
)
}
func (ppm *AccountPayloadMarshaller) multiaccountFromProtobuf(pbMultiAccount *protobuf.MultiAccount) {
ppm.multiaccount = new(multiaccounts.Account)
ppm.multiaccount.FromProtobuf(pbMultiAccount)
}
type PayloadRepository interface {
LoadFromSource() error
StoreToSource() error
}
// AccountPayloadRepository is responsible for loading, parsing, validating and storing Server payload data
type AccountPayloadRepository struct {
*AccountPayload
multiaccountsDB *multiaccounts.Database
keystorePath, keyUID string
kdfIterations int
loggedInKeyUID string
}
func NewAccountPayloadRepository(p *AccountPayload, config *AccountPayloadManagerConfig) (*AccountPayloadRepository, error) {
ppr := &AccountPayloadRepository{
AccountPayload: p,
}
if config == nil {
return ppr, nil
}
ppr.multiaccountsDB = config.DB
if config.GetPayloadSourceSenderConfig() != nil && config.GetPayloadSourceReceiverConfig() != nil {
return nil, ErrPayloadSourceConfigBothSet
}
if config.GetPayloadSourceSenderConfig() != nil {
ppr.keyUID = config.KeyUID
ppr.password = config.Password
} else if config.GetPayloadSourceReceiverConfig() != nil {
ppr.kdfIterations = config.KDFIterations
}
ppr.keystorePath = config.GetKeystorePath()
ppr.loggedInKeyUID = config.LoggedInKeyUID
return ppr, nil
}
func (apr *AccountPayloadRepository) LoadFromSource() error {
err := apr.loadKeys(apr.keystorePath)
if err != nil {
return err
}
err = apr.validateKeys(apr.password)
if err != nil {
return err
}
apr.multiaccount, err = apr.multiaccountsDB.GetAccount(apr.keyUID)
if err != nil {
return err
}
return nil
}
func (apr *AccountPayloadRepository) loadKeys(keyStorePath string) error {
apr.keys = make(map[string][]byte)
fileWalker := func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}
if fileInfo.IsDir() || filepath.Dir(path) != keyStorePath {
return nil
}
rawKeyFile, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("invalid account key file: %v", err)
}
accountKey := new(keystore.EncryptedKeyJSONV3)
if err := json.Unmarshal(rawKeyFile, &accountKey); err != nil {
return fmt.Errorf("failed to read key file: %s", err)
}
if len(accountKey.Address) != 40 {
return fmt.Errorf("account key address has invalid length '%s'", accountKey.Address)
}
apr.keys[fileInfo.Name()] = rawKeyFile
return nil
}
err := filepath.Walk(keyStorePath, fileWalker)
if err != nil {
return fmt.Errorf("cannot traverse key store folder: %v", err)
}
return nil
}
func (apr *AccountPayloadRepository) StoreToSource() error {
keyUID := apr.multiaccount.KeyUID
if apr.loggedInKeyUID != "" && apr.loggedInKeyUID != keyUID {
return ErrLoggedInKeyUIDConflict
}
if apr.loggedInKeyUID == keyUID {
// skip storing keys if user is logged in with the same key
return nil
}
err := apr.validateKeys(apr.password)
if err != nil {
return err
}
if err = apr.storeKeys(apr.keystorePath); err != nil && err != ErrKeyFileAlreadyExists {
return err
}
// skip storing multiaccount if key already exists
if err == ErrKeyFileAlreadyExists {
apr.exist = true
apr.multiaccount, err = apr.multiaccountsDB.GetAccount(keyUID)
if err != nil {
return err
}
return nil
}
err = apr.storeMultiAccount()
if err != nil {
return err
}
// TODO install PublicKey into settings, probably do this outside of StoreToSource
return nil
}
func (apr *AccountPayloadRepository) validateKeys(password string) error {
for _, key := range apr.keys {
k, err := keystore.DecryptKey(key, password)
if err != nil {
return err
}
err = generator.ValidateKeystoreExtendedKey(k)
if err != nil {
return err
}
}
return nil
}
func (apr *AccountPayloadRepository) storeKeys(keyStorePath string) error {
if keyStorePath == "" {
return fmt.Errorf("keyStorePath can not be empty")
}
_, lastDir := filepath.Split(keyStorePath)
// If lastDir == "keystore" we presume we need to create the rest of the keystore path
// else we presume the provided keystore is valid
if lastDir == "keystore" {
if apr.multiaccount == nil || apr.multiaccount.KeyUID == "" {
return fmt.Errorf("no known Key UID")
}
keyStorePath = filepath.Join(keyStorePath, apr.multiaccount.KeyUID)
_, err := os.Stat(keyStorePath)
if os.IsNotExist(err) {
err := os.MkdirAll(keyStorePath, 0777)
if err != nil {
return err
}
} else if err != nil {
return err
} else {
return ErrKeyFileAlreadyExists
}
}
for name, data := range apr.keys {
accountKey := new(keystore.EncryptedKeyJSONV3)
if err := json.Unmarshal(data, &accountKey); err != nil {
return fmt.Errorf("failed to read key file: %s", err)
}
if len(accountKey.Address) != 40 {
return fmt.Errorf("account key address has invalid length '%s'", accountKey.Address)
}
err := ioutil.WriteFile(filepath.Join(keyStorePath, name), data, 0600)
if err != nil {
return err
}
}
return nil
}
func (apr *AccountPayloadRepository) storeMultiAccount() error {
apr.multiaccount.KDFIterations = apr.kdfIterations
return apr.multiaccountsDB.SaveAccount(*apr.multiaccount)
}
type RawMessagePayloadManager struct {
logger *zap.Logger
// reference from AccountPayloadManager#accountPayload
accountPayload *AccountPayload
*PayloadEncryptionManager
payloadRepository *RawMessageRepository
}
func NewRawMessagePayloadManager(logger *zap.Logger, accountPayload *AccountPayload, aesKey []byte, backend *api.GethStatusBackend, nodeConfig *params.NodeConfig, settingCurrentNetwork, deviceType string) (*RawMessagePayloadManager, error) {
l := logger.Named("RawMessagePayloadManager")
pem, err := NewPayloadEncryptionManager(aesKey, l)
if err != nil {
return nil, err
}
return &RawMessagePayloadManager{
logger: l,
accountPayload: accountPayload,
PayloadEncryptionManager: pem,
payloadRepository: NewRawMessageRepository(backend, accountPayload, nodeConfig, settingCurrentNetwork, deviceType),
}, nil
}
func (r *RawMessagePayloadManager) Mount() error {
err := r.payloadRepository.LoadFromSource()
if err != nil {
return err
}
return r.Encrypt(r.payloadRepository.payload)
}
func (r *RawMessagePayloadManager) Receive(data []byte) error {
err := r.Decrypt(data)
if err != nil {
return err
}
r.payloadRepository.payload = r.Received()
return r.payloadRepository.StoreToSource()
}
func (r *RawMessagePayloadManager) ResetPayload() {
r.payloadRepository.payload = make([]byte, 0)
r.PayloadEncryptionManager.ResetPayload()
}
type RawMessageRepository struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
accountPayload *AccountPayload
nodeConfig *params.NodeConfig
settingCurrentNetwork string
deviceType string
}
func NewRawMessageRepository(backend *api.GethStatusBackend, accountPayload *AccountPayload, config *params.NodeConfig, settingCurrentNetwork, deviceType string) *RawMessageRepository {
return &RawMessageRepository{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
payload: make([]byte, 0),
accountPayload: accountPayload,
nodeConfig: config,
settingCurrentNetwork: settingCurrentNetwork,
deviceType: deviceType,
}
}
func (r *RawMessageRepository) LoadFromSource() error {
account := r.accountPayload.multiaccount
if account == nil || account.KeyUID == "" {
return fmt.Errorf("no known KeyUID when loading raw messages")
}
payload, err := r.syncRawMessageHandler.PrepareRawMessage(account.KeyUID, r.deviceType)
if err != nil {
return err
}
r.payload = payload
return nil
}
func (r *RawMessageRepository) StoreToSource() error {
accountPayload := r.accountPayload
if accountPayload == nil || accountPayload.multiaccount == nil {
return fmt.Errorf("no known multiaccount when storing raw messages")
}
return r.syncRawMessageHandler.HandleRawMessage(accountPayload, r.nodeConfig, r.settingCurrentNetwork, r.deviceType, r.payload)
}
type InstallationPayloadManager struct {
logger *zap.Logger
*PayloadEncryptionManager
installationPayloadRepository *InstallationPayloadRepository
}
func NewInstallationPayloadManager(logger *zap.Logger, aesKey []byte, backend *api.GethStatusBackend, deviceType string) (*InstallationPayloadManager, error) {
l := logger.Named("InstallationPayloadManager")
pem, err := NewPayloadEncryptionManager(aesKey, l)
if err != nil {
return nil, err
}
return &InstallationPayloadManager{
logger: l,
PayloadEncryptionManager: pem,
installationPayloadRepository: NewInstallationPayloadRepository(backend, deviceType),
}, nil
}
func (i *InstallationPayloadManager) Mount() error {
err := i.installationPayloadRepository.LoadFromSource()
if err != nil {
return err
}
return i.Encrypt(i.installationPayloadRepository.payload)
}
func (i *InstallationPayloadManager) Receive(data []byte) error {
err := i.Decrypt(data)
if err != nil {
return err
}
i.installationPayloadRepository.payload = i.Received()
return i.installationPayloadRepository.StoreToSource()
}
func (i *InstallationPayloadManager) ResetPayload() {
i.installationPayloadRepository.payload = make([]byte, 0)
i.PayloadEncryptionManager.ResetPayload()
}
type InstallationPayloadRepository struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
deviceType string
backend *api.GethStatusBackend
}
func NewInstallationPayloadRepository(backend *api.GethStatusBackend, deviceType string) *InstallationPayloadRepository {
return &InstallationPayloadRepository{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
deviceType: deviceType,
backend: backend,
}
}
func (r *InstallationPayloadRepository) LoadFromSource() error {
rawMessageCollector := new(RawMessageCollector)
err := r.syncRawMessageHandler.CollectInstallationData(rawMessageCollector, r.deviceType)
if err != nil {
return err
}
r.payload, err = proto.Marshal(rawMessageCollector.convertToSyncRawMessage())
return err
}
func (r *InstallationPayloadRepository) StoreToSource() error {
messenger := r.backend.Messenger()
if messenger == nil {
return fmt.Errorf("messenger is nil when invoke InstallationPayloadRepository#StoreToSource")
}
rawMessages, _, _, err := r.syncRawMessageHandler.unmarshalSyncRawMessage(r.payload)
if err != nil {
return err
}
err = messenger.SetInstallationDeviceType(r.deviceType)
if err != nil {
return err
}
return messenger.HandleSyncRawMessages(rawMessages)
}

View File

@ -0,0 +1,318 @@
package pairing
import (
"fmt"
"io/fs"
"io/ioutil"
"path/filepath"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/multiaccounts"
)
type PayloadMounter interface {
PayloadLocker
// Mount Loads the payload into the PayloadManager's state
Mount() error
// ToSend returns an outbound safe (encrypted) payload
ToSend() []byte
}
type PayloadLoader interface {
Load() error
}
/*
|--------------------------------------------------------------------------
| AccountPayload
|--------------------------------------------------------------------------
|
| AccountPayloadMounter, AccountPayloadLoader and AccountPayloadMarshaller
|
*/
// AccountPayloadMounter is responsible for the whole lifecycle of an AccountPayload
type AccountPayloadMounter struct {
logger *zap.Logger
accountPayload *AccountPayload
encryptor *PayloadEncryptor
accountPayloadMarshaller *AccountPayloadMarshaller
payloadLoader PayloadLoader
}
// NewAccountPayloadMounter generates a new and initialised AccountPayloadMounter
func NewAccountPayloadMounter(pe *PayloadEncryptor, config *SenderConfig, logger *zap.Logger) (*AccountPayloadMounter, error) {
l := logger.Named("AccountPayloadLoader")
l.Debug("fired", zap.Any("config", config))
// A new SHARED AccountPayload
p := new(AccountPayload)
apl, err := NewAccountPayloadLoader(p, config)
if err != nil {
return nil, err
}
return &AccountPayloadMounter{
logger: l,
accountPayload: p,
encryptor: pe.Renew(),
accountPayloadMarshaller: NewPairingPayloadMarshaller(p, l),
payloadLoader: apl,
}, nil
}
// Mount loads and prepares the payload to be stored in the AccountPayloadLoader's state ready for later access
func (apm *AccountPayloadMounter) Mount() error {
l := apm.logger.Named("Mount()")
l.Debug("fired")
err := apm.payloadLoader.Load()
if err != nil {
return err
}
l.Debug("after Load()")
pb, err := apm.accountPayloadMarshaller.MarshalProtobuf()
if err != nil {
return err
}
l.Debug(
"after MarshalProtobuf",
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.keys", apm.accountPayloadMarshaller.keys),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.multiaccount", apm.accountPayloadMarshaller.multiaccount),
zap.String("accountPayloadMarshaller.accountPayloadMarshaller.password", apm.accountPayloadMarshaller.password),
zap.Binary("pb", pb),
)
return apm.encryptor.encrypt(pb)
}
func (apm *AccountPayloadMounter) ToSend() []byte {
return apm.encryptor.getEncrypted()
}
func (apm *AccountPayloadMounter) LockPayload() {
apm.encryptor.lockPayload()
}
// AccountPayloadLoader is responsible for loading, parsing and validating AccountPayload data
type AccountPayloadLoader struct {
*AccountPayload
multiaccountsDB *multiaccounts.Database
keystorePath string
keyUID string
}
func NewAccountPayloadLoader(p *AccountPayload, config *SenderConfig) (*AccountPayloadLoader, error) {
ppr := &AccountPayloadLoader{
AccountPayload: p,
}
if config == nil {
return ppr, nil
}
ppr.multiaccountsDB = config.DB
ppr.keyUID = config.KeyUID
ppr.password = config.Password
ppr.keystorePath = config.KeystorePath
return ppr, nil
}
func (apl *AccountPayloadLoader) Load() error {
err := apl.loadKeys(apl.keystorePath)
if err != nil {
return err
}
err = validateKeys(apl.keys, apl.password)
if err != nil {
return err
}
apl.multiaccount, err = apl.multiaccountsDB.GetAccount(apl.keyUID)
if err != nil {
return err
}
return nil
}
func (apl *AccountPayloadLoader) loadKeys(keyStorePath string) error {
apl.keys = make(map[string][]byte)
fileWalker := func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
return err
}
if dirEntry.IsDir() || filepath.Dir(path) != keyStorePath {
return nil
}
rawKeyFile, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("invalid account key file: %v", err)
}
apl.keys[dirEntry.Name()] = rawKeyFile
return nil
}
err := filepath.WalkDir(keyStorePath, fileWalker)
if err != nil {
return fmt.Errorf("cannot traverse key store folder: %v", err)
}
return nil
}
/*
|--------------------------------------------------------------------------
| RawMessagePayload
|--------------------------------------------------------------------------
|
| RawMessagePayloadMounter and RawMessageLoader
|
*/
type RawMessagePayloadMounter struct {
logger *zap.Logger
encryptor *PayloadEncryptor
loader *RawMessageLoader
}
func NewRawMessagePayloadMounter(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, config *SenderConfig) *RawMessagePayloadMounter {
l := logger.Named("RawMessagePayloadManager")
return &RawMessagePayloadMounter{
logger: l,
encryptor: pe.Renew(),
loader: NewRawMessageLoader(backend, config),
}
}
func (r *RawMessagePayloadMounter) Mount() error {
err := r.loader.Load()
if err != nil {
return err
}
return r.encryptor.encrypt(r.loader.payload)
}
func (r *RawMessagePayloadMounter) ToSend() []byte {
return r.encryptor.getEncrypted()
}
func (r *RawMessagePayloadMounter) LockPayload() {
r.encryptor.lockPayload()
}
type RawMessageLoader struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
keyUID string
deviceType string
}
func NewRawMessageLoader(backend *api.GethStatusBackend, config *SenderConfig) *RawMessageLoader {
return &RawMessageLoader{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
payload: make([]byte, 0),
keyUID: config.KeyUID,
deviceType: config.DeviceType,
}
}
func (r *RawMessageLoader) Load() (err error) {
r.payload, err = r.syncRawMessageHandler.PrepareRawMessage(r.keyUID, r.deviceType)
return err
}
/*
|--------------------------------------------------------------------------
| InstallationPayload
|--------------------------------------------------------------------------
|
| InstallationPayloadMounter and InstallationPayloadLoader
|
*/
type InstallationPayloadMounter struct {
logger *zap.Logger
encryptor *PayloadEncryptor
loader *InstallationPayloadLoader
}
func NewInstallationPayloadMounter(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, deviceType string) *InstallationPayloadMounter {
return &InstallationPayloadMounter{
logger: logger.Named("InstallationPayloadManager"),
encryptor: pe.Renew(),
loader: NewInstallationPayloadLoader(backend, deviceType),
}
}
func (i *InstallationPayloadMounter) Mount() error {
err := i.loader.Load()
if err != nil {
return err
}
return i.encryptor.encrypt(i.loader.payload)
}
func (i *InstallationPayloadMounter) ToSend() []byte {
return i.encryptor.getEncrypted()
}
func (i *InstallationPayloadMounter) LockPayload() {
i.encryptor.lockPayload()
}
type InstallationPayloadLoader struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
deviceType string
}
func NewInstallationPayloadLoader(backend *api.GethStatusBackend, deviceType string) *InstallationPayloadLoader {
return &InstallationPayloadLoader{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
deviceType: deviceType,
}
}
func (r *InstallationPayloadLoader) Load() error {
rawMessageCollector := new(RawMessageCollector)
err := r.syncRawMessageHandler.CollectInstallationData(rawMessageCollector, r.deviceType)
if err != nil {
return err
}
r.payload, err = proto.Marshal(rawMessageCollector.convertToSyncRawMessage())
return err
}
/*
|--------------------------------------------------------------------------
| PayloadMounters
|--------------------------------------------------------------------------
|
| Funcs for all PayloadMounters AccountPayloadMounter, RawMessagePayloadMounter and InstallationPayloadMounter
|
*/
func NewPayloadMounters(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, config *SenderConfig) (*AccountPayloadMounter, *RawMessagePayloadMounter, *InstallationPayloadMounterReceiver, error) {
am, err := NewAccountPayloadMounter(pe, config, logger)
if err != nil {
return nil, nil, nil, err
}
rmm := NewRawMessagePayloadMounter(logger, pe, backend, config)
imr := NewInstallationPayloadMounterReceiver(logger, pe, backend, config.DeviceType)
return am, rmm, imr, nil
}

View File

@ -0,0 +1,371 @@
package pairing
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"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/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
}
/*
|--------------------------------------------------------------------------
| AccountPayload
|--------------------------------------------------------------------------
|
| AccountPayloadReceiver, AccountPayloadStorer and AccountPayloadMarshaller
|
*/
// AccountPayloadReceiver is responsible for the whole lifecycle of a AccountPayload
type AccountPayloadReceiver struct {
logger *zap.Logger
accountPayload *AccountPayload
encryptor *PayloadEncryptor
accountPayloadMarshaller *AccountPayloadMarshaller
accountStorer *AccountPayloadStorer
}
// NewAccountPayloadReceiver generates a new and initialised AccountPayloadManager
func NewAccountPayloadReceiver(encryptor *PayloadEncryptor, config *ReceiverConfig, logger *zap.Logger) (*AccountPayloadReceiver, error) {
l := logger.Named("AccountPayloadManager")
l.Debug("fired", zap.Any("config", config))
// A new SHARED AccountPayload
p := new(AccountPayload)
accountPayloadRepository, err := NewAccountPayloadStorer(p, config)
if err != nil {
return nil, err
}
return &AccountPayloadReceiver{
logger: l,
accountPayload: p,
encryptor: encryptor.Renew(),
accountPayloadMarshaller: NewPairingPayloadMarshaller(p, l),
accountStorer: accountPayloadRepository,
}, nil
}
// Receive takes a []byte representing raw data, parses and stores the data
func (apr *AccountPayloadReceiver) Receive(data []byte) error {
l := apr.logger.Named("Receive()")
l.Debug("fired")
err := apr.encryptor.decrypt(data)
if err != nil {
return err
}
l.Debug("after Decrypt")
err = apr.accountPayloadMarshaller.UnmarshalProtobuf(apr.Received())
if err != nil {
return err
}
l.Debug(
"after UnmarshalProtobuf",
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.keys", apr.accountPayloadMarshaller.keys),
zap.Any("accountPayloadMarshaller.accountPayloadMarshaller.multiaccount", apr.accountPayloadMarshaller.multiaccount),
zap.String("accountPayloadMarshaller.accountPayloadMarshaller.password", apr.accountPayloadMarshaller.password),
zap.Binary("accountPayloadMarshaller.Received()", apr.Received()),
)
signal.SendLocalPairingEvent(Event{Type: EventReceivedAccount, Action: ActionPairingAccount, Data: apr.accountPayload.multiaccount})
return apr.accountStorer.Store()
}
func (apr *AccountPayloadReceiver) Received() []byte {
return apr.encryptor.getDecrypted()
}
func (apr *AccountPayloadReceiver) LockPayload() {
apr.encryptor.lockPayload()
}
// 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
|
*/
type RawMessagePayloadReceiver struct {
logger *zap.Logger
encryptor *PayloadEncryptor
storer *RawMessageStorer
}
func NewRawMessagePayloadReceiver(logger *zap.Logger, accountPayload *AccountPayload, encryptor *PayloadEncryptor, backend *api.GethStatusBackend, config *ReceiverConfig) *RawMessagePayloadReceiver {
l := logger.Named("RawMessagePayloadManager")
return &RawMessagePayloadReceiver{
logger: l,
encryptor: encryptor.Renew(),
storer: NewRawMessageStorer(backend, accountPayload, config),
}
}
func (r *RawMessagePayloadReceiver) Receive(data []byte) error {
err := r.encryptor.decrypt(data)
if err != nil {
return err
}
r.storer.payload = r.Received()
return r.storer.Store()
}
func (r *RawMessagePayloadReceiver) Received() []byte {
return r.encryptor.getDecrypted()
}
func (r *RawMessagePayloadReceiver) LockPayload() {
r.encryptor.lockPayload()
}
type RawMessageStorer struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
accountPayload *AccountPayload
nodeConfig *params.NodeConfig
settingCurrentNetwork string
deviceType string
}
func NewRawMessageStorer(backend *api.GethStatusBackend, accountPayload *AccountPayload, config *ReceiverConfig) *RawMessageStorer {
return &RawMessageStorer{
syncRawMessageHandler: NewSyncRawMessageHandler(backend),
payload: make([]byte, 0),
accountPayload: accountPayload,
nodeConfig: config.NodeConfig,
settingCurrentNetwork: config.SettingCurrentNetwork,
deviceType: config.DeviceType,
}
}
func (r *RawMessageStorer) Store() error {
accountPayload := r.accountPayload
if accountPayload == nil || accountPayload.multiaccount == nil {
return fmt.Errorf("no known multiaccount when storing raw messages")
}
return r.syncRawMessageHandler.HandleRawMessage(accountPayload, r.nodeConfig, r.settingCurrentNetwork, r.deviceType, r.payload)
}
/*
|--------------------------------------------------------------------------
| InstallationPayload
|--------------------------------------------------------------------------
|
| InstallationPayloadReceiver and InstallationPayloadStorer
|
*/
type InstallationPayloadReceiver struct {
logger *zap.Logger
encryptor *PayloadEncryptor
storer *InstallationPayloadStorer
}
func NewInstallationPayloadReceiver(logger *zap.Logger, encryptor *PayloadEncryptor, backend *api.GethStatusBackend, deviceType string) *InstallationPayloadReceiver {
l := logger.Named("InstallationPayloadManager")
return &InstallationPayloadReceiver{
logger: l,
encryptor: encryptor.Renew(),
storer: NewInstallationPayloadStorer(backend, deviceType),
}
}
func (i *InstallationPayloadReceiver) Receive(data []byte) error {
err := i.encryptor.decrypt(data)
if err != nil {
return err
}
i.storer.payload = i.encryptor.getDecrypted()
return i.storer.Store()
}
func (i *InstallationPayloadReceiver) Received() []byte {
return i.encryptor.getDecrypted()
}
func (i *InstallationPayloadReceiver) LockPayload() {
i.encryptor.lockPayload()
}
type InstallationPayloadStorer struct {
payload []byte
syncRawMessageHandler *SyncRawMessageHandler
deviceType string
backend *api.GethStatusBackend
}
func NewInstallationPayloadStorer(backend *api.GethStatusBackend, deviceType string) *InstallationPayloadStorer {
return &InstallationPayloadStorer{
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()")
}
rawMessages, _, _, err := r.syncRawMessageHandler.unmarshalSyncRawMessage(r.payload)
if err != nil {
return err
}
err = messenger.SetInstallationDeviceType(r.deviceType)
if err != nil {
return err
}
return messenger.HandleSyncRawMessages(rawMessages)
}
/*
|--------------------------------------------------------------------------
| PayloadReceivers
|--------------------------------------------------------------------------
|
| Funcs for all PayloadReceivers AccountPayloadReceiver, RawMessagePayloadReceiver and InstallationPayloadMounter
|
*/
func NewPayloadReceivers(logger *zap.Logger, pe *PayloadEncryptor, backend *api.GethStatusBackend, config *ReceiverConfig) (*AccountPayloadReceiver, *RawMessagePayloadReceiver, *InstallationPayloadMounterReceiver, error) {
ar, err := NewAccountPayloadReceiver(pe, config, logger)
if err != nil {
return nil, nil, nil, err
}
rmr := NewRawMessagePayloadReceiver(logger, ar.accountPayload, pe, backend, config)
imr := NewInstallationPayloadMounterReceiver(logger, pe, backend, config.DeviceType)
return ar, rmr, imr, nil
}

View File

@ -24,6 +24,8 @@ func NewSyncRawMessageHandler(backend *api.GethStatusBackend) *SyncRawMessageHan
} }
func (s *SyncRawMessageHandler) CollectInstallationData(rawMessageCollector *RawMessageCollector, deviceType string) error { func (s *SyncRawMessageHandler) CollectInstallationData(rawMessageCollector *RawMessageCollector, deviceType string) error {
// TODO Could this function be part of the installation data exchange flow?
// https://github.com/status-im/status-go/issues/3304
messenger := s.backend.Messenger() messenger := s.backend.Messenger()
if messenger == nil { if messenger == nil {
return fmt.Errorf("messenger is nil when CollectInstallationData") return fmt.Errorf("messenger is nil when CollectInstallationData")

View File

@ -4,41 +4,63 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/tls" "encoding/json"
"fmt" "fmt"
"net" "net"
"time" "time"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"go.uber.org/zap"
"github.com/status-im/status-go/api" "github.com/status-im/status-go/api"
"github.com/status-im/status-go/logutils" "github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/server" "github.com/status-im/status-go/server"
) )
type Server struct { /*
server.Server |--------------------------------------------------------------------------
PayloadManager | type BaseServer struct {
rawMessagePayloadManager *RawMessagePayloadManager |--------------------------------------------------------------------------
installationPayloadManager *InstallationPayloadManager |
|
|
*/
pk *ecdsa.PublicKey type BaseServer struct {
ek []byte server.Server
mode Mode
cookieStore *sessions.CookieStore cookieStore *sessions.CookieStore
encryptor *PayloadEncryptor
pk *ecdsa.PublicKey
ek []byte
// TODO remove mode from pairing process
// https://github.com/status-im/status-go/issues/3301
mode Mode
} }
type Config struct { // NewBaseServer returns a *BaseServer init from the given *SenderServerConfig
// Connection fields func NewBaseServer(logger *zap.Logger, e *PayloadEncryptor, config *ServerConfig) (*BaseServer, error) {
PK *ecdsa.PublicKey cs, err := makeCookieStore()
EK []byte if err != nil {
Cert *tls.Certificate return nil, err
Hostname string }
Mode Mode
// AccountPayload management fields bs := &BaseServer{
*AccountPayloadManagerConfig Server: server.NewServer(
config.Cert,
config.Hostname,
nil,
logger,
),
encryptor: e,
cookieStore: cs,
pk: config.PK,
ek: config.EK,
mode: config.Mode,
}
bs.SetTimeout(config.Timeout)
return bs, nil
} }
func makeCookieStore() (*sessions.CookieStore, error) { func makeCookieStore() (*sessions.CookieStore, error) {
@ -57,50 +79,8 @@ func makeCookieStore() (*sessions.CookieStore, error) {
return sessions.NewCookieStore(auth, enc), nil return sessions.NewCookieStore(auth, enc), nil
} }
// NewPairingServer returns a *Server init from the given *Config
func NewPairingServer(backend *api.GethStatusBackend, config *Config) (*Server, error) {
logger := logutils.ZapLogger().Named("Server")
accountPayloadManagerConfig := config.AccountPayloadManagerConfig
pm, err := NewAccountPayloadManager(config.EK, accountPayloadManagerConfig, logger)
if err != nil {
return nil, err
}
cs, err := makeCookieStore()
if err != nil {
return nil, err
}
rmpm, err := NewRawMessagePayloadManager(logger, pm.accountPayload, config.EK, backend, accountPayloadManagerConfig.GetNodeConfig(), accountPayloadManagerConfig.GetSettingCurrentNetwork(), accountPayloadManagerConfig.GetDeviceType())
if err != nil {
return nil, err
}
ipm, err := NewInstallationPayloadManager(logger, config.EK, backend, accountPayloadManagerConfig.GetDeviceType())
if err != nil {
return nil, err
}
s := &Server{Server: server.NewServer(
config.Cert,
config.Hostname,
nil,
logger,
),
pk: config.PK,
ek: config.EK,
mode: config.Mode,
PayloadManager: pm,
cookieStore: cs,
rawMessagePayloadManager: rmpm,
installationPayloadManager: ipm,
}
s.SetTimeout(config.GetTimeout())
return s, nil
}
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state // MakeConnectionParams generates a *ConnectionParams based on the Server's current state
func (s *Server) MakeConnectionParams() (*ConnectionParams, error) { func (s *BaseServer) MakeConnectionParams() (*ConnectionParams, error) {
hostname := s.GetHostname() hostname := s.GetHostname()
netIP := net.ParseIP(hostname) netIP := net.ParseIP(hostname)
if netIP == nil { if netIP == nil {
@ -115,106 +95,218 @@ func (s *Server) MakeConnectionParams() (*ConnectionParams, error) {
return NewConnectionParams(netIP, s.MustGetPort(), s.pk, s.ek, s.mode), nil return NewConnectionParams(netIP, s.MustGetPort(), s.pk, s.ek, s.mode), nil
} }
func (s *Server) StartPairing() error { func (s *BaseServer) GetCookieStore() *sessions.CookieStore {
switch s.mode { return s.cookieStore
case Receiving:
return s.startReceivingData()
case Sending:
return s.startSendingData()
default:
return fmt.Errorf("invalid server mode '%d'", s.mode)
}
} }
func (s *Server) startReceivingData() error { func (s *BaseServer) DecryptPlain(data []byte) ([]byte, error) {
s.SetHandlers(server.HandlerPatternMap{ return s.encryptor.decryptPlain(data)
pairingReceiveAccount: handleReceiveAccount(s),
pairingChallenge: handlePairingChallenge(s),
pairingSyncDeviceReceive: handleParingSyncDeviceReceive(s),
// send installation data back to sender
pairingSendInstallation: handleSendInstallation(s),
})
return s.Start()
} }
func (s *Server) startSendingData() error { func MakeServerConfig(config *ServerConfig) error {
err := s.Mount()
if err != nil {
return err
}
s.SetHandlers(server.HandlerPatternMap{
pairingSendAccount: challengeMiddleware(s, handleSendAccount(s)),
pairingChallenge: handlePairingChallenge(s),
pairingSyncDeviceSend: challengeMiddleware(s, handlePairingSyncDeviceSend(s)),
// receive installation data from receiver
pairingReceiveInstallation: challengeMiddleware(s, handleReceiveInstallation(s)),
})
return s.Start()
}
// MakeFullPairingServer generates a fully configured and randomly seeded Server
func MakeFullPairingServer(backend *api.GethStatusBackend, mode Mode, storeConfig *PayloadSourceConfig) (*Server, error) {
tlsKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) tlsKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {
return nil, err return err
} }
AESKey := make([]byte, 32) AESKey := make([]byte, 32)
_, err = rand.Read(AESKey) _, err = rand.Read(AESKey)
if err != nil { if err != nil {
return nil, err return err
} }
outboundIP, err := server.GetOutboundIP() outboundIP, err := server.GetOutboundIP()
if err != nil { if err != nil {
return nil, err return err
} }
tlsCert, _, err := GenerateCertFromKey(tlsKey, time.Now(), outboundIP.String()) tlsCert, _, err := GenerateCertFromKey(tlsKey, time.Now(), outboundIP.String())
if err != nil {
return err
}
config.PK = &tlsKey.PublicKey
config.EK = AESKey
config.Cert = &tlsCert
config.Hostname = outboundIP.String()
return nil
}
/*
|--------------------------------------------------------------------------
| type SenderServer struct {
|--------------------------------------------------------------------------
|
| With AccountPayloadMounter, RawMessagePayloadMounter and InstallationPayloadMounterReceiver
|
*/
type SenderServer struct {
*BaseServer
accountMounter PayloadMounter
rawMessageMounter *RawMessagePayloadMounter
installationMounter *InstallationPayloadMounterReceiver
}
// NewSenderServer returns a *SenderServer init from the given *SenderServerConfig
func NewSenderServer(backend *api.GethStatusBackend, config *SenderServerConfig) (*SenderServer, error) {
logger := logutils.ZapLogger().Named("SenderServer")
e := NewPayloadEncryptor(config.ServerConfig.EK)
bs, err := NewBaseServer(logger, e, config.ServerConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
accountPayloadManagerConfig := &AccountPayloadManagerConfig{ am, rmm, imr, err := NewPayloadMounters(logger, e, backend, config.SenderConfig)
// Things that can't be generated, but DO NOT come from app client if err != nil {
DB: backend.GetMultiaccountDB(), return nil, err
// Things that can't be generated, but DO come from the app client
PayloadSourceConfig: storeConfig,
}
if mode == Receiving {
updateLoggedInKeyUID(accountPayloadManagerConfig, backend)
} }
return NewPairingServer(backend, &Config{ return &SenderServer{
// Things that can be generated, and CANNOT come from the app client (well they could be this is better) BaseServer: bs,
PK: &tlsKey.PublicKey, accountMounter: am,
EK: AESKey, rawMessageMounter: rmm,
Cert: &tlsCert, installationMounter: imr,
Hostname: outboundIP.String(), }, nil
// Things that can't be generated, but DO come from the app client
Mode: mode,
AccountPayloadManagerConfig: accountPayloadManagerConfig,
})
} }
// StartUpPairingServer generates a Server, starts the pairing server in the correct mode func (s *SenderServer) startSendingData() error {
// and returns the ConnectionParams string to allow a Client to make a successful connection. s.SetHandlers(server.HandlerPatternMap{
func StartUpPairingServer(backend *api.GethStatusBackend, mode Mode, configJSON string) (string, error) { pairingChallenge: handlePairingChallenge(s),
conf, err := NewPayloadSourceForServer(configJSON, mode) pairingSendAccount: middlewareChallenge(s, handleSendAccount(s, s.accountMounter)),
pairingSendSyncDevice: middlewareChallenge(s, handlePairingSyncDeviceSend(s, s.rawMessageMounter)),
// TODO implement refactor of installation data exchange to follow the send/receive pattern of
// the other handlers.
// https://github.com/status-im/status-go/issues/3304
// receive installation data from receiver
pairingReceiveInstallation: middlewareChallenge(s, handleReceiveInstallation(s, s.installationMounter)),
})
return s.Start()
}
// MakeFullSenderServer generates a fully configured and randomly seeded SenderServer
func MakeFullSenderServer(backend *api.GethStatusBackend, mode Mode, config *SenderServerConfig) (*SenderServer, error) {
err := MakeServerConfig(config.ServerConfig)
if err != nil {
return nil, err
}
config.SenderConfig.DB = backend.GetMultiaccountDB()
return NewSenderServer(backend, config)
}
// StartUpSenderServer generates a SenderServer, starts the sending server in the correct mode
// and returns the ConnectionParams string to allow a ReceiverClient to make a successful connection.
func StartUpSenderServer(backend *api.GethStatusBackend, mode Mode, configJSON string) (string, error) {
conf := NewSenderServerConfig()
err := json.Unmarshal([]byte(configJSON), conf)
if err != nil { if err != nil {
return "", err return "", err
} }
ps, err := MakeFullPairingServer(backend, mode, conf) ps, err := MakeFullSenderServer(backend, mode, conf)
if err != nil { if err != nil {
return "", err return "", err
} }
err = ps.StartPairing() err = ps.startSendingData()
if err != nil {
return "", err
}
cp, err := ps.MakeConnectionParams()
if err != nil {
return "", err
}
return cp.ToString(), nil
}
/*
|--------------------------------------------------------------------------
| ReceiverServer
|--------------------------------------------------------------------------
|
| With AccountPayloadReceiver, RawMessagePayloadReceiver, InstallationPayloadMounterReceiver
|
*/
type ReceiverServer struct {
*BaseServer
accountReceiver PayloadReceiver
rawMessageReceiver PayloadReceiver
installationReceiver PayloadMounterReceiver
}
// NewReceiverServer returns a *SenderServer init from the given *ReceiverServerConfig
func NewReceiverServer(backend *api.GethStatusBackend, config *ReceiverServerConfig) (*ReceiverServer, error) {
logger := logutils.ZapLogger().Named("SenderServer")
e := NewPayloadEncryptor(config.ServerConfig.EK)
bs, err := NewBaseServer(logger, e, config.ServerConfig)
if err != nil {
return nil, err
}
ar, rmr, imr, err := NewPayloadReceivers(logger, e, backend, config.ReceiverConfig)
if err != nil {
return nil, err
}
return &ReceiverServer{
BaseServer: bs,
accountReceiver: ar,
rawMessageReceiver: rmr,
installationReceiver: imr,
}, nil
}
func (s *ReceiverServer) startReceivingData() error {
s.SetHandlers(server.HandlerPatternMap{
pairingChallenge: handlePairingChallenge(s),
pairingReceiveAccount: handleReceiveAccount(s, s.accountReceiver),
pairingReceiveSyncDevice: handleParingSyncDeviceReceive(s, s.rawMessageReceiver),
// TODO implement refactor of installation data exchange to follow the send/receive pattern of
// the other handlers.
// https://github.com/status-im/status-go/issues/3304
// send installation data back to sender
pairingSendInstallation: handleSendInstallation(s, s.installationReceiver),
})
return s.Start()
}
// MakeFullReceiverServer generates a fully configured and randomly seeded ReceiverServer
func MakeFullReceiverServer(backend *api.GethStatusBackend, mode Mode, config *ReceiverServerConfig) (*ReceiverServer, error) {
err := MakeServerConfig(config.ServerConfig)
if err != nil {
return nil, err
}
activeAccount, _ := backend.GetActiveAccount()
if activeAccount != nil {
config.ReceiverConfig.LoggedInKeyUID = activeAccount.KeyUID
}
config.ReceiverConfig.DB = backend.GetMultiaccountDB()
return NewReceiverServer(backend, config)
}
// StartUpReceiverServer generates a ReceiverServer, starts the sending server in the correct mode
// and returns the ConnectionParams string to allow a SenderClient to make a successful connection.
func StartUpReceiverServer(backend *api.GethStatusBackend, mode Mode, configJSON string) (string, error) {
conf := NewReceiverServerConfig()
err := json.Unmarshal([]byte(configJSON), conf)
if err != nil {
return "", err
}
ps, err := MakeFullReceiverServer(backend, mode, conf)
if err != nil {
return "", err
}
err = ps.startReceivingData()
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -30,133 +30,61 @@ func (s *PairingServerSuite) SetupTest() {
} }
func (s *PairingServerSuite) TestMultiBackgroundForeground() { func (s *PairingServerSuite) TestMultiBackgroundForeground() {
err := s.PS.Start() err := s.SS.Start()
s.Require().NoError(err) s.Require().NoError(err)
s.PS.ToBackground() s.SS.ToBackground()
s.PS.ToForeground() s.SS.ToForeground()
s.PS.ToBackground() s.SS.ToBackground()
s.PS.ToBackground() s.SS.ToBackground()
s.PS.ToForeground() s.SS.ToForeground()
s.PS.ToForeground() s.SS.ToForeground()
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.SS.MakeBaseURL().String()) // nolint: gosimple
} }
func (s *PairingServerSuite) TestMultiTimeout() { func (s *PairingServerSuite) TestMultiTimeout() {
s.PS.SetTimeout(20) s.SS.SetTimeout(20)
err := s.PS.Start() err := s.SS.Start()
s.Require().NoError(err) s.Require().NoError(err)
s.PS.ToBackground() s.SS.ToBackground()
s.PS.ToForeground() s.SS.ToForeground()
s.PS.ToBackground() s.SS.ToBackground()
s.PS.ToBackground() s.SS.ToBackground()
s.PS.ToForeground() s.SS.ToForeground()
s.PS.ToForeground() s.SS.ToForeground()
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.SS.MakeBaseURL().String()) // nolint: gosimple
time.Sleep(7 * time.Millisecond) time.Sleep(7 * time.Millisecond)
s.PS.ToBackground() s.SS.ToBackground()
time.Sleep(7 * time.Millisecond) time.Sleep(7 * time.Millisecond)
s.PS.ToForeground() s.SS.ToForeground()
time.Sleep(7 * time.Millisecond) time.Sleep(7 * time.Millisecond)
s.PS.ToBackground() s.SS.ToBackground()
time.Sleep(7 * time.Millisecond) time.Sleep(7 * time.Millisecond)
s.PS.ToBackground() s.SS.ToBackground()
time.Sleep(7 * time.Millisecond) time.Sleep(7 * time.Millisecond)
s.PS.ToForeground() s.SS.ToForeground()
time.Sleep(7 * time.Millisecond) time.Sleep(7 * time.Millisecond)
s.PS.ToForeground() s.SS.ToForeground()
// Wait for timeout to expire // Wait for timeout to expire
time.Sleep(40 * time.Millisecond) time.Sleep(40 * time.Millisecond)
s.Require().False(s.PS.IsRunning()) s.Require().False(s.SS.IsRunning())
} }
func newAccountPayloadManagerConfig() *AccountPayloadManagerConfig { // TestPairingServer_StartPairingSend tests that a Server can send data to a ReceiverClient
return &AccountPayloadManagerConfig{} func (s *PairingServerSuite) TestPairingServer_StartPairingSend() {
} // Replace PairingServer.accountMounter with a MockPayloadMounter
pm := NewMockPayloadMounter(s.EphemeralAES)
s.SS.accountMounter = pm
s.SS.mode = Sending
func (s *PairingServerSuite) TestPairingServer_StartPairing() { err := s.SS.startSendingData()
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
s.Require().NoError(err)
s.PS.PayloadManager = pm
modes := []Mode{
Receiving,
Sending,
}
for _, m := range modes {
s.PS.mode = m
err = s.PS.StartPairing()
s.Require().NoError(err)
cp, err := s.PS.MakeConnectionParams()
s.Require().NoError(err)
qr := cp.ToString()
// Client reads QR code and parses the connection string
ccp := new(ConnectionParams)
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewPairingClient(nil, ccp, newAccountPayloadManagerConfig())
s.Require().NoError(err)
// Compare cert values
cert := c.serverCert
cl := s.PS.GetCert().Leaf
s.Require().Equal(cl.Signature, cert.Signature)
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X))
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y))
s.Require().Equal(cl.Version, cert.Version)
s.Require().Zero(cl.SerialNumber.Cmp(cert.SerialNumber))
s.Require().Exactly(cl.NotBefore, cert.NotBefore)
s.Require().Exactly(cl.NotAfter, cert.NotAfter)
s.Require().Exactly(cl.IPAddresses, cert.IPAddresses)
// Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager
c.PayloadManager, err = NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
s.Require().NoError(err)
err = c.PairAccount()
s.Require().NoError(err)
switch m {
case Receiving:
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.plain, s.PS.Received())
s.Require().Equal(s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).received.encrypted, c.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.encrypted)
s.Require().Nil(s.PS.ToSend())
s.Require().Nil(c.Received())
case Sending:
s.Require().Equal(c.Received(), s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.plain)
s.Require().Equal(c.PayloadManager.(*MockEncryptOnlyPayloadManager).received.encrypted, s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).toSend.encrypted)
s.Require().Nil(c.ToSend())
s.Require().Nil(s.PS.Received())
}
// Reset the server's PayloadEncryptionManager
s.PS.PayloadManager.(*MockEncryptOnlyPayloadManager).ResetPayload()
s.PS.ResetPort()
}
}
func (s *PairingServerSuite) sendingSetup() *Client {
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
s.Require().NoError(err)
s.PS.PayloadManager = pm
s.PS.mode = Sending
err = s.PS.StartPairing()
s.Require().NoError(err) s.Require().NoError(err)
cp, err := s.PS.MakeConnectionParams() cp, err := s.SS.MakeConnectionParams()
s.Require().NoError(err) s.Require().NoError(err)
qr := cp.ToString() qr := cp.ToString()
@ -166,11 +94,104 @@ func (s *PairingServerSuite) sendingSetup() *Client {
err = ccp.FromString(qr) err = ccp.FromString(qr)
s.Require().NoError(err) s.Require().NoError(err)
c, err := NewPairingClient(nil, ccp, newAccountPayloadManagerConfig()) c, err := NewReceiverClient(nil, ccp, NewReceiverClientConfig())
s.Require().NoError(err)
// Compare cert values
cert := c.serverCert
cl := s.SS.GetCert().Leaf
s.Require().Equal(cl.Signature, cert.Signature)
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X))
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y))
s.Require().Equal(cl.Version, cert.Version)
s.Require().Zero(cl.SerialNumber.Cmp(cert.SerialNumber))
s.Require().Exactly(cl.NotBefore, cert.NotBefore)
s.Require().Exactly(cl.NotAfter, cert.NotAfter)
s.Require().Exactly(cl.IPAddresses, cert.IPAddresses)
// Replace ReceivingClient.accountReceiver with a MockPayloadReceiver
c.accountReceiver = NewMockPayloadReceiver(s.EphemeralAES)
err = c.getChallenge()
s.Require().NoError(err)
err = c.receiveAccountData()
s.Require().NoError(err)
s.Require().Equal(c.accountReceiver.Received(), s.SS.accountMounter.(*MockPayloadMounter).encryptor.payload.plain)
s.Require().Equal(c.accountReceiver.(*MockPayloadReceiver).encryptor.payload.encrypted, s.SS.accountMounter.(*MockPayloadMounter).encryptor.payload.encrypted)
}
// TestPairingServer_StartPairingReceive tests that a Server can receive data to a SenderClient
func (s *PairingServerSuite) TestPairingServer_StartPairingReceive() {
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
pm := NewMockPayloadReceiver(s.EphemeralAES)
s.RS.accountReceiver = pm
s.RS.mode = Receiving
err := s.RS.startReceivingData()
s.Require().NoError(err)
cp, err := s.RS.MakeConnectionParams()
s.Require().NoError(err)
qr := cp.ToString()
// Client reads QR code and parses the connection string
ccp := new(ConnectionParams)
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewSenderClient(nil, ccp, &SenderClientConfig{SenderConfig: &SenderConfig{}, ClientConfig: &ClientConfig{}})
s.Require().NoError(err)
// Compare cert values
cert := c.serverCert
cl := s.RS.GetCert().Leaf
s.Require().Equal(cl.Signature, cert.Signature)
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).X.Cmp(cert.PublicKey.(*ecdsa.PublicKey).X))
s.Require().Zero(cl.PublicKey.(*ecdsa.PublicKey).Y.Cmp(cert.PublicKey.(*ecdsa.PublicKey).Y))
s.Require().Equal(cl.Version, cert.Version)
s.Require().Zero(cl.SerialNumber.Cmp(cert.SerialNumber))
s.Require().Exactly(cl.NotBefore, cert.NotBefore)
s.Require().Exactly(cl.NotAfter, cert.NotAfter)
s.Require().Exactly(cl.IPAddresses, cert.IPAddresses)
// Replace SendingClient.accountMounter with a MockPayloadMounter
c.accountMounter = NewMockPayloadMounter(s.EphemeralAES)
s.Require().NoError(err)
err = c.sendAccountData()
s.Require().NoError(err)
s.Require().Equal(c.accountMounter.(*MockPayloadMounter).encryptor.payload.plain, s.RS.accountReceiver.Received())
s.Require().Equal(s.RS.accountReceiver.(*MockPayloadReceiver).encryptor.getEncrypted(), c.accountMounter.(*MockPayloadMounter).encryptor.payload.encrypted)
}
func (s *PairingServerSuite) sendingSetup() *ReceiverClient {
// Replace PairingServer.PayloadManager with a MockPayloadReceiver
pm := NewMockPayloadMounter(s.EphemeralAES)
s.SS.accountMounter = pm
s.SS.mode = Sending
err := s.SS.startSendingData()
s.Require().NoError(err)
cp, err := s.SS.MakeConnectionParams()
s.Require().NoError(err)
qr := cp.ToString()
// Client reads QR code and parses the connection string
ccp := new(ConnectionParams)
err = ccp.FromString(qr)
s.Require().NoError(err)
c, err := NewReceiverClient(nil, ccp, NewReceiverClientConfig())
s.Require().NoError(err) s.Require().NoError(err)
// Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager // Replace PairingClient.PayloadManager with a MockEncryptOnlyPayloadManager
c.PayloadManager, err = NewMockEncryptOnlyPayloadManager(s.EphemeralAES) c.accountReceiver = NewMockPayloadReceiver(s.EphemeralAES)
s.Require().NoError(err) s.Require().NoError(err)
return c return c
@ -257,17 +278,17 @@ func makeThingToSay() (string, error) {
} }
func (s *PairingServerSuite) TestGetOutboundIPWithFullServerE2e() { func (s *PairingServerSuite) TestGetOutboundIPWithFullServerE2e() {
s.PS.mode = Sending s.SS.mode = Sending
s.PS.SetHandlers(server.HandlerPatternMap{"/hello": testHandler(s.T())}) s.SS.SetHandlers(server.HandlerPatternMap{"/hello": testHandler(s.T())})
err := s.PS.Start() err := s.SS.Start()
s.Require().NoError(err) s.Require().NoError(err)
// Give time for the sever to be ready, hacky I know, I'll iron this out // Give time for the sever to be ready, hacky I know, I'll iron this out
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
// Server generates a QR code connection string // Server generates a QR code connection string
cp, err := s.PS.MakeConnectionParams() cp, err := s.SS.MakeConnectionParams()
s.Require().NoError(err) s.Require().NoError(err)
qr := cp.ToString() qr := cp.ToString()
@ -277,7 +298,7 @@ func (s *PairingServerSuite) TestGetOutboundIPWithFullServerE2e() {
err = ccp.FromString(qr) err = ccp.FromString(qr)
s.Require().NoError(err) s.Require().NoError(err)
c, err := NewPairingClient(nil, ccp, newAccountPayloadManagerConfig()) c, err := NewReceiverClient(nil, ccp, NewReceiverClientConfig())
s.Require().NoError(err) s.Require().NoError(err)
thing, err := makeThingToSay() thing, err := makeThingToSay()

View File

@ -23,17 +23,16 @@ import (
"github.com/status-im/status-go/sqlite" "github.com/status-im/status-go/sqlite"
) )
const pathWalletRoot = "m/44'/60'/0'/0" const (
const pathEIP1581 = "m/43'/60'/1581'" pathWalletRoot = "m/44'/60'/0'/0"
const pathDefaultChat = pathEIP1581 + "/0'/0" pathEIP1581 = "m/43'/60'/1581'"
const pathDefaultWallet = pathWalletRoot + "/0" pathDefaultChat = pathEIP1581 + "/0'/0"
pathDefaultWallet = pathWalletRoot + "/0"
currentNetwork = "mainnet_rpc"
)
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet} var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
const keystoreDir = "keystore"
const currentNetwork = "mainnet_rpc"
func TestSyncDeviceSuite(t *testing.T) { func TestSyncDeviceSuite(t *testing.T) {
suite.Run(t, new(SyncDeviceSuite)) suite.Run(t, new(SyncDeviceSuite))
} }
@ -141,19 +140,20 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
require.NoError(s.T(), err) require.NoError(s.T(), err)
expectedKDFIterations := 1024 expectedKDFIterations := 1024
serverKeystoreDir := filepath.Join(serverTmpDir, keystoreDir) serverKeystoreDir := filepath.Join(serverTmpDir, keystoreDir)
serverPayloadSourceConfig := PayloadSourceConfig{ serverPayloadSourceConfig := &ReceiverServerConfig{
KeystorePath: serverKeystoreDir, ReceiverConfig: &ReceiverConfig{
DeviceType: "desktop",
PayloadSourceReceiverConfig: &PayloadSourceReceiverConfig{
KDFIterations: expectedKDFIterations,
NodeConfig: serverNodeConfig, NodeConfig: serverNodeConfig,
RootDataDir: serverTmpDir, KeystorePath: serverKeystoreDir,
DeviceType: "desktop",
KDFIterations: expectedKDFIterations,
SettingCurrentNetwork: currentNetwork, SettingCurrentNetwork: currentNetwork,
}, },
ServerConfig: new(ServerConfig),
} }
serverNodeConfig.RootDataDir = serverTmpDir
serverConfigBytes, err := json.Marshal(serverPayloadSourceConfig) serverConfigBytes, err := json.Marshal(serverPayloadSourceConfig)
require.NoError(s.T(), err) require.NoError(s.T(), err)
cs, err := StartUpPairingServer(serverBackend, Receiving, string(serverConfigBytes)) cs, err := StartUpReceiverServer(serverBackend, Receiving, string(serverConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
// generate some data for the client // generate some data for the client
@ -167,17 +167,18 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
clientActiveAccount, err := clientBackend.GetActiveAccount() clientActiveAccount, err := clientBackend.GetActiveAccount()
require.NoError(s.T(), err) require.NoError(s.T(), err)
clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir, clientActiveAccount.KeyUID) clientKeystorePath := filepath.Join(clientTmpDir, keystoreDir, clientActiveAccount.KeyUID)
clientPayloadSourceConfig := PayloadSourceConfig{ clientPayloadSourceConfig := SenderClientConfig{
KeystorePath: clientKeystorePath, SenderConfig: &SenderConfig{
DeviceType: "android", KeystorePath: clientKeystorePath,
PayloadSourceSenderConfig: &PayloadSourceSenderConfig{ DeviceType: "android",
KeyUID: clientActiveAccount.KeyUID, KeyUID: clientActiveAccount.KeyUID,
Password: s.password, Password: s.password,
}, },
ClientConfig: new(ClientConfig),
} }
clientConfigBytes, err := json.Marshal(clientPayloadSourceConfig) clientConfigBytes, err := json.Marshal(clientPayloadSourceConfig)
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpSendingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
serverBrowserAPI := serverBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API) serverBrowserAPI := serverBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
@ -206,18 +207,18 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsSender() {
require.False(s.T(), serverMessenger.HasPairedDevices()) require.False(s.T(), serverMessenger.HasPairedDevices())
// repeat local pairing, we should expect no error after receiver logged in // repeat local pairing, we should expect no error after receiver logged in
cs, err = StartUpPairingServer(serverBackend, Receiving, string(serverConfigBytes)) cs, err = StartUpReceiverServer(serverBackend, Receiving, string(serverConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpSendingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
require.True(s.T(), clientMessenger.HasPairedDevices()) require.True(s.T(), clientMessenger.HasPairedDevices())
require.True(s.T(), serverMessenger.HasPairedDevices()) require.True(s.T(), serverMessenger.HasPairedDevices())
// test if it's okay when account already exist but not logged in // test if it's okay when account already exist but not logged in
require.NoError(s.T(), serverBackend.Logout()) require.NoError(s.T(), serverBackend.Logout())
cs, err = StartUpPairingServer(serverBackend, Receiving, string(serverConfigBytes)) cs, err = StartUpReceiverServer(serverBackend, Receiving, string(serverConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpSendingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
} }
@ -235,17 +236,18 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
serverActiveAccount, err := serverBackend.GetActiveAccount() serverActiveAccount, err := serverBackend.GetActiveAccount()
require.NoError(s.T(), err) require.NoError(s.T(), err)
serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir, serverActiveAccount.KeyUID) serverKeystorePath := filepath.Join(serverTmpDir, keystoreDir, serverActiveAccount.KeyUID)
var config = PayloadSourceConfig{ var config = &SenderServerConfig{
KeystorePath: serverKeystorePath, SenderConfig: &SenderConfig{
DeviceType: "desktop", KeystorePath: serverKeystorePath,
PayloadSourceSenderConfig: &PayloadSourceSenderConfig{ DeviceType: "desktop",
KeyUID: serverActiveAccount.KeyUID, KeyUID: serverActiveAccount.KeyUID,
Password: s.password, Password: s.password,
}, },
ServerConfig: new(ServerConfig),
} }
configBytes, err := json.Marshal(config) configBytes, err := json.Marshal(config)
require.NoError(s.T(), err) require.NoError(s.T(), err)
cs, err := StartUpPairingServer(serverBackend, Sending, string(configBytes)) cs, err := StartUpSenderServer(serverBackend, Sending, string(configBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
// generate some data for the server // generate some data for the server
@ -264,19 +266,20 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
require.NoError(s.T(), err) require.NoError(s.T(), err)
expectedKDFIterations := 2048 expectedKDFIterations := 2048
clientKeystoreDir := filepath.Join(clientTmpDir, keystoreDir) clientKeystoreDir := filepath.Join(clientTmpDir, keystoreDir)
clientPayloadSourceConfig := PayloadSourceConfig{ clientPayloadSourceConfig := ReceiverClientConfig{
KeystorePath: clientKeystoreDir, ReceiverConfig: &ReceiverConfig{
DeviceType: "iphone", KeystorePath: clientKeystoreDir,
PayloadSourceReceiverConfig: &PayloadSourceReceiverConfig{ DeviceType: "iphone",
KDFIterations: expectedKDFIterations, KDFIterations: expectedKDFIterations,
NodeConfig: clientNodeConfig, NodeConfig: clientNodeConfig,
RootDataDir: clientTmpDir,
SettingCurrentNetwork: currentNetwork, SettingCurrentNetwork: currentNetwork,
}, },
ClientConfig: new(ClientConfig),
} }
clientNodeConfig.RootDataDir = clientTmpDir
clientConfigBytes, err := json.Marshal(clientPayloadSourceConfig) clientConfigBytes, err := json.Marshal(clientPayloadSourceConfig)
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpReceivingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
clientBrowserAPI := clientBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API) clientBrowserAPI := clientBackend.StatusNode().BrowserService().APIs()[0].Service.(*browsers.API)
@ -305,18 +308,18 @@ func (s *SyncDeviceSuite) TestPairingSyncDeviceClientAsReceiver() {
require.False(s.T(), clientMessenger.HasPairedDevices()) require.False(s.T(), clientMessenger.HasPairedDevices())
// repeat local pairing, we should expect no error after receiver logged in // repeat local pairing, we should expect no error after receiver logged in
cs, err = StartUpPairingServer(serverBackend, Sending, string(configBytes)) cs, err = StartUpSenderServer(serverBackend, Sending, string(configBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpReceivingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
require.True(s.T(), serverMessenger.HasPairedDevices()) require.True(s.T(), serverMessenger.HasPairedDevices())
require.True(s.T(), clientMessenger.HasPairedDevices()) require.True(s.T(), clientMessenger.HasPairedDevices())
// test if it's okay when account already exist but not logged in // test if it's okay when account already exist but not logged in
require.NoError(s.T(), clientBackend.Logout()) require.NoError(s.T(), clientBackend.Logout())
cs, err = StartUpPairingServer(serverBackend, Sending, string(configBytes)) cs, err = StartUpSenderServer(serverBackend, Sending, string(configBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
err = StartUpPairingClient(clientBackend, cs, string(clientConfigBytes)) err = StartUpReceivingClient(clientBackend, cs, string(clientConfigBytes))
require.NoError(s.T(), err) require.NoError(s.T(), err)
} }

View File

@ -61,6 +61,8 @@ func (t *timeoutManager) run(terminate func(), exit chan struct{}) {
return return
case <-time.After(time.Duration(t.timeout) * time.Millisecond): case <-time.After(time.Duration(t.timeout) * time.Millisecond):
terminate() terminate()
// TODO fire signal to let UI know
// https://github.com/status-im/status-go/issues/3305
return return
} }
} }