Files
mesh-host/internal/link/pinned_test.go
T
jschoubben c7468656af The engine dials as the login the vault's node-logins names
A membership may name its Logins (ADR 0583 §1): the login in use and the password of each login held, kept beside the
identity, so a restart dials as the login in use. The engine follows its own key of node-logins as a watch on every link
and dials as the login it names when it holds its value, with the node's one inbox; with no key, or a key naming the first
login, it dials node.<n> with the membership's password as before. A change of the login in use drops the link so it is
opened again as the new one, and the vault is asked in the name of the login dialled (the subject names the caller).
Tests: one added, paid by merging the pinned-broker test with the malformed-pin test (two into one).

(issue 774, 774/S61)

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 01:49:01 +01:00

168 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; 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 (two 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)
}
}
}
func TestADifferentBrokerIsRefused(t *testing.T) {
// 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.
address, _ := server(t)
_, 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")
}
}