status-go/server/payload_manager.go

447 lines
11 KiB
Go
Raw Normal View History

package server
import (
"crypto/ecdsa"
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/golang/protobuf/proto"
2022-06-24 23:09:01 +00:00
"github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/eth-node/keystore"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
2022-07-01 15:37:53 +00:00
type PayloadManager interface {
Mount() error
Receive(data []byte) error
ToSend() []byte
Received() []byte
}
type PairingPayloadManagerConfig struct {
DB *multiaccounts.Database
KeystorePath, KeyUID, Password string
}
// PairingPayloadManager is responsible for the whole lifecycle of a PairingPayload
2022-06-29 15:21:22 +00:00
type PairingPayloadManager struct {
pem *PayloadEncryptionManager
ppm *PairingPayloadMarshaller
2022-07-01 15:37:53 +00:00
ppr PayloadRepository
2022-06-29 15:21:22 +00:00
}
2022-07-01 15:37:53 +00:00
func NewPairingPayloadManager(pk *ecdsa.PrivateKey, config *PairingPayloadManagerConfig) (*PairingPayloadManager, error) {
2022-06-29 15:21:22 +00:00
pem, err := NewPayloadEncryptionManager(pk)
if err != nil {
return nil, err
}
return &PairingPayloadManager{
pem: pem,
ppm: NewPairingPayloadMarshaller(),
2022-07-01 15:37:53 +00:00
ppr: NewPairingPayloadRepository(config),
2022-06-29 15:21:22 +00:00
}, nil
}
2022-07-01 15:37:53 +00:00
func (ppm *PairingPayloadManager) Mount() error {
err := ppm.ppr.LoadFromSource()
if err != nil {
return err
}
ppm.ppm.LoadPayload(ppm.ppr.GetPayload())
pb, err := ppm.ppm.MarshalToProtobuf()
if err != nil {
return err
}
return ppm.pem.Encrypt(pb)
}
func (ppm *PairingPayloadManager) Receive(data []byte) error {
err := ppm.pem.Decrypt(data)
if err != nil {
return err
}
err = ppm.ppm.UnmarshalProtobuf(ppm.pem.Received())
if err != nil {
return err
}
ppm.ppr.LoadPayload(ppm.ppm.GetPayload())
return ppm.ppr.StoreToSource()
}
func (ppm *PairingPayloadManager) ToSend() []byte {
return ppm.pem.ToSend()
}
func (ppm *PairingPayloadManager) Received() []byte {
return ppm.pem.Received()
}
// EncryptionPayload represents the plain text and encrypted text of payload data
2022-06-29 15:21:22 +00:00
type EncryptionPayload struct {
plain []byte
encrypted []byte
}
2022-07-01 15:37:53 +00:00
// PayloadEncryptionManager is responsible for encrypting and decrypting payload data
2022-06-29 15:21:22 +00:00
type PayloadEncryptionManager struct {
aesKey []byte
2022-06-29 15:21:22 +00:00
toSend *EncryptionPayload
received *EncryptionPayload
}
2022-06-29 15:21:22 +00:00
func NewPayloadEncryptionManager(pk *ecdsa.PrivateKey) (*PayloadEncryptionManager, error) {
ek, err := makeEncryptionKey(pk)
if err != nil {
return nil, err
}
2022-06-29 15:21:22 +00:00
return &PayloadEncryptionManager{ek, new(EncryptionPayload), new(EncryptionPayload)}, nil
}
2022-07-01 15:37:53 +00:00
func (pem *PayloadEncryptionManager) Encrypt(data []byte) error {
2022-06-29 15:21:22 +00:00
ep, err := common.Encrypt(data, pem.aesKey, rand.Reader)
if err != nil {
return err
}
2022-06-29 15:21:22 +00:00
pem.toSend.plain = data
pem.toSend.encrypted = ep
return nil
}
2022-07-01 15:37:53 +00:00
func (pem *PayloadEncryptionManager) Decrypt(data []byte) error {
2022-06-29 15:21:22 +00:00
pd, err := common.Decrypt(data, pem.aesKey)
if err != nil {
return err
}
2022-06-29 15:21:22 +00:00
pem.received.encrypted = data
pem.received.plain = pd
return nil
}
2022-06-29 15:21:22 +00:00
func (pem *PayloadEncryptionManager) ToSend() []byte {
return pem.toSend.encrypted
}
2022-06-29 15:21:22 +00:00
func (pem *PayloadEncryptionManager) Received() []byte {
return pem.received.plain
}
2022-06-29 15:21:22 +00:00
func (pem *PayloadEncryptionManager) ResetPayload() {
pem.toSend = new(EncryptionPayload)
pem.received = new(EncryptionPayload)
}
2022-06-29 15:21:22 +00:00
// PairingPayload represents the payload structure a PairingServer handles
type PairingPayload struct {
keys map[string][]byte
multiaccount *multiaccounts.Account
password string
}
2022-06-29 15:21:22 +00:00
// PairingPayloadMarshaller is responsible for marshalling and unmarshalling PairingServer payload data
type PairingPayloadMarshaller struct {
*PairingPayload
}
func NewPairingPayloadMarshaller() *PairingPayloadMarshaller {
return &PairingPayloadMarshaller{PairingPayload: new(PairingPayload)}
}
2022-07-01 15:37:53 +00:00
func (ppm *PairingPayloadMarshaller) LoadPayload(payload *PairingPayload) {
2022-06-29 15:21:22 +00:00
ppm.PairingPayload = payload
}
2022-07-01 15:37:53 +00:00
func (ppm *PairingPayloadMarshaller) GetPayload() *PairingPayload {
return ppm.PairingPayload
}
2022-06-29 15:21:22 +00:00
func (ppm *PairingPayloadMarshaller) MarshalToProtobuf() ([]byte, error) {
return proto.Marshal(&protobuf.LocalPairingPayload{
Keys: ppm.accountKeysToProtobuf(),
Multiaccount: ppm.multiaccountToProtobuf(),
Password: ppm.password,
})
}
func (ppm *PairingPayloadMarshaller) 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 *PairingPayloadMarshaller) multiaccountToProtobuf() *protobuf.MultiAccount {
var colourHashes []*protobuf.MultiAccount_ColourHash
for _, index := range ppm.multiaccount.ColorHash {
var i []int64
for _, is := range index {
i = append(i, int64(is))
}
colourHashes = append(colourHashes, &protobuf.MultiAccount_ColourHash{Index: i})
}
var identityImages []*protobuf.MultiAccount_IdentityImage
for _, ii := range ppm.multiaccount.Images {
identityImages = append(identityImages, &protobuf.MultiAccount_IdentityImage{
KeyUid: ii.KeyUID,
Name: ii.Name,
Payload: ii.Payload,
Width: int64(ii.Width),
Height: int64(ii.Height),
Filesize: int64(ii.FileSize),
ResizeTarget: int64(ii.ResizeTarget),
Clock: ii.Clock,
})
}
return &protobuf.MultiAccount{
Name: ppm.multiaccount.Name,
Timestamp: ppm.multiaccount.Timestamp,
Identicon: ppm.multiaccount.Identicon,
ColorHash: colourHashes,
ColorId: ppm.multiaccount.ColorID,
KeycardPairing: ppm.multiaccount.KeycardPairing,
KeyUid: ppm.multiaccount.KeyUID,
Images: identityImages,
}
2022-06-28 23:08:57 +00:00
}
2022-06-29 15:21:22 +00:00
func (ppm *PairingPayloadMarshaller) UnmarshalProtobuf(data []byte) error {
pb := new(protobuf.LocalPairingPayload)
err := proto.Unmarshal(data, pb)
if err != nil {
return err
}
2022-06-29 15:21:22 +00:00
ppm.accountKeysFromProtobuf(pb.Keys)
ppm.multiaccountFromProtobuf(pb.Multiaccount)
ppm.password = pb.Password
return nil
}
func (ppm *PairingPayloadMarshaller) accountKeysFromProtobuf(pbKeys []*protobuf.LocalPairingPayload_Key) {
if ppm.keys == nil {
ppm.keys = make(map[string][]byte)
}
for _, key := range pbKeys {
ppm.keys[key.Name] = key.Data
}
}
func (ppm *PairingPayloadMarshaller) multiaccountFromProtobuf(pbMultiAccount *protobuf.MultiAccount) {
var colourHash [][]int
for _, index := range pbMultiAccount.ColorHash {
var i []int
for _, is := range index.Index {
i = append(i, int(is))
}
colourHash = append(colourHash, i)
}
var identityImages []images.IdentityImage
for _, ii := range pbMultiAccount.Images {
identityImages = append(identityImages, images.IdentityImage{
KeyUID: ii.KeyUid,
Name: ii.Name,
Payload: ii.Payload,
Width: int(ii.Width),
Height: int(ii.Height),
FileSize: int(ii.Filesize),
ResizeTarget: int(ii.ResizeTarget),
Clock: ii.Clock,
})
}
ppm.multiaccount = &multiaccounts.Account{
Name: pbMultiAccount.Name,
Timestamp: pbMultiAccount.Timestamp,
Identicon: pbMultiAccount.Identicon,
ColorHash: colourHash,
ColorID: pbMultiAccount.ColorId,
KeycardPairing: pbMultiAccount.KeycardPairing,
KeyUID: pbMultiAccount.KeyUid,
Images: identityImages,
}
}
2022-07-01 15:37:53 +00:00
type PayloadHandler interface {
LoadPayload(*PairingPayload)
GetPayload() *PairingPayload
}
type PayloadRepository interface {
PayloadHandler
LoadFromSource() error
StoreToSource() error
}
2022-06-29 15:21:22 +00:00
// PairingPayloadRepository is responsible for loading, parsing, validating and storing PairingServer payload data
type PairingPayloadRepository struct {
*PairingPayload
multiaccountsDB *multiaccounts.Database
2022-07-01 15:37:53 +00:00
keystorePath, keyUID string
2022-06-29 15:21:22 +00:00
}
2022-07-01 15:37:53 +00:00
func NewPairingPayloadRepository(config *PairingPayloadManagerConfig) *PairingPayloadRepository {
ppr := &PairingPayloadRepository{
PairingPayload: new(PairingPayload),
2022-06-29 15:21:22 +00:00
}
2022-07-01 15:37:53 +00:00
if config == nil {
return ppr
}
ppr.multiaccountsDB = config.DB
ppr.keystorePath = config.KeystorePath
ppr.keyUID = config.KeyUID
ppr.password = config.Password
return ppr
2022-06-29 15:21:22 +00:00
}
2022-07-01 15:37:53 +00:00
func (ppr *PairingPayloadRepository) LoadPayload(payload *PairingPayload) {
2022-06-29 15:21:22 +00:00
ppr.PairingPayload = payload
}
2022-07-01 15:37:53 +00:00
func (ppr *PairingPayloadRepository) GetPayload() *PairingPayload {
return ppr.PairingPayload
}
func (ppr *PairingPayloadRepository) LoadFromSource() error {
err := ppr.loadKeys(ppr.keystorePath)
if err != nil {
return err
}
2022-06-29 15:21:22 +00:00
2022-07-01 15:37:53 +00:00
err = ppr.validateKeys(ppr.password)
2022-06-29 15:21:22 +00:00
if err != nil {
return err
}
2022-07-01 15:37:53 +00:00
ppr.multiaccount, err = ppr.multiaccountsDB.GetAccount(ppr.keyUID)
2022-06-29 15:21:22 +00:00
if err != nil {
return err
}
return nil
}
2022-06-29 15:21:22 +00:00
func (ppr *PairingPayloadRepository) loadKeys(keyStorePath string) error {
ppr.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)
}
2022-06-29 15:21:22 +00:00
ppr.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
}
2022-07-01 15:37:53 +00:00
func (ppr *PairingPayloadRepository) StoreToSource() error {
err := ppr.validateKeys(ppr.password)
2022-06-24 23:09:01 +00:00
if err != nil {
return err
}
2022-07-01 15:37:53 +00:00
err = ppr.storeKeys(ppr.keystorePath)
2022-06-24 23:09:01 +00:00
if err != nil {
return err
}
2022-06-29 15:21:22 +00:00
err = ppr.storeMultiAccount()
2022-06-24 23:09:01 +00:00
if err != nil {
return err
}
2022-06-29 15:21:22 +00:00
// TODO install PublicKey into settings, probably do this outside of StoreToSource
2022-06-24 23:09:01 +00:00
return nil
}
2022-06-29 15:21:22 +00:00
func (ppr *PairingPayloadRepository) validateKeys(password string) error {
for _, key := range ppr.keys {
2022-06-24 23:09:01 +00:00
k, err := keystore.DecryptKey(key, password)
if err != nil {
return err
}
err = generator.ValidateKeystoreExtendedKey(k)
if err != nil {
return err
}
}
return nil
}
2022-06-29 15:21:22 +00:00
func (ppr *PairingPayloadRepository) storeKeys(keyStorePath string) error {
for name, data := range ppr.keys {
2022-06-24 23:09:01 +00:00
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
}
2022-06-29 15:21:22 +00:00
func (ppr *PairingPayloadRepository) storeMultiAccount() error {
return ppr.multiaccountsDB.SaveAccount(*ppr.multiaccount)
}