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) } func TestTheRightBrokerIsAccepted(t *testing.T) { address, pin := server(t) conn, err := Dial(address, pin, 5*time.Second) if err != nil { t.Fatalf("the broker its token describes was refused: %v", err) } conn.Close() } 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 := Dial(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 := Dial(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 TestAMalformedPinIsRefusedBeforeConnecting(t *testing.T) { // Caught here rather than at the handshake, so a mistyped token fails while a person is // looking at it. 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 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 = Dial(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") } }