status-go/server/server_pairing.go

201 lines
4.4 KiB
Go

package server
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"time"
"github.com/gorilla/sessions"
"github.com/status-im/status-go/multiaccounts"
)
type PairingServer struct {
Server
PayloadManager
pk *ecdsa.PublicKey
ek []byte
mode Mode
cookieStore *sessions.CookieStore
}
type Config struct {
// Connection fields
PK *ecdsa.PublicKey
EK []byte
Cert *tls.Certificate
Hostname string
Mode Mode
// Payload management fields
*PairingPayloadManagerConfig
}
func makeCookieStore() (*sessions.CookieStore, error) {
auth := make([]byte, 64)
_, err := rand.Read(auth)
if err != nil {
return nil, err
}
enc := make([]byte, 32)
_, err = rand.Read(enc)
if err != nil {
return nil, err
}
return sessions.NewCookieStore(auth, enc), nil
}
// NewPairingServer returns a *PairingServer init from the given *Config
func NewPairingServer(config *Config) (*PairingServer, error) {
pm, err := NewPairingPayloadManager(config.EK, config.PairingPayloadManagerConfig)
if err != nil {
return nil, err
}
cs, err := makeCookieStore()
if err != nil {
return nil, err
}
return &PairingServer{Server: NewServer(
config.Cert,
config.Hostname,
),
pk: config.PK,
ek: config.EK,
mode: config.Mode,
PayloadManager: pm,
cookieStore: cs,
}, nil
}
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
func (s *PairingServer) MakeConnectionParams() (*ConnectionParams, error) {
netIP := net.ParseIP(s.hostname)
if netIP == nil {
return nil, fmt.Errorf("invalid ip address given '%s'", s.hostname)
}
netIP4 := netIP.To4()
if netIP4 != nil {
netIP = netIP4
}
if s.port == 0 {
return nil, fmt.Errorf("port is 0, listener is not yet set")
}
return NewConnectionParams(netIP, s.port, s.pk, s.ek, s.mode), nil
}
func (s *PairingServer) StartPairing() error {
switch s.mode {
case Receiving:
return s.startReceivingAccountData()
case Sending:
return s.startSendingAccountData()
default:
return fmt.Errorf("invalid server mode '%d'", s.mode)
}
}
func (s *PairingServer) startReceivingAccountData() error {
s.SetHandlers(HandlerPatternMap{
pairingReceive: handlePairingReceive(s),
pairingChallenge: handlePairingChallenge(s),
})
return s.Start()
}
func (s *PairingServer) startSendingAccountData() error {
err := s.Mount()
if err != nil {
return err
}
s.SetHandlers(HandlerPatternMap{
pairingSend: challengeMiddleware(s, handlePairingSend(s)),
pairingChallenge: handlePairingChallenge(s),
})
return s.Start()
}
// MakeFullPairingServer generates a fully configured and randomly seeded PairingServer
func MakeFullPairingServer(db *multiaccounts.Database, mode Mode, storeConfig PairingPayloadSourceConfig) (*PairingServer, error) {
tlsKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
}
AESKey := make([]byte, 32)
_, err = rand.Read(AESKey)
if err != nil {
return nil, err
}
outboundIP, err := GetOutboundIP()
if err != nil {
return nil, err
}
tlsCert, _, err := GenerateCertFromKey(tlsKey, time.Now(), outboundIP.String())
if err != nil {
return nil, err
}
return NewPairingServer(&Config{
// Things that can be generated, and CANNOT come from the app client (well they could be this is better)
PK: &tlsKey.PublicKey,
EK: AESKey,
Cert: &tlsCert,
Hostname: outboundIP.String(),
// Things that can't be generated, but DO come from the app client
Mode: mode,
PairingPayloadManagerConfig: &PairingPayloadManagerConfig{
// Things that can't be generated, but DO NOT come from app client
DB: db,
// Things that can't be generated, but DO come from the app client
PairingPayloadSourceConfig: storeConfig,
},
})
}
// StartUpPairingServer generates a PairingServer, starts the pairing server in the correct mode
// and returns the ConnectionParams string to allow a PairingClient to make a successful connection.
func StartUpPairingServer(db *multiaccounts.Database, mode Mode, configJSON string) (string, error) {
var conf PairingPayloadSourceConfig
err := json.Unmarshal([]byte(configJSON), &conf)
if err != nil {
return "", err
}
ps, err := MakeFullPairingServer(db, mode, conf)
if err != nil {
return "", err
}
err = ps.StartPairing()
if err != nil {
return "", err
}
cp, err := ps.MakeConnectionParams()
if err != nil {
return "", err
}
return cp.ToString(), nil
}