status-go/server/connection.go

197 lines
4.7 KiB
Go

package server
import (
"crypto/ecdsa"
"encoding/asn1"
"fmt"
"math/big"
"net"
"net/url"
"strings"
"time"
"github.com/btcsuite/btcutil/base58"
)
type ConnectionParamVersion int
type Mode int
const (
Version1 ConnectionParamVersion = iota + 1
)
const (
Receiving Mode = iota + 1
Sending
)
type ConnectionParams struct {
version ConnectionParamVersion
netIP net.IP
port int
privateKey *ecdsa.PrivateKey
notBefore time.Time
serverMode Mode
}
func NewConnectionParams(netIP net.IP, port int, privateKey *ecdsa.PrivateKey, notBefore time.Time, mode Mode) *ConnectionParams {
cp := new(ConnectionParams)
cp.version = Version1
cp.netIP = netIP
cp.port = port
cp.privateKey = privateKey
cp.notBefore = notBefore
cp.serverMode = mode
return cp
}
// ToString generates a string required for generating a secure connection to another Status device.
//
// The returned string will look like below:
// - "2:4FHRnp:H6G:6jpbvo2ucrtrnpXXF4DQYuysh697isH9ppd2aT8uSRDh:eQUriVtGtkWhPJFeLZjF:2"
//
// Format bytes encoded into a base58 string, delimited by ":"
// - version
// - net.IP
// - port
// - ecdsa.PrivateKey D field
// - asn1.Marshal time.Time
// - server mode
func (cp *ConnectionParams) ToString() (string, error) {
v := base58.Encode(new(big.Int).SetInt64(int64(cp.version)).Bytes())
ip := base58.Encode(cp.netIP)
p := base58.Encode(new(big.Int).SetInt64(int64(cp.port)).Bytes())
k := base58.Encode(cp.privateKey.D.Bytes())
tb, err := asn1.Marshal(cp.notBefore.UTC())
if err != nil {
return "", err
}
t := base58.Encode(tb)
m := base58.Encode(new(big.Int).SetInt64(int64(cp.serverMode)).Bytes())
return fmt.Sprintf("%s:%s:%s:%s:%s:%s", v, ip, p, k, t, m), nil
}
// FromString parses a connection params string required for to securely connect to another Status device.
// This function parses a connection string generated by ToString
func (cp *ConnectionParams) FromString(s string) error {
requiredParams := 6
sData := strings.Split(s, ":")
if len(sData) != requiredParams {
return fmt.Errorf("expected data '%s' to have length of '%d', received '%d'", s, requiredParams, len(sData))
}
cp.version = ConnectionParamVersion(new(big.Int).SetBytes(base58.Decode(sData[0])).Int64())
cp.netIP = base58.Decode(sData[1])
cp.port = int(new(big.Int).SetBytes(base58.Decode(sData[2])).Int64())
cp.privateKey = ToECDSA(base58.Decode(sData[3]))
t := time.Time{}
_, err := asn1.Unmarshal(base58.Decode(sData[4]), &t)
if err != nil {
return err
}
cp.notBefore = t
cp.serverMode = Mode(new(big.Int).SetBytes(base58.Decode(sData[5])).Int64())
return cp.validate()
}
func (cp *ConnectionParams) validate() error {
err := cp.validateVersion()
if err != nil {
return err
}
err = cp.validateNetIP()
if err != nil {
return err
}
err = cp.validatePort()
if err != nil {
return err
}
err = cp.validatePrivateKey()
if err != nil {
return err
}
err = cp.validateNotBefore()
if err != nil {
return err
}
return cp.validateServerMode()
}
func (cp *ConnectionParams) validateVersion() error {
switch cp.version {
case Version1:
return nil
default:
return fmt.Errorf("unsupported version '%d'", cp.version)
}
}
func (cp *ConnectionParams) validateNetIP() error {
if ok := net.ParseIP(cp.netIP.String()); ok == nil {
return fmt.Errorf("invalid net ip '%s'", cp.netIP)
}
return nil
}
func (cp *ConnectionParams) validatePort() error {
if cp.port > 0 && cp.port < 0x10000 {
return nil
}
return fmt.Errorf("port '%d' outside of bounds of 1 - 65535", cp.port)
}
func (cp *ConnectionParams) validatePrivateKey() error {
switch {
case cp.privateKey.D == nil, cp.privateKey.D.Cmp(big.NewInt(0)) == 0:
return fmt.Errorf("private key D not set")
case cp.privateKey.PublicKey.X == nil, cp.privateKey.PublicKey.X.Cmp(big.NewInt(0)) == 0:
return fmt.Errorf("public key X not set")
case cp.privateKey.PublicKey.Y == nil, cp.privateKey.PublicKey.Y.Cmp(big.NewInt(0)) == 0:
return fmt.Errorf("public key Y not set")
default:
return nil
}
}
func (cp *ConnectionParams) validateNotBefore() error {
if cp.notBefore.IsZero() {
return fmt.Errorf("notBefore time is zero")
}
return nil
}
func (cp *ConnectionParams) validateServerMode() error {
switch cp.serverMode {
case Receiving, Sending:
return nil
default:
return fmt.Errorf("invalid server mode '%d'", cp.serverMode)
}
}
// Generate returns a *url.URL and encoded pem.Block generated from ConnectionParams set fields
func (cp *ConnectionParams) Generate() (*url.URL, []byte, error) {
u := &url.URL{
Scheme: "https",
Host: fmt.Sprintf("%s:%d", cp.netIP, cp.port),
}
_, pem, err := GenerateCertFromKey(cp.privateKey, cp.notBefore, cp.netIP.String())
if err != nil {
return nil, nil, err
}
return u, pem, nil
}