LAN connection string (#2653)
* Version bump * Implemented lan connection string functionality Also added more robust testing * Added ConnectionParams struct and related funcs * Add server mode to ConnectionParams
This commit is contained in:
parent
7f149f93c1
commit
05b8ddf57a
2
go.mod
2
go.mod
|
@ -15,6 +15,7 @@ replace github.com/raulk/go-watchdog v1.2.0 => github.com/status-im/go-watchdog
|
||||||
require (
|
require (
|
||||||
github.com/anacrolix/torrent v1.41.0
|
github.com/anacrolix/torrent v1.41.0
|
||||||
github.com/beevik/ntp v0.2.0
|
github.com/beevik/ntp v0.2.0
|
||||||
|
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
|
||||||
github.com/cenkalti/backoff/v3 v3.2.2
|
github.com/cenkalti/backoff/v3 v3.2.2
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/deckarep/golang-set v1.8.0
|
github.com/deckarep/golang-set v1.8.0
|
||||||
|
@ -104,7 +105,6 @@ require (
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||||
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect
|
||||||
github.com/btcsuite/btcd v0.22.0-beta // indirect
|
github.com/btcsuite/btcd v0.22.0-beta // indirect
|
||||||
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||||
github.com/cheekybits/genny v1.0.0 // indirect
|
github.com/cheekybits/genny v1.0.0 // indirect
|
||||||
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 // indirect
|
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327 // indirect
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
@ -110,13 +111,8 @@ func PublicTLSCert() (string, error) {
|
||||||
return globalPem, nil
|
return globalPem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCertFromKey(pk *ecdsa.PrivateKey, ttl time.Duration, hostname string) (tls.Certificate, []byte, error) {
|
func GenerateCertFromKey(pk *ecdsa.PrivateKey, from time.Time, hostname string) (tls.Certificate, []byte, error) {
|
||||||
// TODO fix, this isn't deterministic,
|
cert := GenerateX509Cert(makeSerialNumberFromKey(pk), from, from.Add(time.Hour), hostname)
|
||||||
|
|
||||||
notBefore := time.Now()
|
|
||||||
notAfter := notBefore.Add(ttl)
|
|
||||||
|
|
||||||
cert := GenerateX509Cert(makeSerialNumberFromKey(pk), notBefore, notAfter, hostname)
|
|
||||||
certPem, keyPem, err := GenerateX509PEMs(cert, pk)
|
certPem, keyPem, err := GenerateX509PEMs(cert, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, nil, err
|
return tls.Certificate{}, nil, err
|
||||||
|
@ -127,6 +123,16 @@ func GenerateCertFromKey(pk *ecdsa.PrivateKey, ttl time.Duration, hostname strin
|
||||||
return tls.Certificate{}, nil, err
|
return tls.Certificate{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(certPem)
|
||||||
|
if block == nil {
|
||||||
|
return tls.Certificate{}, nil, fmt.Errorf("failed to decode certPem")
|
||||||
|
}
|
||||||
|
leaf, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, nil, err
|
||||||
|
}
|
||||||
|
tlsCert.Leaf = leaf
|
||||||
|
|
||||||
return tlsCert, certPem, nil
|
return tlsCert, certPem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,54 +12,19 @@ func TestCerts(t *testing.T) {
|
||||||
suite.Run(t, new(CertsSuite))
|
suite.Run(t, new(CertsSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
X = "7744735542292224619198421067303535767629647588258222392379329927711683109548"
|
|
||||||
Y = "6855516769916529066379811647277920115118980625614889267697023742462401590771"
|
|
||||||
D = "38564357061962143106230288374146033267100509055924181407058066820384455255240"
|
|
||||||
DB58 = "6jpbvo2ucrtrnpXXF4DQYuysh697isH9ppd2aT8uSRDh"
|
|
||||||
SN = "91849736469742262272885892667727604096707836853856473239722372976236128900962"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CertsSuite struct {
|
type CertsSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
TestKeyComponents
|
||||||
X *big.Int
|
TestCertComponents
|
||||||
Y *big.Int
|
|
||||||
D *big.Int
|
|
||||||
DBytes []byte
|
|
||||||
SN *big.Int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertsSuite) SetupSuite() {
|
func (s *CertsSuite) SetupSuite() {
|
||||||
var ok bool
|
s.SetupKeyComponents(s.T())
|
||||||
|
s.SetupCertComponents(s.T())
|
||||||
s.X, ok = new(big.Int).SetString(X, 10)
|
|
||||||
s.Require().True(ok)
|
|
||||||
|
|
||||||
s.Y, ok = new(big.Int).SetString(Y, 10)
|
|
||||||
s.Require().True(ok)
|
|
||||||
|
|
||||||
s.D, ok = new(big.Int).SetString(D, 10)
|
|
||||||
s.Require().True(ok)
|
|
||||||
|
|
||||||
s.DBytes = base58.Decode(DB58)
|
|
||||||
s.Require().Exactly(s.D.Bytes(), s.DBytes)
|
|
||||||
|
|
||||||
s.SN, ok = new(big.Int).SetString(SN, 10)
|
|
||||||
s.Require().True(ok)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertsSuite) Test_makeSerialNumberFromKey() {
|
func (s *CertsSuite) Test_makeSerialNumberFromKey() {
|
||||||
pk := &ecdsa.PrivateKey{
|
s.Require().Zero(makeSerialNumberFromKey(s.PK).Cmp(s.SN))
|
||||||
PublicKey: ecdsa.PublicKey{
|
|
||||||
Curve: elliptic.P256(),
|
|
||||||
X: s.X,
|
|
||||||
Y: s.Y,
|
|
||||||
},
|
|
||||||
D: s.D,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Require().Zero(makeSerialNumberFromKey(pk).Cmp(s.SN))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CertsSuite) TestToECDSA() {
|
func (s *CertsSuite) TestToECDSA() {
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"encoding/asn1"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil/base58"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
X = "7744735542292224619198421067303535767629647588258222392379329927711683109548"
|
||||||
|
Y = "6855516769916529066379811647277920115118980625614889267697023742462401590771"
|
||||||
|
D = "38564357061962143106230288374146033267100509055924181407058066820384455255240"
|
||||||
|
DB58 = "6jpbvo2ucrtrnpXXF4DQYuysh697isH9ppd2aT8uSRDh"
|
||||||
|
SN = "91849736469742262272885892667727604096707836853856473239722372976236128900962"
|
||||||
|
CertTime = "eQUriVtGtkWhPJFeLZjF"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestKeyComponents struct {
|
||||||
|
X *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
D *big.Int
|
||||||
|
DBytes []byte
|
||||||
|
PK *ecdsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tk *TestKeyComponents) SetupKeyComponents(t *testing.T) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
tk.X, ok = new(big.Int).SetString(X, 10)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
tk.Y, ok = new(big.Int).SetString(Y, 10)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
tk.D, ok = new(big.Int).SetString(D, 10)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
tk.DBytes = base58.Decode(DB58)
|
||||||
|
require.Exactly(t, tk.D.Bytes(), tk.DBytes)
|
||||||
|
|
||||||
|
tk.PK = &ecdsa.PrivateKey{
|
||||||
|
PublicKey: ecdsa.PublicKey{
|
||||||
|
Curve: elliptic.P256(),
|
||||||
|
X: tk.X,
|
||||||
|
Y: tk.Y,
|
||||||
|
},
|
||||||
|
D: tk.D,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCertComponents struct {
|
||||||
|
NotBefore, NotAfter time.Time
|
||||||
|
SN *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcc *TestCertComponents) SetupCertComponents(t *testing.T) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
tcc.SN, ok = new(big.Int).SetString(SN, 10)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
_, err := asn1.Unmarshal(base58.Decode(CertTime), &tcc.NotBefore)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tcc.NotAfter = tcc.NotBefore.Add(time.Hour)
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
connectionString = "2:4FHRnp:Q4:6jpbvo2ucrtrnpXXF4DQYuysh697isH9ppd2aT8uSRDh:eQUriVtGtkWhPJFeLZjF:3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConnectionParamsSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(ConnectionParamsSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionParamsSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
TestKeyComponents
|
||||||
|
TestCertComponents
|
||||||
|
|
||||||
|
server *PairingServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionParamsSuite) SetupSuite() {
|
||||||
|
s.SetupKeyComponents(s.T())
|
||||||
|
s.SetupCertComponents(s.T())
|
||||||
|
|
||||||
|
cert, _, err := GenerateCertFromKey(s.PK, s.NotBefore, defaultIP.String())
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
bs := NewServer(&cert, defaultIP.String())
|
||||||
|
bs.port = 1337
|
||||||
|
|
||||||
|
s.server = &PairingServer{
|
||||||
|
Server: bs,
|
||||||
|
pk: s.PK,
|
||||||
|
mode: Sending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionParamsSuite) TestConnectionParams_ToString() {
|
||||||
|
cp, err := s.server.MakeConnectionParams()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
cps, err := cp.ToString()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Require().Equal(connectionString, cps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionParamsSuite) TestConnectionParams_Generate() {
|
||||||
|
cp := new(ConnectionParams)
|
||||||
|
err := cp.FromString(connectionString)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Require().Exactly(Sending, cp.serverMode)
|
||||||
|
|
||||||
|
u, c, err := cp.Generate()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.Require().Equal("https://127.0.0.1:1337", u.String())
|
||||||
|
s.Require().Equal(defaultIP.String(), u.Hostname())
|
||||||
|
s.Require().Equal("1337", u.Port())
|
||||||
|
|
||||||
|
// Parse cert PEM into x509 cert
|
||||||
|
block, _ := pem.Decode(c)
|
||||||
|
s.Require().NotNil(block)
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
// Compare cert values
|
||||||
|
cl := s.server.cert.Leaf
|
||||||
|
s.Require().NotEqual(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)
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,25 +30,46 @@ func testHandler(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOutboundIPWithFullServerE2e(t *testing.T) {
|
func TestGetOutboundIPWithFullServerE2e(t *testing.T) {
|
||||||
|
// Get 3 key components for tls.cert generation
|
||||||
|
// 1) Ephemeral private key
|
||||||
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// 2) Device outbound IP address
|
||||||
ip, err := GetOutboundIP()
|
ip, err := GetOutboundIP()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cert, certPem, err := GenerateCertFromKey(pk, time.Hour, ip.String())
|
// 3) NotBefore time
|
||||||
|
certTime := time.Now()
|
||||||
|
|
||||||
|
// Generate tls.Certificate and Server
|
||||||
|
cert, _, err := GenerateCertFromKey(pk, certTime, ip.String())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
s := NewPairingServer(&Config{&cert, ip.String()})
|
s := NewPairingServer(&Config{pk, &cert, ip.String(), Sending})
|
||||||
|
|
||||||
s.SetHandlers(HandlerPatternMap{"/hello": testHandler(t)})
|
s.SetHandlers(HandlerPatternMap{"/hello": testHandler(t)})
|
||||||
|
|
||||||
err = s.Start()
|
err = s.Start()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Give time for the sever to be ready, hacky I know
|
// 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)
|
||||||
spew.Dump(s.MakeBaseURL().String())
|
|
||||||
|
// Server generates a QR code connection string
|
||||||
|
cp, err := s.MakeConnectionParams()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
qr, err := cp.ToString()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Client reads QR code and parses the connection string
|
||||||
|
ccp := new(ConnectionParams)
|
||||||
|
err = ccp.FromString(qr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u, certPem, err := ccp.Generate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
rootCAs, err := x509.SystemCertPool()
|
rootCAs, err := x509.SystemCertPool()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -71,7 +91,7 @@ func TestGetOutboundIPWithFullServerE2e(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
thing := hex.EncodeToString(b)
|
thing := hex.EncodeToString(b)
|
||||||
|
|
||||||
response, err := client.Get(s.MakeBaseURL().String() + "/hello?say=" + thing)
|
response, err := client.Get(u.String() + "/hello?say=" + thing)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PairingServer struct {
|
type PairingServer struct {
|
||||||
Server
|
Server
|
||||||
|
|
||||||
|
pk *ecdsa.PrivateKey
|
||||||
|
mode Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
PK *ecdsa.PrivateKey
|
||||||
Cert *tls.Certificate
|
Cert *tls.Certificate
|
||||||
Hostname string
|
Hostname string
|
||||||
|
Mode Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPairingServer returns a *NewPairingServer init from the given *Config
|
// NewPairingServer returns a *NewPairingServer init from the given *Config
|
||||||
|
@ -18,5 +26,35 @@ func NewPairingServer(config *Config) *PairingServer {
|
||||||
return &PairingServer{Server: NewServer(
|
return &PairingServer{Server: NewServer(
|
||||||
config.Cert,
|
config.Cert,
|
||||||
config.Hostname,
|
config.Hostname,
|
||||||
)}
|
),
|
||||||
|
pk: config.PK,
|
||||||
|
mode: config.Mode}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
|
||||||
|
func (s *PairingServer) MakeConnectionParams() (*ConnectionParams, error) {
|
||||||
|
switch {
|
||||||
|
case s.cert == nil:
|
||||||
|
return nil, fmt.Errorf("server has no cert set")
|
||||||
|
case s.cert.Leaf == nil:
|
||||||
|
return nil, fmt.Errorf("server cert has no Leaf set")
|
||||||
|
case s.cert.Leaf.NotBefore.IsZero():
|
||||||
|
return nil, fmt.Errorf("server cert Leaf has a zero value NotBefore")
|
||||||
|
}
|
||||||
|
|
||||||
|
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.cert.Leaf.NotBefore, s.mode), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,16 @@ func TestServerURLSuite(t *testing.T) {
|
||||||
|
|
||||||
type ServerURLSuite struct {
|
type ServerURLSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
TestKeyComponents
|
||||||
|
|
||||||
server *MediaServer
|
server *MediaServer
|
||||||
serverNoPort *MediaServer
|
serverNoPort *MediaServer
|
||||||
|
pairingServer *PairingServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerURLSuite) SetupSuite() {
|
func (s *ServerURLSuite) SetupSuite() {
|
||||||
|
s.SetupKeyComponents(s.T())
|
||||||
|
|
||||||
s.server = &MediaServer{Server: Server{
|
s.server = &MediaServer{Server: Server{
|
||||||
hostname: defaultIP.String(),
|
hostname: defaultIP.String(),
|
||||||
port: 1337,
|
port: 1337,
|
||||||
|
|
Loading…
Reference in New Issue