REALITY.5d: serve the hand-rolled flight on the relay's authed path (last of REALITY.5) #87

Merged
vxfemboy merged 6 commits from feat/reality-5d-wire-server-flight into main 2026-07-19 01:19:35 +00:00
vxfemboy commented 2026-07-18 18:45:03 +00:00 (Migrated from github.com)

REALITY.5d — wire the hand-rolled server flight into the relay

Stacked on #86 (REALITY.5c). Base is feat/reality-5c-server-flight-emit; review the 5d delta here, merge after #86. PR 4 of 4 — the LAST of REALITY.5 (#76), and the last anti-DPI item.

The relay's authed REALITY path now serves the dest-faithful hand-rolled flight it can build (5b ServerHello + 5c encrypted flight), replacing the BoringSSL SslAcceptor. After this, an authed connection to the relay is byte/length-indistinguishable from a genuine Chrome↔dest TLS 1.3 session end to end — closing the passive-DPI gap REALITY.3 explicitly left open for the authed path.

What's here (all yip-rendezvous)

  • ParserClientHelloInfo.key_share_mlkem_x25519: the x25519 bundled in the client's group-4588 key_share entry (mlkem_ek(1184) ‖ x25519(32)), fail-closed. The authed path keys the 4588 hybrid DH against this (and the standalone 0x001d share for group 29) — correct-by-construction, not relying on the client reusing one ephemeral.
  • Genericized pumphandle_connection + read_and_classify + into_decoy + run_tunnel now flow any AsyncRead+AsyncWrite+Unpin+Send stream, so a yip_utls::RealityStream runs through the same relay logic as an SslStream (the non-REALITY relay-Trojan path is unchanged).
  • The authed-path rewrite (run_reality_conn's Decision::Accept): look up the captured template + fields for the SNI → forge the leaf (forge_leaf, SPKI = derive_cert_key(shared), natural size) → emit_server_hello (5b) and write it → serve the 5c flight under HANDSHAKE_TIMEOUT → pump the tunnel. The dead build_forged_acceptor* / CacheEntry.acceptor / RealityCertCache.key are removed.

The splice-vs-drop fail-safe boundary

Splicing forwards a pristine connection to the real dest (a prober sees a genuine site) — but only works before we've written our own bytes. So: every failure before the ServerHello write (no template, unsupported group, missing client share, forge/emit error, OS-rng failure) → splice; every failure after (serve error/timeout/IO) → drop (the client's 4b fallback covers it). The final review traced every early-return and confirmed the boundary holds in both directions.

Design decisions

  • Natural forged leaf, no exact-length padding (your call): the passive DPI can't see the Certificate message size (it's inside the AEAD); a real dest's leaf carries SCTs that forge_leaf drops, so a forged flight is reliably smaller than dest's and fits the captured record budget. FlightTooLarge → splice is the fail-safe if it ever doesn't.
  • OS-CSPRNG rng for the ServerHello (never seeded); serve bounded by HANDSHAKE_TIMEOUT (its CCS-drain is otherwise unbounded); anti-replay unchanged, runs before the branch.

The gate — a live end-to-end money test

bin/yipd/tests/run-netns-reality-5d.sh (ran under sudo, PASS): a real yipd client dialing reality://…&verify=on tunnels A→B through the relay whose authed path hand-rolls the flight — relay-forwarded=2106 packets (that counter is reachable only via the authed tunnel path, so >0 proves the 5b+5c+5d handshake completed and the 4b binding verified). A wrong-key client fails closed (0 packets, no retry-storm). The dest is a local openssl s_server -tls1_3 (hermetic). Wired into CI.

