Skip to content

REALITY.5b: byte-matching ServerHello emission + server key schedule (4588+X25519, round-trip proven)#85

Closed
vxfemboy wants to merge 7 commits into
feat/reality-5a-dest-flight-capturefrom
feat/reality-5b-serverhello-emit
Closed

REALITY.5b: byte-matching ServerHello emission + server key schedule (4588+X25519, round-trip proven)#85
vxfemboy wants to merge 7 commits into
feat/reality-5a-dest-flight-capturefrom
feat/reality-5b-serverhello-emit

Conversation

@vxfemboy

Copy link
Copy Markdown
Member

REALITY.5b — byte-matching ServerHello emission + server key schedule

Stacked on #83 (REALITY.5a). Base is feat/reality-5a-dest-flight-capture; review the 5b delta here, merge after #83. PR 2 of 4 for REALITY.5 (#76).

The server-side counterpart to REALITY.2's client. Given the dest template captured in 5a, the relay emits a byte-matching ServerHello — structurally identical to dest's (cipher, ordered extensions incl. GREASE, chosen group) but keyed on the relay's own ephemeral, so the relay holds the session keys. A passive DPI byte-matches the cleartext ServerHello; dest's real key_share/random are per-connection random too, so substituting the relay's own values is indistinguishable.

What's here

  • yip-utls::server::server_key_share — the relay's server-side KEX.
    • Group 4588 (X25519MLKEM768): ML-KEM Encapsulate against the client's ek (the mirror of REALITY.2's client Decapsulate) + X25519 DH; server_key_share = ct(1088) ‖ x25519_pub(32), shared = mlkem_ss ‖ x25519_ss — the same combiner order the client uses.
    • Group 29 (X25519): plain X25519.
    • Any other group → Err(UnsupportedGroup) (5d degrades to splice-only; P256/P384 + HelloRetryRequest deferred to REALITY: P256/P384 server-side KEX + HelloRetryRequest support (evaluate whether it's worth it) #84).
  • yip-utls::server::emit_server_hello — rebuilds the ServerHello verbatim from the captured ServerHelloShape, substituting only a fresh 32-byte random, the client's legacy_session_id echo, and the key_share ext body (the relay's own). Derives handshake keys over transcript = ClientHello ‖ ServerHello via REALITY.2's derive_handshake_keys (reused unchanged).
  • yip-rendezvous::realityClientHelloInfo.key_share_mlkem_ek extracted from the group-4588 key_share entry (mlkem_ek(1184) ‖ x25519(32)), fail-closed on a hostile/malformed ClientHello. This is what 5d feeds to emit_server_hello.

Correctness gate: the round-trip

The proof is a genuine round-trip (not a tautology): a test client — REALITY.2's own parse_server_hello + the client KEX (decapsulate/DH, separate RNGs) + derive_handshake_keys — parses the relay's emitted ServerHello and derives the byte-identical HandshakeKeys, for both 4588 and X25519. If the combiner order or X25519 direction were wrong, no keys would match. Plus a byte-match test asserting only random/session_id/key_share differ from the template (GREASE and extension order preserved).

Scope / non-goals

5d hand-off notes

  • group-4588 X25519 sourcing: the DH's X25519 half should key against the x25519 bundled in the 4588 entry (…‖ x25519(32) tail), not the standalone 0x001d entry. Safe within 5b because yip's own client reuses one ephemeral across both shares; 5d must either thread the 4588-tail x25519 through or assert that invariant. Fails closed (dead session), no security downgrade, if ever violated.
  • RNG: server_key_share's rng drives ML-KEM encapsulation + the X25519 ephemeral; 5d must wire an OS-CSPRNG-backed RandomSource.

Tests

  • server_key_share shapes/secret-lengths for 4588 + 29; unsupported group + missing ek → Err.
  • emit_server_hello round-trips (4588 + 29) with identical keys; byte-match except substituted fields; u24 body / u16 ext length guards.
  • parse_client_hello extracts the 1184-byte mlkem_ek; wrong-length 4588 entry → None; distinct x25519 in the two entries proves key_share_x25519 stays sourced from 0x001d.

Final whole-branch review (opus): READY TO MERGE — KEX verified end-to-end, every network-reachable path fail-closed, ServerHello byte-fidelity preserved, forbid-unsafe / no as / no bare #[allow].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant