mesh/delivery merged: source.main moved to novox/mesh-host@89475bb0cfec
The review of fe9533c1: a new test function raised a count already over the
budget, so two broker-pin tests are one; KVWatch no longer yields every
change, and its doc says so (issue 1966).
Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
164 lines
4.9 KiB
Go
164 lines
4.9 KiB
Go
package link
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"errors"
|
|
"math/big"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// A real TLS server with a real self-signed certificate. Not a fake: what is being tested is that
|
|
// Go's TLS stack calls this verification before the handshake completes and that a wrong
|
|
// certificate is refused there — a fake would assert that the fake refuses it
|
|
// (novox/hq ADR 0017).
|
|
func server(t *testing.T) (address string, fingerprint string) {
|
|
t.Helper()
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{CommonName: "mesh-broker"},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
listener, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{
|
|
Certificates: []tls.Certificate{{Certificate: [][]byte{der}, PrivateKey: key}},
|
|
MinVersion: tls.VersionTLS12,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { listener.Close() })
|
|
|
|
go func() {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func() {
|
|
// Complete the handshake, then close. Enough for a client to have checked.
|
|
_ = conn.(*tls.Conn).Handshake()
|
|
conn.Close()
|
|
}()
|
|
}
|
|
}()
|
|
return listener.Addr().String(), Fingerprint(der)
|
|
}
|
|
|
|
// The right broker is accepted, a different one refused, and a malformed pin is caught before connecting, rather than at
|
|
// the handshake, so a mistyped token fails while a person is looking at it (three tests merged).
|
|
func TestTheRightBrokerIsAcceptedAndAMalformedPinRefused(t *testing.T) {
|
|
address, pin := server(t)
|
|
conn, err := dialPinned(address, pin, 5*time.Second)
|
|
if err != nil {
|
|
t.Fatalf("the broker its token describes was refused: %v", err)
|
|
}
|
|
conn.Close()
|
|
for _, bad := range []string{"", "sha256:short", strings.Repeat("a", 64),
|
|
"sha256:" + strings.Repeat("z", 64), "md5:" + strings.Repeat("a", 64)} {
|
|
if _, err := PinnedConfig(bad); err == nil {
|
|
t.Errorf("%q was accepted as a fingerprint", bad)
|
|
}
|
|
}
|
|
|
|
// The case the pin exists for: something else answering at that address. Since the host
|
|
// applies whatever the link delivers, connecting to the wrong mesh is the whole machine.
|
|
_, other := server(t)
|
|
_, err = dialPinned(address, other, 5*time.Second)
|
|
if err == nil {
|
|
t.Fatal("a broker presenting a different certificate was accepted")
|
|
}
|
|
if !errors.Is(err, ErrWrongCertificate) {
|
|
t.Fatalf("refused, but not as a wrong certificate: %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "retrying will not help") {
|
|
t.Error("the error reads like a connection problem; this one must not be retried")
|
|
}
|
|
}
|
|
|
|
func TestNothingIsSentToTheWrongBroker(t *testing.T) {
|
|
// ADR 0004 requires the check to happen *before* anything is sent. Asserted by counting what
|
|
// the wrong server received: a handshake, and no application bytes.
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
template := x509.Certificate{
|
|
SerialNumber: big.NewInt(2),
|
|
Subject: pkix.Name{CommonName: "impostor"},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
listener, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{
|
|
Certificates: []tls.Certificate{{Certificate: [][]byte{der}, PrivateKey: key}},
|
|
MinVersion: tls.VersionTLS12,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
received := make(chan int, 1)
|
|
go func() {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
received <- -1
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
buf := make([]byte, 512)
|
|
n, _ := conn.Read(buf)
|
|
received <- n
|
|
}()
|
|
|
|
// A pin for a certificate this server does not have.
|
|
_, elsewhere := server(t)
|
|
if _, err := dialPinned(listener.Addr().String(), elsewhere, 5*time.Second); err == nil {
|
|
t.Fatal("the impostor was accepted")
|
|
}
|
|
if n := <-received; n > 0 {
|
|
t.Errorf("%d application byte(s) reached a broker that failed the pin", n)
|
|
}
|
|
}
|
|
|
|
func TestAnUnreachableBrokerIsAnOrdinaryFailure(t *testing.T) {
|
|
// Must not read as a wrong certificate: one is a network problem worth retrying, the other
|
|
// means the mesh was substituted.
|
|
_, pin := server(t)
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
address := listener.Addr().String()
|
|
listener.Close()
|
|
|
|
_, err = dialPinned(address, pin, 2*time.Second)
|
|
if err == nil {
|
|
t.Fatal("dialling a closed port succeeded")
|
|
}
|
|
if errors.Is(err, ErrWrongCertificate) {
|
|
t.Error("an unreachable broker was reported as presenting the wrong certificate")
|
|
}
|
|
}
|