Scope / non-goals

  • No client changes (4a/4b shipped), no anti-replay changes, no 5b/5c construction changes, no P256/P384+HRR (#84 — such a group → splice), no dropping the boring dep (the relay-Trojan front + extract_fields still use it). yip_utls is untouched.

Follow-ups (non-blocking, from the final review)

  • Hoist the CertVerify signing-key load above the ServerHello write (keeps all fallible steps on the splice side — its failure is currently unreachable).
  • A group-29-dest test (the 4588 path is netns-covered; group-29 is functionally sound but untested).
  • Exact-leaf-length padding (deferred by design) + the 5c cleanups (Error::MessageTooLarge, u24-helper extraction).

Final whole-branch review (opus): READY WITH FOLLOW-UPS — splice-vs-drop correct in both directions, no fail-open / no panic-on-input / no dangling reference, yip_utls untouched, forbid-unsafe / no as / no bare #[allow].

With this, REALITY.5 (5a/5b/5c/5d) is complete — the authed REALITY path is end-to-end dest-faithful.

## REALITY.5d — wire the hand-rolled server flight into the relay **Stacked on #86 (REALITY.5c).** Base is `feat/reality-5c-server-flight-emit`; review the 5d delta here, merge after #86. **PR 4 of 4 — the LAST of REALITY.5 (#76), and the last anti-DPI item.** The relay's authed REALITY path now **serves** the dest-faithful hand-rolled flight it can build (5b ServerHello + 5c encrypted flight), replacing the BoringSSL `SslAcceptor`. After this, an authed connection to the relay is byte/length-indistinguishable from a genuine Chrome↔`dest` TLS 1.3 session end to end — closing the passive-DPI gap REALITY.3 explicitly left open for the authed path. ### What's here (all `yip-rendezvous`) - **Parser** — `ClientHelloInfo.key_share_mlkem_x25519`: the x25519 bundled in the client's group-4588 key_share entry (`mlkem_ek(1184) ‖ x25519(32)`), fail-closed. The authed path keys the 4588 hybrid DH against this (and the standalone `0x001d` share for group 29) — correct-by-construction, not relying on the client reusing one ephemeral. - **Genericized pump** — `handle_connection` + `read_and_classify` + `into_decoy` + `run_tunnel` now flow any `AsyncRead+AsyncWrite+Unpin+Send` stream, so a `yip_utls::RealityStream` runs through the same relay logic as an `SslStream` (the non-REALITY relay-Trojan path is unchanged). - **The authed-path rewrite** (`run_reality_conn`'s `Decision::Accept`): look up the captured template + fields for the SNI → forge the leaf (`forge_leaf`, SPKI = `derive_cert_key(shared)`, natural size) → `emit_server_hello` (5b) and write it → `serve` the 5c flight under `HANDSHAKE_TIMEOUT` → pump the tunnel. The dead `build_forged_acceptor*` / `CacheEntry.acceptor` / `RealityCertCache.key` are removed. ### The splice-vs-drop fail-safe boundary Splicing forwards a pristine connection to the real `dest` (a prober sees a genuine site) — but only works before we've written our own bytes. So: every failure **before** the ServerHello write (no template, unsupported group, missing client share, forge/emit error, OS-rng failure) → **splice**; every failure **after** (serve error/timeout/IO) → **drop** (the client's 4b fallback covers it). The final review traced every early-return and confirmed the boundary holds in both directions. ### Design decisions - **Natural forged leaf, no exact-length padding** (your call): the passive DPI can't see the Certificate message size (it's inside the AEAD); a real dest's leaf carries SCTs that `forge_leaf` drops, so a forged flight is reliably *smaller* than dest's and fits the captured record budget. `FlightTooLarge` → splice is the fail-safe if it ever doesn't. - **OS-CSPRNG rng** for the ServerHello (never seeded); `serve` bounded by `HANDSHAKE_TIMEOUT` (its CCS-drain is otherwise unbounded); anti-replay unchanged, runs before the branch. ### The gate — a live end-to-end money test `bin/yipd/tests/run-netns-reality-5d.sh` (**ran under sudo, PASS**): a real yipd client dialing `reality://…&verify=on` tunnels A→B through the relay whose authed path hand-rolls the flight — relay-forwarded=2106 packets (that counter is reachable only via the authed tunnel path, so >0 proves the 5b+5c+5d handshake completed *and* the 4b binding verified). A wrong-key client fails closed (0 packets, no retry-storm). The dest is a local `openssl s_server -tls1_3` (hermetic). Wired into CI. ### Scope / non-goals - No client changes (4a/4b shipped), no anti-replay changes, no 5b/5c construction changes, no P256/P384+HRR (#84 — such a group → splice), no dropping the `boring` dep (the relay-Trojan front + `extract_fields` still use it). `yip_utls` is untouched. ### Follow-ups (non-blocking, from the final review) - Hoist the CertVerify signing-key load above the ServerHello write (keeps all fallible steps on the splice side — its failure is currently unreachable). - A group-29-dest test (the 4588 path is netns-covered; group-29 is functionally sound but untested). - Exact-leaf-length padding (deferred by design) + the 5c cleanups (`Error::MessageTooLarge`, u24-helper extraction). Final whole-branch review (opus): **READY WITH FOLLOW-UPS** — splice-vs-drop correct in both directions, no fail-open / no panic-on-input / no dangling reference, `yip_utls` untouched, `forbid-unsafe` / no `as` / no bare `#[allow]`. **With this, REALITY.5 (5a/5b/5c/5d) is complete — the authed REALITY path is end-to-end dest-faithful.**
Sign in to join this conversation.
No description provided.