We maintain three independent hand-rolled TLS 1.3 implementations that must agree byte-for-byte — the pinned Chrome JA4, cipher 0x1301, X25519, the HKDF key schedule, the ClientToken/ServerToken construction, and the record framing: the Go node (node/internal/server/serverhandshake.go, reality.go, record.go, clienthello.go), the Swift client (ios/APNExtension/Handshake.swift, Record.swift, ClientToken.swift, ServerToken.swift), and the Kotlin client (android/.../vpn/Handshake.kt, Record.kt, ClientHello.kt). The only interop test is node/internal/server/handshake_interop_test.go:15, which merely checks that Go's crypto/tls stdlib client completes a handshake against our server.
That test proves nothing about the two clients that actually ship. Nothing asserts the three stacks derive identical ClientToken, ServerToken, ServerHello.random, session keys, and sealed records from the same inputs, so a silent divergence in the handshake or record layer would either break a client or open a security hole with no test to catch it — and cross-language drift is already a live problem here (the proto copies drift and break the iOS build). There is also no fuzzing of the ClientHello/record parsers (reality.Inspect, fromBytes, readRecord) even though they parse attacker-controlled bytes on every connection. WireGuard sidesteps this entirely with a single reference implementation whose Noise handshake is formally verified (Tamarin, CryptoVerif, ProVerif); we cannot formally verify three hand-rolled stacks, but we currently have no substitute assurance either.
Two concrete steps: add a shared, checked-in set of handshake/AEAD test vectors — a fixed PSK plus a fixed ephemeral key mapping to the expected ClientToken, ServerToken, ServerHello.random, derived app keys, and one sealed application_data record — asserted by unit tests in all three repos so any drifting stack fails; and add a Go fuzz target (testing.F) over reality.Inspect, fromBytes, and readRecord. Filing per a WireGuard comparison review.
We maintain three independent hand-rolled TLS 1.3 implementations that must agree byte-for-byte — the pinned Chrome JA4, cipher
0x1301, X25519, the HKDF key schedule, the ClientToken/ServerToken construction, and the record framing: the Go node (node/internal/server/serverhandshake.go,reality.go,record.go,clienthello.go), the Swift client (ios/APNExtension/Handshake.swift,Record.swift,ClientToken.swift,ServerToken.swift), and the Kotlin client (android/.../vpn/Handshake.kt,Record.kt,ClientHello.kt). The only interop test isnode/internal/server/handshake_interop_test.go:15, which merely checks that Go'scrypto/tlsstdlib client completes a handshake against our server.That test proves nothing about the two clients that actually ship. Nothing asserts the three stacks derive identical ClientToken, ServerToken,
ServerHello.random, session keys, and sealed records from the same inputs, so a silent divergence in the handshake or record layer would either break a client or open a security hole with no test to catch it — and cross-language drift is already a live problem here (the proto copies drift and break the iOS build). There is also no fuzzing of the ClientHello/record parsers (reality.Inspect,fromBytes,readRecord) even though they parse attacker-controlled bytes on every connection. WireGuard sidesteps this entirely with a single reference implementation whose Noise handshake is formally verified (Tamarin, CryptoVerif, ProVerif); we cannot formally verify three hand-rolled stacks, but we currently have no substitute assurance either.Two concrete steps: add a shared, checked-in set of handshake/AEAD test vectors — a fixed PSK plus a fixed ephemeral key mapping to the expected ClientToken, ServerToken,
ServerHello.random, derived app keys, and one sealedapplication_datarecord — asserted by unit tests in all three repos so any drifting stack fails; and add a Go fuzz target (testing.F) overreality.Inspect,fromBytes, andreadRecord. Filing per a WireGuard comparison review.