Local Pairing: connection string version 2 with sharing multiple server addresses (#3909)
* feat: network functions for local pairing (#3898)
This commit is contained in:
parent
e922fc40d5
commit
5a8f1feea9
|
@ -21,8 +21,8 @@ func makeRandomSerialNumber() (*big.Int, error) {
|
||||||
return rand.Int(rand.Reader, serialNumberLimit)
|
return rand.Int(rand.Reader, serialNumberLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateX509Cert(sn *big.Int, from, to time.Time, hostname string) *x509.Certificate {
|
func GenerateX509Cert(sn *big.Int, from, to time.Time, IPAddresses []net.IP, DNSNames []string) *x509.Certificate {
|
||||||
c := &x509.Certificate{
|
return &x509.Certificate{
|
||||||
SerialNumber: sn,
|
SerialNumber: sn,
|
||||||
Subject: pkix.Name{Organization: []string{"Self-signed cert"}},
|
Subject: pkix.Name{Organization: []string{"Self-signed cert"}},
|
||||||
NotBefore: from,
|
NotBefore: from,
|
||||||
|
@ -31,16 +31,9 @@ func GenerateX509Cert(sn *big.Int, from, to time.Time, hostname string) *x509.Ce
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
|
IPAddresses: IPAddresses,
|
||||||
|
DNSNames: DNSNames,
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := net.ParseIP(hostname)
|
|
||||||
if ip != nil {
|
|
||||||
c.IPAddresses = []net.IP{ip}
|
|
||||||
} else {
|
|
||||||
c.DNSNames = []string{hostname}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, keyPem []byte, err error) {
|
func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, keyPem []byte, err error) {
|
||||||
|
@ -59,7 +52,7 @@ func GenerateX509PEMs(cert *x509.Certificate, key *ecdsa.PrivateKey) (certPem, k
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateTLSCert(notBefore, notAfter time.Time, hostname string) (*tls.Certificate, []byte, error) {
|
func GenerateTLSCert(notBefore, notAfter time.Time, IPAddresses []net.IP, DNSNames []string) (*tls.Certificate, []byte, error) {
|
||||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -70,7 +63,7 @@ func GenerateTLSCert(notBefore, notAfter time.Time, hostname string) (*tls.Certi
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := GenerateX509Cert(sn, notBefore, notAfter, hostname)
|
cert := GenerateX509Cert(sn, notBefore, notAfter, IPAddresses, DNSNames)
|
||||||
certPem, keyPem, err := GenerateX509PEMs(cert, priv)
|
certPem, keyPem, err := GenerateX509PEMs(cert, priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -88,7 +81,7 @@ func generateMediaTLSCert() error {
|
||||||
notBefore := time.Now()
|
notBefore := time.Now()
|
||||||
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
notAfter := notBefore.Add(365 * 24 * time.Hour)
|
||||||
|
|
||||||
finalCert, certPem, err := GenerateTLSCert(notBefore, notAfter, Localhost)
|
finalCert, certPem, err := GenerateTLSCert(notBefore, notAfter, []net.IP{}, []string{Localhost})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -42,12 +43,12 @@ func (s *CertsSuite) TestGenerateX509Cert() {
|
||||||
notBefore := time.Now()
|
notBefore := time.Now()
|
||||||
notAfter := notBefore.Add(time.Hour)
|
notAfter := notBefore.Add(time.Hour)
|
||||||
|
|
||||||
c1 := GenerateX509Cert(s.SN, notBefore, notAfter, Localhost)
|
c1 := GenerateX509Cert(s.SN, notBefore, notAfter, []net.IP{}, []string{Localhost})
|
||||||
s.Require().Exactly([]string{Localhost}, c1.DNSNames)
|
s.Require().Exactly([]string{Localhost}, c1.DNSNames)
|
||||||
s.Require().Nil(c1.IPAddresses)
|
s.Require().Empty(c1.IPAddresses)
|
||||||
|
|
||||||
c2 := GenerateX509Cert(s.SN, notBefore, notAfter, DefaultIP.String())
|
c2 := GenerateX509Cert(s.SN, notBefore, notAfter, []net.IP{LocalHostIP}, []string{})
|
||||||
s.Require().Len(c2.IPAddresses, 1)
|
s.Require().Len(c2.IPAddresses, 1)
|
||||||
s.Require().Equal(DefaultIP.String(), c2.IPAddresses[0].String())
|
s.Require().Equal(LocalHostIP.String(), c2.IPAddresses[0].String())
|
||||||
s.Require().Nil(c2.DNSNames)
|
s.Require().Empty(c2.DNSNames)
|
||||||
}
|
}
|
||||||
|
|
150
server/ips.go
150
server/ips.go
|
@ -2,10 +2,14 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/logutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultIP = net.IP{127, 0, 0, 1}
|
LocalHostIP = net.IP{127, 0, 0, 1}
|
||||||
Localhost = "Localhost"
|
Localhost = "Localhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,3 +24,147 @@ func GetOutboundIP() (net.IP, error) {
|
||||||
|
|
||||||
return localAddr.IP, nil
|
return localAddr.IP, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addrToIPNet casts addr to IPNet.
|
||||||
|
// Returns nil if addr is not of IPNet type.
|
||||||
|
func addrToIPNet(addr net.Addr) *net.IPNet {
|
||||||
|
switch v := addr.(type) {
|
||||||
|
case *net.IPNet:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterAddressesForPairingServer filters private unicast addresses.
|
||||||
|
// ips is a 2-dimensional array, where each sub-array is a list of IP
|
||||||
|
// addresses for a single network interface.
|
||||||
|
func filterAddressesForPairingServer(ips [][]net.IP) []net.IP {
|
||||||
|
var result []net.IP
|
||||||
|
|
||||||
|
for _, niIps := range ips {
|
||||||
|
var ipv4, ipv6 []net.IP
|
||||||
|
|
||||||
|
for _, ip := range niIps {
|
||||||
|
|
||||||
|
// Only take private global unicast addrs
|
||||||
|
if !ip.IsGlobalUnicast() || !ip.IsPrivate() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := ip.To4(); v != nil {
|
||||||
|
ipv4 = append(ipv4, ip)
|
||||||
|
} else {
|
||||||
|
ipv6 = append(ipv6, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer IPv4 over IPv6 for shorter connection string
|
||||||
|
if len(ipv4) == 0 {
|
||||||
|
result = append(result, ipv6...)
|
||||||
|
} else {
|
||||||
|
result = append(result, ipv4...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLocalAddresses returns an array of all addresses
|
||||||
|
// of all available network interfaces.
|
||||||
|
func getLocalAddresses() ([][]net.IP, error) {
|
||||||
|
nis, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ips [][]net.IP
|
||||||
|
|
||||||
|
for _, ni := range nis {
|
||||||
|
var niIps []net.IP
|
||||||
|
|
||||||
|
addrs, err := ni.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
logutils.ZapLogger().Warn("failed to get addresses of network interface",
|
||||||
|
zap.String("networkInterface", ni.Name),
|
||||||
|
zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
if ipNet := addrToIPNet(addr); ipNet == nil {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
ip = ipNet.IP
|
||||||
|
}
|
||||||
|
niIps = append(niIps, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(niIps) > 0 {
|
||||||
|
ips = append(ips, niIps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalAddressesForPairingServer is a high-level func
|
||||||
|
// that returns a list of addresses to be used by local pairing server.
|
||||||
|
func GetLocalAddressesForPairingServer() ([]net.IP, error) {
|
||||||
|
ips, err := getLocalAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return filterAddressesForPairingServer(ips), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findReachableAddresses returns a filtered remoteIps array,
|
||||||
|
// in which each IP matches one or more of given localNets.
|
||||||
|
func findReachableAddresses(remoteIPs []net.IP, localNets []net.IPNet) []net.IP {
|
||||||
|
var result []net.IP
|
||||||
|
for _, localNet := range localNets {
|
||||||
|
for _, remoteIP := range remoteIPs {
|
||||||
|
if localNet.Contains(remoteIP) {
|
||||||
|
result = append(result, remoteIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAllAvailableNetworks collects all networks
|
||||||
|
// from available network interfaces.
|
||||||
|
func getAllAvailableNetworks() ([]net.IPNet, error) {
|
||||||
|
var localNets []net.IPNet
|
||||||
|
|
||||||
|
nis, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ni := range nis {
|
||||||
|
addrs, err := ni.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
logutils.ZapLogger().Warn("failed to get addresses of network interface",
|
||||||
|
zap.String("networkInterface", ni.Name),
|
||||||
|
zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, localAddr := range addrs {
|
||||||
|
localNets = append(localNets, *addrToIPNet(localAddr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localNets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindReachableAddressesForPairingClient is a high-level func
|
||||||
|
// that returns a reachable server's address to be used by local pairing client.
|
||||||
|
func FindReachableAddressesForPairingClient(serverIps []net.IP) ([]net.IP, error) {
|
||||||
|
nets, err := getAllAvailableNetworks()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return findReachableAddresses(serverIps, nets), nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/server/servertest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIPsTestingSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(IPsTestingSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPsTestingSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
servertest.TestLoggerComponents
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IPsTestingSuite) SetupSuite() {
|
||||||
|
s.SetupLoggerComponents()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IPsTestingSuite) TestConnectionParams_GetLocalAddressesForPairingServer() {
|
||||||
|
|
||||||
|
allIps := [][]net.IP{
|
||||||
|
{
|
||||||
|
net.IPv4(127, 0, 0, 1),
|
||||||
|
net.IPv6loopback,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.IPv4(192, 168, 1, 42),
|
||||||
|
net.IP{0xfc, 0x80, 0, 0, 0, 0, 0, 0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.IPv4(11, 12, 13, 14),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.IP{0xfc, 0x80, 0, 0, 0, 0, 0, 0, 0xff, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.IP{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0xff, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First NI is a loop-back
|
||||||
|
ni0 := allIps[0]
|
||||||
|
s.Require().NotNil(ni0[0].To4())
|
||||||
|
s.Require().True(ni0[0].IsLoopback())
|
||||||
|
s.Require().Len(ni0[1], net.IPv6len)
|
||||||
|
s.Require().True(ni0[1].IsLoopback())
|
||||||
|
|
||||||
|
// Second NI's both IP addresses fits the needs. IPv6 should be filtered out.
|
||||||
|
ni1 := allIps[1]
|
||||||
|
s.Require().NotNil(ni1[0].To4())
|
||||||
|
s.Require().True(ni1[0].IsGlobalUnicast())
|
||||||
|
s.Require().True(ni1[0].IsPrivate())
|
||||||
|
s.Require().Len(ni1[1], net.IPv6len)
|
||||||
|
s.Require().True(ni1[1].IsGlobalUnicast())
|
||||||
|
s.Require().True(ni1[1].IsPrivate())
|
||||||
|
|
||||||
|
// Next NI should be filtered out as non-private
|
||||||
|
ni2 := allIps[2]
|
||||||
|
s.Require().NotNil(ni2[0].To4())
|
||||||
|
s.Require().False(ni2[0].IsPrivate())
|
||||||
|
|
||||||
|
// Next NI fits the needs, should be taken,
|
||||||
|
// as no preferred IPv4 is available on this NI.
|
||||||
|
ni3 := allIps[3]
|
||||||
|
s.Require().Len(ni3[0], net.IPv6len)
|
||||||
|
s.Require().True(ni3[0].IsGlobalUnicast())
|
||||||
|
s.Require().True(ni3[0].IsPrivate())
|
||||||
|
|
||||||
|
// Last NI has a link-local unicast address,
|
||||||
|
// which should be filtered out as non-private.
|
||||||
|
ni4 := allIps[4]
|
||||||
|
s.Require().Len(ni4[0], net.IPv6len)
|
||||||
|
s.Require().True(ni4[0].IsLinkLocalUnicast())
|
||||||
|
s.Require().False(ni4[0].IsGlobalUnicast())
|
||||||
|
s.Require().False(ni4[0].IsPrivate())
|
||||||
|
|
||||||
|
ips := filterAddressesForPairingServer(allIps)
|
||||||
|
s.Require().Len(ips, 2)
|
||||||
|
s.Require().NotNil(ips[0].To4())
|
||||||
|
s.Require().NotNil(ni1[0].To4())
|
||||||
|
s.Require().Equal(ips[0].To4(), ni1[0].To4())
|
||||||
|
s.Require().Equal(ips[1], ni3[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IPsTestingSuite) TestConnectionParams_FindReachableAddresses() {
|
||||||
|
var remoteIps []net.IP
|
||||||
|
var localNets []net.IPNet
|
||||||
|
var ips []net.IP
|
||||||
|
|
||||||
|
// Test 1
|
||||||
|
remoteIps = []net.IP{
|
||||||
|
net.IPv4(10, 1, 2, 3),
|
||||||
|
net.IPv4(172, 16, 2, 42),
|
||||||
|
net.IPv4(192, 168, 1, 42),
|
||||||
|
}
|
||||||
|
localNets = []net.IPNet{
|
||||||
|
{IP: net.IPv4(192, 168, 1, 43), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||||
|
}
|
||||||
|
ips = findReachableAddresses(remoteIps, localNets)
|
||||||
|
s.Require().Len(ips, 1)
|
||||||
|
s.Require().Equal(ips[0], remoteIps[2])
|
||||||
|
|
||||||
|
// Test 2
|
||||||
|
remoteIps = []net.IP{
|
||||||
|
net.IPv4(10, 1, 2, 3),
|
||||||
|
net.IPv4(172, 16, 2, 42),
|
||||||
|
net.IPv4(192, 168, 1, 42),
|
||||||
|
}
|
||||||
|
localNets = []net.IPNet{
|
||||||
|
{IP: net.IPv4(10, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 0, 0)},
|
||||||
|
{IP: net.IPv4(172, 16, 2, 43), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||||
|
{IP: net.IPv4(192, 168, 2, 43), Mask: net.IPv4Mask(255, 255, 255, 0)},
|
||||||
|
}
|
||||||
|
ips = findReachableAddresses(remoteIps, localNets)
|
||||||
|
s.Require().Len(ips, 2)
|
||||||
|
s.Require().Equal(ips[0], remoteIps[0])
|
||||||
|
s.Require().Equal(ips[1], remoteIps[1])
|
||||||
|
|
||||||
|
// Test 3
|
||||||
|
remoteIps = []net.IP{
|
||||||
|
net.IPv4(10, 1, 2, 3),
|
||||||
|
net.IPv4(172, 16, 2, 42),
|
||||||
|
net.IPv4(192, 168, 1, 42),
|
||||||
|
}
|
||||||
|
localNets = []net.IPNet{}
|
||||||
|
ips = findReachableAddresses(remoteIps, localNets)
|
||||||
|
s.Require().Len(ips, 0)
|
||||||
|
|
||||||
|
// Test 4
|
||||||
|
remoteIps = []net.IP{}
|
||||||
|
localNets = []net.IPNet{}
|
||||||
|
ips = findReachableAddresses(remoteIps, localNets)
|
||||||
|
s.Require().Len(ips, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IPsTestingSuite) TestConnectionParams_RealNetworksTest() {
|
||||||
|
|
||||||
|
// This test is intended to be run manually.
|
||||||
|
// 1. set `printDetails` to true
|
||||||
|
// 2. run Part 1 on 2 devices
|
||||||
|
// 3. copy printed results to Part 2
|
||||||
|
// 4. update expected results in Part 3
|
||||||
|
// 5. run Part 3
|
||||||
|
|
||||||
|
// printing is disabled by default to avoid showing sensitive information
|
||||||
|
const printDetails = false
|
||||||
|
|
||||||
|
printLocalAddresses := func(in [][]net.IP) {
|
||||||
|
fmt.Println("{")
|
||||||
|
for _, a := range in {
|
||||||
|
fmt.Println(" {")
|
||||||
|
for _, v := range a {
|
||||||
|
fmt.Println(" net.ParseIP(\"", v.String(), "\"),")
|
||||||
|
|
||||||
|
}
|
||||||
|
fmt.Println(" },")
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
printNets := func(in []net.IPNet) {
|
||||||
|
fmt.Println("{")
|
||||||
|
for _, n := range in {
|
||||||
|
fmt.Println(" \"", n.String(), "\",")
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
parseNets := func(in []string) []net.IPNet {
|
||||||
|
var out []net.IPNet
|
||||||
|
for _, v := range in {
|
||||||
|
_, network, err := net.ParseCIDR(v)
|
||||||
|
s.Require().NoError(err)
|
||||||
|
out = append(out, *network)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 1:
|
||||||
|
// print needed stuff. Run on both machines.
|
||||||
|
|
||||||
|
addrs, err := getLocalAddresses()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Logger.Info("MacOS:", zap.Any("addrs", addrs))
|
||||||
|
|
||||||
|
if printDetails {
|
||||||
|
printLocalAddresses(addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
nets, err := getAllAvailableNetworks()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Logger.Info("MacOS:", zap.Any("nets", nets))
|
||||||
|
|
||||||
|
if printDetails {
|
||||||
|
printNets(nets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part 2:
|
||||||
|
// Input all printed devices details below
|
||||||
|
|
||||||
|
macNIs := [][]net.IP{
|
||||||
|
{
|
||||||
|
net.ParseIP("127.0.0.1"),
|
||||||
|
net.ParseIP("::1"),
|
||||||
|
net.ParseIP("fe80::1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.ParseIP("fe80::c1f:ee0d:1476:dd9a"),
|
||||||
|
net.ParseIP("192.168.1.36"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.ParseIP("172.16.9.1"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
macNets := parseNets([]string{
|
||||||
|
"127.0.0.1/8",
|
||||||
|
"::1/128",
|
||||||
|
"fe80::1/64",
|
||||||
|
"fe80::c1f:ee0d:1476:dd9a/64",
|
||||||
|
"192.168.1.36/24",
|
||||||
|
"172.16.9.1/24",
|
||||||
|
})
|
||||||
|
|
||||||
|
winNIs := [][]net.IP{
|
||||||
|
{
|
||||||
|
net.ParseIP("fe80::6fd7:5ce4:554f:165a"),
|
||||||
|
net.ParseIP("192.168.1.33"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.ParseIP("fe80::ffa5:98e1:285c:42eb"),
|
||||||
|
net.ParseIP("10.0.85.2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
net.ParseIP("::1"),
|
||||||
|
net.ParseIP("127.0.0.1"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
winNets := parseNets([]string{
|
||||||
|
"fe80::6fd7:5ce4:554f:165a/64",
|
||||||
|
"192.168.1.33/24",
|
||||||
|
"fe80::ffa5:98e1:285c:42eb/64",
|
||||||
|
"10.0.85.2/32",
|
||||||
|
"::1/128",
|
||||||
|
"127.0.0.1/8",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Part 3:
|
||||||
|
// The test itself
|
||||||
|
|
||||||
|
// Windows as server, Mac as client
|
||||||
|
winIPs := filterAddressesForPairingServer(winNIs)
|
||||||
|
winReachableIps := findReachableAddresses(winIPs, macNets)
|
||||||
|
s.Require().Len(winReachableIps, 1)
|
||||||
|
s.Require().Equal(winReachableIps[0].String(), "192.168.1.33")
|
||||||
|
|
||||||
|
// Windows as server, Mac as client
|
||||||
|
macIPs := filterAddressesForPairingServer(macNIs)
|
||||||
|
macReachableIps := findReachableAddresses(macIPs, winNets)
|
||||||
|
s.Require().Len(macReachableIps, 1)
|
||||||
|
s.Require().Equal(macReachableIps[0].String(), "192.168.1.36")
|
||||||
|
}
|
|
@ -9,11 +9,14 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/logutils"
|
||||||
"github.com/status-im/status-go/server"
|
"github.com/status-im/status-go/server"
|
||||||
"github.com/status-im/status-go/signal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int {
|
func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int {
|
||||||
|
@ -23,8 +26,8 @@ func makeSerialNumberFromKey(pk *ecdsa.PrivateKey) *big.Int {
|
||||||
return new(big.Int).SetBytes(h.Sum(nil))
|
return new(big.Int).SetBytes(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCertFromKey(pk *ecdsa.PrivateKey, from time.Time, hostname string) (tls.Certificate, []byte, error) {
|
func GenerateCertFromKey(pk *ecdsa.PrivateKey, from time.Time, IPAddresses []net.IP, DNSNames []string) (tls.Certificate, []byte, error) {
|
||||||
cert := server.GenerateX509Cert(makeSerialNumberFromKey(pk), from, from.Add(time.Hour), hostname)
|
cert := server.GenerateX509Cert(makeSerialNumberFromKey(pk), from, from.Add(time.Hour), IPAddresses, DNSNames)
|
||||||
certPem, keyPem, err := server.GenerateX509PEMs(cert, pk)
|
certPem, keyPem, err := server.GenerateX509PEMs(cert, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, nil, err
|
return tls.Certificate{}, nil, err
|
||||||
|
@ -111,13 +114,13 @@ func getServerCert(URL *url.URL) (*x509.Certificate, error) {
|
||||||
|
|
||||||
conn, err := tls.Dial("tcp", URL.Host, conf)
|
conn, err := tls.Dial("tcp", URL.Host, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
signal.SendLocalPairingEvent(Event{Type: EventConnectionError, Error: err.Error(), Action: ActionConnect})
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func(conn *tls.Conn) {
|
||||||
|
if e := conn.Close(); e != nil {
|
||||||
// No error on the dial out then the URL.Host is accessible
|
logutils.ZapLogger().Warn("failed to close temporary TLS connection:", zap.Error(e))
|
||||||
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionConnect})
|
}
|
||||||
|
}(conn)
|
||||||
|
|
||||||
certs := conn.ConnectionState().PeerCertificates
|
certs := conn.ConnectionState().PeerCertificates
|
||||||
if len(certs) != 1 {
|
if len(certs) != 1 {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"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/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,16 +37,41 @@ type BaseClient struct {
|
||||||
|
|
||||||
// NewBaseClient returns a fully qualified BaseClient from the given ConnectionParams
|
// NewBaseClient returns a fully qualified BaseClient from the given ConnectionParams
|
||||||
func NewBaseClient(c *ConnectionParams) (*BaseClient, error) {
|
func NewBaseClient(c *ConnectionParams) (*BaseClient, error) {
|
||||||
u, err := c.URL()
|
|
||||||
|
var baseAddress *url.URL
|
||||||
|
var serverCert *x509.Certificate
|
||||||
|
var certErrs error
|
||||||
|
|
||||||
|
netIps, err := server.FindReachableAddressesForPairingClient(c.netIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
serverCert, err := getServerCert(u)
|
for i := range netIps {
|
||||||
|
u, err := c.URL(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverCert, err = getServerCert(u)
|
||||||
|
if err != nil {
|
||||||
|
certErrs = fmt.Errorf("%sconnecting to '%s' failed: %s; ", certErrs.Error(), u, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
baseAddress = u
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverCert == nil {
|
||||||
|
certErrs = fmt.Errorf("failed to connect to any of given addresses. %w", certErrs)
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventConnectionError, Error: certErrs.Error(), Action: ActionConnect})
|
||||||
|
return nil, certErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// No error on the dial out then the URL.Host is accessible
|
||||||
|
signal.SendLocalPairingEvent(Event{Type: EventConnectionSuccess, Action: ActionConnect})
|
||||||
|
|
||||||
err = verifyCert(serverCert, c.publicKey)
|
err = verifyCert(serverCert, c.publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -78,7 +104,7 @@ func NewBaseClient(c *ConnectionParams) (*BaseClient, error) {
|
||||||
Client: &http.Client{Transport: tr, Jar: cj},
|
Client: &http.Client{Transport: tr, Jar: cj},
|
||||||
serverCert: serverCert,
|
serverCert: serverCert,
|
||||||
challengeTaker: NewChallengeTaker(NewPayloadEncryptor(c.aesKey)),
|
challengeTaker: NewChallengeTaker(NewPayloadEncryptor(c.aesKey)),
|
||||||
baseAddress: u,
|
baseAddress: baseAddress,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
type TestPairingServerComponents struct {
|
type TestPairingServerComponents struct {
|
||||||
EphemeralPK *ecdsa.PrivateKey
|
EphemeralPK *ecdsa.PrivateKey
|
||||||
EphemeralAES []byte
|
EphemeralAES []byte
|
||||||
OutboundIP net.IP
|
IPAddresses []net.IP
|
||||||
Cert tls.Certificate
|
Cert tls.Certificate
|
||||||
SS *SenderServer
|
SS *SenderServer
|
||||||
RS *ReceiverServer
|
RS *ReceiverServer
|
||||||
|
@ -36,19 +36,20 @@ func (tpsc *TestPairingServerComponents) SetupPairingServerComponents(t *testing
|
||||||
tpsc.EphemeralAES, err = common.MakeECDHSharedKey(tpsc.EphemeralPK, &tpsc.EphemeralPK.PublicKey)
|
tpsc.EphemeralAES, err = common.MakeECDHSharedKey(tpsc.EphemeralPK, &tpsc.EphemeralPK.PublicKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// 3) Device outbound IP address
|
// 3) Device IP address
|
||||||
tpsc.OutboundIP, err = server.GetOutboundIP()
|
tpsc.IPAddresses, err = server.GetLocalAddressesForPairingServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Generate tls.Certificate and Server
|
// Generate tls.Certificate and Server
|
||||||
tpsc.Cert, _, err = GenerateCertFromKey(tpsc.EphemeralPK, time.Now(), tpsc.OutboundIP.String())
|
tpsc.Cert, _, err = GenerateCertFromKey(tpsc.EphemeralPK, time.Now(), tpsc.IPAddresses, []string{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
sc := &ServerConfig{
|
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(),
|
IPAddresses: tpsc.IPAddresses,
|
||||||
|
ListenIP: net.IPv4zero,
|
||||||
}
|
}
|
||||||
|
|
||||||
tpsc.SS, err = NewSenderServer(nil, &SenderServerConfig{ServerConfig: sc, SenderConfig: &SenderConfig{}})
|
tpsc.SS, err = NewSenderServer(nil, &SenderServerConfig{ServerConfig: sc, SenderConfig: &SenderConfig{}})
|
||||||
|
|
|
@ -3,6 +3,7 @@ package pairing
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
"github.com/status-im/status-go/multiaccounts"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
@ -67,7 +68,8 @@ type ServerConfig struct {
|
||||||
PK *ecdsa.PublicKey `json:"-"`
|
PK *ecdsa.PublicKey `json:"-"`
|
||||||
EK []byte `json:"-"`
|
EK []byte `json:"-"`
|
||||||
Cert *tls.Certificate `json:"-"`
|
Cert *tls.Certificate `json:"-"`
|
||||||
Hostname string `json:"-"`
|
ListenIP net.IP `json:"-"`
|
||||||
|
IPAddresses []net.IP `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientConfig struct{}
|
type ClientConfig struct{}
|
||||||
|
|
|
@ -20,16 +20,16 @@ const (
|
||||||
|
|
||||||
type ConnectionParams struct {
|
type ConnectionParams struct {
|
||||||
version versioning.ConnectionParamVersion
|
version versioning.ConnectionParamVersion
|
||||||
netIP net.IP
|
netIPs []net.IP
|
||||||
port int
|
port int
|
||||||
publicKey *ecdsa.PublicKey
|
publicKey *ecdsa.PublicKey
|
||||||
aesKey []byte
|
aesKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnectionParams(netIP net.IP, port int, publicKey *ecdsa.PublicKey, aesKey []byte) *ConnectionParams {
|
func NewConnectionParams(netIPs []net.IP, port int, publicKey *ecdsa.PublicKey, aesKey []byte) *ConnectionParams {
|
||||||
cp := new(ConnectionParams)
|
cp := new(ConnectionParams)
|
||||||
cp.version = versioning.LatestConnectionParamVer
|
cp.version = versioning.LatestConnectionParamVer
|
||||||
cp.netIP = netIP
|
cp.netIPs = netIPs
|
||||||
cp.port = port
|
cp.port = port
|
||||||
cp.publicKey = publicKey
|
cp.publicKey = publicKey
|
||||||
cp.aesKey = aesKey
|
cp.aesKey = aesKey
|
||||||
|
@ -45,17 +45,72 @@ func NewConnectionParams(netIP net.IP, port int, publicKey *ecdsa.PublicKey, aes
|
||||||
// - string type identifier
|
// - string type identifier
|
||||||
// - version
|
// - version
|
||||||
// - net.IP
|
// - net.IP
|
||||||
|
// - version 1: a single net.IP
|
||||||
|
// - version 2: array of IPs in next form:
|
||||||
|
// | 1 byte | 4*N bytes | 1 byte | 16*N bytes |
|
||||||
|
// | N | N * IPv4 | M | M * IPv6 |
|
||||||
// - port
|
// - port
|
||||||
// - ecdsa CompressedPublicKey
|
// - ecdsa CompressedPublicKey
|
||||||
// - AES encryption key
|
// - AES encryption key
|
||||||
func (cp *ConnectionParams) ToString() string {
|
func (cp *ConnectionParams) ToString() string {
|
||||||
v := base58.Encode(new(big.Int).SetInt64(int64(cp.version)).Bytes())
|
v := base58.Encode(new(big.Int).SetInt64(int64(cp.version)).Bytes())
|
||||||
ip := base58.Encode(cp.netIP)
|
ips := base58.Encode(SerializeNetIps(cp.netIPs))
|
||||||
p := base58.Encode(new(big.Int).SetInt64(int64(cp.port)).Bytes())
|
p := base58.Encode(new(big.Int).SetInt64(int64(cp.port)).Bytes())
|
||||||
k := base58.Encode(elliptic.MarshalCompressed(cp.publicKey.Curve, cp.publicKey.X, cp.publicKey.Y))
|
k := base58.Encode(elliptic.MarshalCompressed(cp.publicKey.Curve, cp.publicKey.X, cp.publicKey.Y))
|
||||||
ek := base58.Encode(cp.aesKey)
|
ek := base58.Encode(cp.aesKey)
|
||||||
|
|
||||||
return fmt.Sprintf("%s%s:%s:%s:%s:%s", connectionStringID, v, ip, p, k, ek)
|
return fmt.Sprintf("%s%s:%s:%s:%s:%s", connectionStringID, v, ips, p, k, ek)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SerializeNetIps(ips []net.IP) []byte {
|
||||||
|
var out []byte
|
||||||
|
var ipv4 []net.IP
|
||||||
|
var ipv6 []net.IP
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
if v := ip.To4(); v != nil {
|
||||||
|
ipv4 = append(ipv4, v)
|
||||||
|
} else {
|
||||||
|
ipv6 = append(ipv6, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arr := range [][]net.IP{ipv4, ipv6} {
|
||||||
|
out = append(out, uint8(len(arr)))
|
||||||
|
for _, ip := range arr {
|
||||||
|
out = append(out, ip...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseNetIps(in []byte) ([]net.IP, error) {
|
||||||
|
var out []net.IP
|
||||||
|
|
||||||
|
if len(in) < 1 {
|
||||||
|
return nil, fmt.Errorf("net.ip field is too short: '%d', at least 1 byte required", len(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ipLen := range []int{net.IPv4len, net.IPv6len} {
|
||||||
|
|
||||||
|
count := int(in[0])
|
||||||
|
in = in[1:]
|
||||||
|
|
||||||
|
if expectedLen := ipLen * count; len(in) < expectedLen {
|
||||||
|
return nil, fmt.Errorf("net.ip.ip%d field is too short, expected at least '%d' bytes, '%d' bytes found", ipLen, expectedLen, len(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
offset := i * ipLen
|
||||||
|
ip := in[offset : ipLen+offset]
|
||||||
|
out = append(out, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
in = in[ipLen*count:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromString parses a connection params string required for to securely connect to another Status device.
|
// FromString parses a connection params string required for to securely connect to another Status device.
|
||||||
|
@ -63,7 +118,7 @@ func (cp *ConnectionParams) ToString() string {
|
||||||
func (cp *ConnectionParams) FromString(s string) error {
|
func (cp *ConnectionParams) FromString(s string) error {
|
||||||
|
|
||||||
if len(s) < 2 {
|
if len(s) < 2 {
|
||||||
return fmt.Errorf("connection string is invalid: '%s'", s)
|
return fmt.Errorf("connection string is too short: '%s'", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s[:2] != connectionStringID {
|
if s[:2] != connectionStringID {
|
||||||
|
@ -78,7 +133,22 @@ func (cp *ConnectionParams) FromString(s string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.version = versioning.ConnectionParamVersion(new(big.Int).SetBytes(base58.Decode(sData[0])).Int64())
|
cp.version = versioning.ConnectionParamVersion(new(big.Int).SetBytes(base58.Decode(sData[0])).Int64())
|
||||||
cp.netIP = base58.Decode(sData[1])
|
|
||||||
|
netIpsBytes := base58.Decode(sData[1])
|
||||||
|
switch cp.version {
|
||||||
|
case versioning.ConnectionParamsV1:
|
||||||
|
if len(netIpsBytes) != net.IPv4len {
|
||||||
|
return fmt.Errorf("invalid IP size: '%d' bytes, expected: '%d' bytes", len(netIpsBytes), net.IPv4len)
|
||||||
|
}
|
||||||
|
cp.netIPs = []net.IP{netIpsBytes}
|
||||||
|
case versioning.ConnectionParamsV2:
|
||||||
|
netIps, err := ParseNetIps(netIpsBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cp.netIPs = netIps
|
||||||
|
}
|
||||||
|
|
||||||
cp.port = int(new(big.Int).SetBytes(base58.Decode(sData[2])).Int64())
|
cp.port = int(new(big.Int).SetBytes(base58.Decode(sData[2])).Int64())
|
||||||
cp.publicKey = new(ecdsa.PublicKey)
|
cp.publicKey = new(ecdsa.PublicKey)
|
||||||
cp.publicKey.X, cp.publicKey.Y = elliptic.UnmarshalCompressed(elliptic.P256(), base58.Decode(sData[3]))
|
cp.publicKey.X, cp.publicKey.Y = elliptic.UnmarshalCompressed(elliptic.P256(), base58.Decode(sData[3]))
|
||||||
|
@ -120,8 +190,10 @@ func (cp *ConnectionParams) validateVersion() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *ConnectionParams) validateNetIP() error {
|
func (cp *ConnectionParams) validateNetIP() error {
|
||||||
if ok := net.ParseIP(cp.netIP.String()); ok == nil {
|
for _, ip := range cp.netIPs {
|
||||||
return fmt.Errorf("invalid net ip '%s'", cp.netIP)
|
if ok := net.ParseIP(ip.String()); ok == nil {
|
||||||
|
return fmt.Errorf("invalid net ip '%s'", cp.netIPs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -154,7 +226,11 @@ func (cp *ConnectionParams) validateAESKey() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *ConnectionParams) URL() (*url.URL, error) {
|
func (cp *ConnectionParams) URL(IPIndex int) (*url.URL, error) {
|
||||||
|
if IPIndex < 0 || IPIndex >= len(cp.netIPs) {
|
||||||
|
return nil, fmt.Errorf("invalid IP index '%d'", IPIndex)
|
||||||
|
}
|
||||||
|
|
||||||
err := cp.validate()
|
err := cp.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -162,7 +238,7 @@ func (cp *ConnectionParams) URL() (*url.URL, error) {
|
||||||
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: fmt.Sprintf("%s:%d", cp.netIP, cp.port),
|
Host: fmt.Sprintf("%s:%d", cp.netIPs[IPIndex], cp.port),
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package pairing
|
package pairing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
@ -9,8 +13,10 @@ import (
|
||||||
"github.com/status-im/status-go/server/servertest"
|
"github.com/status-im/status-go/server/servertest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
connectionString = "cs2:4FHRnp:Q4:uqnnMwVUfJc2Fkcaojet8F1ufKC3hZdGEt47joyBx9yd:BbnZ7Gc66t54a9kEFCf7FW8SGQuYypwHVeNkRYeNoqV6"
|
connectionStringV1 = "cs2:4FHRnp:Q4:uqnnMwVUfJc2Fkcaojet8F1ufKC3hZdGEt47joyBx9yd:BbnZ7Gc66t54a9kEFCf7FW8SGQuYypwHVeNkRYeNoqV6"
|
||||||
|
connectionStringV2 = "cs3:kDDauj5:Q4:uqnnMwVUfJc2Fkcaojet8F1ufKC3hZdGEt47joyBx9yd:BbnZ7Gc66t54a9kEFCf7FW8SGQuYypwHVeNkRYeNoqV6"
|
||||||
|
port = 1337
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConnectionParamsSuite(t *testing.T) {
|
func TestConnectionParamsSuite(t *testing.T) {
|
||||||
|
@ -31,17 +37,27 @@ func (s *ConnectionParamsSuite) SetupSuite() {
|
||||||
s.SetupCertComponents(s.T())
|
s.SetupCertComponents(s.T())
|
||||||
s.SetupLoggerComponents()
|
s.SetupLoggerComponents()
|
||||||
|
|
||||||
cert, _, err := GenerateCertFromKey(s.PK, s.NotBefore, server.DefaultIP.String())
|
ip := server.LocalHostIP
|
||||||
|
ips := []net.IP{ip}
|
||||||
|
|
||||||
|
cert, _, err := GenerateCertFromKey(s.PK, s.NotBefore, ips, []string{})
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
bs := server.NewServer(&cert, server.DefaultIP.String(), nil, s.Logger)
|
sc := ServerConfig{
|
||||||
err = bs.SetPort(1337)
|
PK: &s.PK.PublicKey,
|
||||||
|
EK: s.AES,
|
||||||
|
Cert: &cert,
|
||||||
|
IPAddresses: ips,
|
||||||
|
ListenIP: net.IPv4zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := server.NewServer(&cert, net.IPv4zero.String(), nil, s.Logger)
|
||||||
|
err = bs.SetPort(port)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
s.server = &BaseServer{
|
s.server = &BaseServer{
|
||||||
Server: bs,
|
Server: bs,
|
||||||
pk: &s.PK.PublicKey,
|
config: sc,
|
||||||
ek: s.AES,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,21 +66,73 @@ func (s *ConnectionParamsSuite) TestConnectionParams_ToString() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
cps := cp.ToString()
|
cps := cp.ToString()
|
||||||
s.Require().Equal(connectionString, cps)
|
s.Require().Equal(connectionStringV2, cps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConnectionParamsSuite) TestConnectionParams_Generate() {
|
func (s *ConnectionParamsSuite) TestConnectionParams_Generate() {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
cs string
|
||||||
|
}{
|
||||||
|
{description: "ConnectionString_version1", cs: connectionStringV1},
|
||||||
|
{description: "ConnectionString_version2", cs: connectionStringV2},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
s.T().Run(tc.description, func(t *testing.T) {
|
||||||
cp := new(ConnectionParams)
|
cp := new(ConnectionParams)
|
||||||
err := cp.FromString(connectionString)
|
err := cp.FromString(connectionStringV2)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
u, err := cp.URL()
|
u, err := cp.URL(0)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
s.Require().Equal("https://127.0.0.1:1337", u.String())
|
expectedURL := fmt.Sprintf("https://%s:%d", server.LocalHostIP.String(), port)
|
||||||
s.Require().Equal(server.DefaultIP.String(), u.Hostname())
|
|
||||||
s.Require().Equal("1337", u.Port())
|
s.Require().Equal(expectedURL, u.String())
|
||||||
|
s.Require().Equal(server.LocalHostIP.String(), u.Hostname())
|
||||||
|
s.Require().Equal(strconv.Itoa(port), u.Port())
|
||||||
|
|
||||||
s.Require().True(cp.publicKey.Equal(&s.PK.PublicKey))
|
s.Require().True(cp.publicKey.Equal(&s.PK.PublicKey))
|
||||||
s.Require().Equal(s.AES, cp.aesKey)
|
s.Require().Equal(s.AES, cp.aesKey)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionParamsSuite) TestConnectionParams_ParseNetIps() {
|
||||||
|
|
||||||
|
in := []net.IP{
|
||||||
|
{192, 168, 1, 42},
|
||||||
|
net.ParseIP("fe80::6fd7:5ce4:554f:165a"),
|
||||||
|
{172, 16, 9, 1},
|
||||||
|
net.ParseIP("fe80::ffa5:98e1:285c:42eb"),
|
||||||
|
net.ParseIP("fe80::c1f:ee0d:1476:dd9a"),
|
||||||
|
}
|
||||||
|
bytes := SerializeNetIps(in)
|
||||||
|
|
||||||
|
s.Require().Equal(bytes,
|
||||||
|
[]byte{
|
||||||
|
2, // v4 count
|
||||||
|
192, 168, 1, 42, // v4 1
|
||||||
|
172, 16, 9, 1, // v4 2
|
||||||
|
3, // v6 count
|
||||||
|
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x6f, 0xd7, 0x5c, 0xe4, 0x55, 0x4f, 0x16, 0x5a, // v6 1
|
||||||
|
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0xff, 0xa5, 0x98, 0xe1, 0x28, 0x5c, 0x42, 0xeb, // v6 2
|
||||||
|
0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x0c, 0x1f, 0xee, 0x0d, 0x14, 0x76, 0xdd, 0x9a, // v6 3
|
||||||
|
})
|
||||||
|
|
||||||
|
out, err := ParseNetIps(bytes)
|
||||||
|
|
||||||
|
s.Require().NoError(err)
|
||||||
|
s.Require().Len(in, 5)
|
||||||
|
|
||||||
|
sort.SliceStable(in, func(i, j int) bool {
|
||||||
|
return in[i].String() < in[j].String()
|
||||||
|
})
|
||||||
|
sort.SliceStable(out, func(i, j int) bool {
|
||||||
|
return out[i].String() < out[j].String()
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Require().Equal(in, out)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func preflightHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
func makeCert(address net.IP) (*tls.Certificate, []byte, error) {
|
func makeCert(address net.IP) (*tls.Certificate, []byte, error) {
|
||||||
notBefore := time.Now()
|
notBefore := time.Now()
|
||||||
notAfter := notBefore.Add(time.Minute)
|
notAfter := notBefore.Add(time.Minute)
|
||||||
return server.GenerateTLSCert(notBefore, notAfter, address.String())
|
return server.GenerateTLSCert(notBefore, notAfter, []net.IP{address}, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAndStartServer(cert *tls.Certificate, address net.IP) (string, func() error, error) {
|
func makeAndStartServer(cert *tls.Certificate, address net.IP) (string, func() error, error) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -29,8 +28,7 @@ type BaseServer struct {
|
||||||
server.Server
|
server.Server
|
||||||
challengeGiver *ChallengeGiver
|
challengeGiver *ChallengeGiver
|
||||||
|
|
||||||
pk *ecdsa.PublicKey
|
config ServerConfig
|
||||||
ek []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBaseServer returns a *BaseServer init from the given *SenderServerConfig
|
// NewBaseServer returns a *BaseServer init from the given *SenderServerConfig
|
||||||
|
@ -43,13 +41,12 @@ func NewBaseServer(logger *zap.Logger, e *PayloadEncryptor, config *ServerConfig
|
||||||
bs := &BaseServer{
|
bs := &BaseServer{
|
||||||
Server: server.NewServer(
|
Server: server.NewServer(
|
||||||
config.Cert,
|
config.Cert,
|
||||||
config.Hostname,
|
config.ListenIP.String(),
|
||||||
nil,
|
nil,
|
||||||
logger,
|
logger,
|
||||||
),
|
),
|
||||||
challengeGiver: cg,
|
challengeGiver: cg,
|
||||||
pk: config.PK,
|
config: *config,
|
||||||
ek: config.EK,
|
|
||||||
}
|
}
|
||||||
bs.SetTimeout(config.Timeout)
|
bs.SetTimeout(config.Timeout)
|
||||||
return bs, nil
|
return bs, nil
|
||||||
|
@ -57,18 +54,7 @@ func NewBaseServer(logger *zap.Logger, e *PayloadEncryptor, config *ServerConfig
|
||||||
|
|
||||||
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
|
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
|
||||||
func (s *BaseServer) MakeConnectionParams() (*ConnectionParams, error) {
|
func (s *BaseServer) MakeConnectionParams() (*ConnectionParams, error) {
|
||||||
hostname := s.GetHostname()
|
return NewConnectionParams(s.config.IPAddresses, s.MustGetPort(), s.config.PK, s.config.EK), nil
|
||||||
netIP := net.ParseIP(hostname)
|
|
||||||
if netIP == nil {
|
|
||||||
return nil, fmt.Errorf("invalid ip address given '%s'", hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
netIP4 := netIP.To4()
|
|
||||||
if netIP4 != nil {
|
|
||||||
netIP = netIP4
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewConnectionParams(netIP, s.MustGetPort(), s.pk, s.ek), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeServerConfig(config *ServerConfig) error {
|
func MakeServerConfig(config *ServerConfig) error {
|
||||||
|
@ -83,12 +69,12 @@ func MakeServerConfig(config *ServerConfig) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outboundIP, err := server.GetOutboundIP()
|
ips, err := server.GetLocalAddressesForPairingServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCert, _, err := GenerateCertFromKey(tlsKey, time.Now(), outboundIP.String())
|
tlsCert, _, err := GenerateCertFromKey(tlsKey, time.Now(), ips, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -96,7 +82,9 @@ func MakeServerConfig(config *ServerConfig) error {
|
||||||
config.PK = &tlsKey.PublicKey
|
config.PK = &tlsKey.PublicKey
|
||||||
config.EK = AESKey
|
config.EK = AESKey
|
||||||
config.Cert = &tlsCert
|
config.Cert = &tlsCert
|
||||||
config.Hostname = outboundIP.String()
|
config.IPAddresses = ips
|
||||||
|
config.ListenIP = net.IPv4zero
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ type ConnectionParamVersion int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ConnectionParamsV1 ConnectionParamVersion = iota + 1
|
ConnectionParamsV1 ConnectionParamVersion = iota + 1
|
||||||
|
ConnectionParamsV2
|
||||||
)
|
)
|
||||||
|
|
||||||
type LocalPairingVersion int
|
type LocalPairingVersion int
|
||||||
|
@ -13,6 +14,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LatestConnectionParamVer = ConnectionParamsV1
|
LatestConnectionParamVer = ConnectionParamsV2
|
||||||
LatestLocalPairingVer = LocalPairingV1
|
LatestLocalPairingVer = LocalPairingV1
|
||||||
)
|
)
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (s *QROpsTestSuite) SetupTest() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
s.serverNoPort = &MediaServer{Server: Server{
|
s.serverNoPort = &MediaServer{Server: Server{
|
||||||
hostname: DefaultIP.String(),
|
hostname: LocalHostIP.String(),
|
||||||
portManger: newPortManager(s.Logger, nil),
|
portManger: newPortManager(s.Logger, nil),
|
||||||
}}
|
}}
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -59,14 +59,14 @@ func (s *ServerURLSuite) SetupTest() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
s.server = &MediaServer{Server: Server{
|
s.server = &MediaServer{Server: Server{
|
||||||
hostname: DefaultIP.String(),
|
hostname: LocalHostIP.String(),
|
||||||
portManger: newPortManager(s.Logger, nil),
|
portManger: newPortManager(s.Logger, nil),
|
||||||
}}
|
}}
|
||||||
err = s.server.SetPort(customPortForTests)
|
err = s.server.SetPort(customPortForTests)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
s.serverNoPort = &MediaServer{Server: Server{
|
s.serverNoPort = &MediaServer{Server: Server{
|
||||||
hostname: DefaultIP.String(),
|
hostname: LocalHostIP.String(),
|
||||||
portManger: newPortManager(s.Logger, nil),
|
portManger: newPortManager(s.Logger, nil),
|
||||||
}}
|
}}
|
||||||
go func() {
|
go func() {
|
||||||
|
|
Loading…
Reference in New Issue