docs(rv2-tls): lock two more F3c-net requirements from the gofiber/Go comparison

Compared §F3c-net's forks against gofiber v3's client (fasthttp + Go
crypto/tls/x509, .dev/reference/fiber). Two gaps my defaults had vs Go,
now locked as decisions 5 and 6:

- (5) bounded handshake deadline: the blocking model would let a stalled
  server hang the shard's one thread indefinitely (the DoS DoTimeout
  closes). connect_tls now bounds connect+handshake via non-blocking
  connect+poll + SO_RCVTIMEO/SNDTIMEO, default WO_TLS_HANDSHAKE_MS
  (10s); expiry traps WO_T_IO. _dl variant + park handshake stay follow-ups
- (6) chain hardening: signatures+validity+SAN alone let a leaf act as a
  CA. Now every non-leaf must assert basicConstraints CA:TRUE (+pathLen)
  and the leaf must carry EKU serverAuth — what Go's crypto/x509 enforces
- acceptance criteria added (stalled-server timeout; leaf-as-CA + no-EKU
  rejected); connect_tls bullet, frontmatter review_pending, status NEXT
  PLAN updated to six locked forks

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9662cd8b040f417b4886dae9ce97b70083e24e40)
This commit is contained in:
shoney.arickathil 2026-09-09 02:18:06 +02:00
parent f1e355c4d4
commit 53485a748c
2 changed files with 48 additions and 13 deletions

View file

@ -95,12 +95,16 @@ developer second review before this drives a live connection.
**Next step — BUILD F3c-net (the only remaining rung before jarvis unblocks).** **Next step — BUILD F3c-net (the only remaining rung before jarvis unblocks).**
Its spec is now `ready`: [rv2 9 §F3c-net](runtime-v2/09-in-process-tls.md) Its spec is now `ready`: [rv2 9 §F3c-net](runtime-v2/09-in-process-tls.md)
brainstormed 2026-09-09 with the four integration forks **locked** (grounded in brainstormed 2026-09-09 with **six** integration forks **locked** (four grounded
the runtime, not assumed): (1) blocking connect+handshake then park the data in the runtime, two from a gofiber/Go `crypto/x509` comparison): (1) blocking
plane, mirroring `net.connect` — park-based handshake a named follow-up; (2) a connect+handshake then park the data plane, mirroring `net.connect` — park-based
per-shard fd-keyed `wo_tls_conn` slot table, no locks (the `wo_child` pattern); handshake a named follow-up; (2) a per-shard fd-keyed `wo_tls_conn` slot table,
(3) failures trap `WO_T_IO` loudly incl. chain/hostname; (4) per-shard lazy no locks (the `wo_child` pattern); (3) failures trap `WO_T_IO` loudly incl.
read-only CA bundle (`/etc/ssl/certs/…`, `WO_CA_BUNDLE` override). Builtins: chain/hostname; (4) per-shard lazy read-only CA bundle (`/etc/ssl/certs/…`,
`WO_CA_BUNDLE` override); (5) a **bounded handshake deadline**
(`WO_TLS_HANDSHAKE_MS`, non-blocking connect+poll + `SO_RCVTIMEO/SNDTIMEO`) so a
stalled server can't hang the shard; (6) **chain hardening** — basicConstraints
CA:TRUE + pathLen + leaf EKU `serverAuth`, not just signatures. Builtins:
`net.connect_tls`/`read_tls`/`write_tls` (ids 115–117, `WO_B_MAX`→117), wiring `net.connect_tls`/`read_tls`/`write_tls` (ids 115–117, `WO_B_MAX`→117), wiring
across `wob.h`/`emit.ml`/`types.ml`/`loader.c`/`builtin.c`/`sysio.c`, live-gated across `wob.h`/`emit.ml`/`types.ml`/`loader.c`/`builtin.c`/`sysio.c`, live-gated
against a local TLS server. Then **G** (inbound server) for porch; after that against a local TLS server. Then **G** (inbound server) for porch; after that

View file

@ -3,7 +3,7 @@ track: runtime-v2
iteration: "9" iteration: "9"
status: in-progress status: in-progress
readiness: ready readiness: ready
review_pending: "forks auto-approved 2026-09-08/09 for autonomous execution — developer second review before this ships. Landed + KAT'd (RFC 8448 / real certs): A–E crypto, F1 record, F2 key schedule, F3a messages, F3b offline verify, F3c-core sans-io driver, SAN/hostname, F3c-net chain validation. §F3c-net (socket/VM slice) brainstormed to READY 2026-09-09 with the four integration forks locked (blocking connect+handshake then park data I/O; per-shard fd-keyed slot table no-locks; failures trap WO_T_IO; per-shard lazy read-only CA bundle). Remaining to BUILD: F3c-net (net.connect_tls/read_tls/write_tls, ids 115-117, live-gated), then G inbound server" review_pending: "forks auto-approved 2026-09-08/09 for autonomous execution — developer second review before this ships. Landed + KAT'd (RFC 8448 / real certs): A–E crypto, F1 record, F2 key schedule, F3a messages, F3b offline verify, F3c-core sans-io driver, SAN/hostname, F3c-net chain validation. §F3c-net (socket/VM slice) brainstormed to READY 2026-09-09 with six integration forks locked (blocking connect+handshake then park data I/O; per-shard fd-keyed slot table no-locks; failures trap WO_T_IO; per-shard lazy read-only CA bundle; a bounded handshake deadline WO_TLS_HANDSHAKE_MS; chain hardening = basicConstraints CA:TRUE + EKU serverAuth — the last two added from the gofiber/Go crypto/x509 comparison). Remaining to BUILD: F3c-net (net.connect_tls/read_tls/write_tls, ids 115-117, live-gated), then G inbound server"
--- ---
# runtime-v2 9 — in-process TLS: retiring the proxy-termination doctrine # runtime-v2 9 — in-process TLS: retiring the proxy-termination doctrine
@ -86,13 +86,16 @@ they may split into their own runtime-v2 iterations as they are picked up.
Everything security-critical is landed and offline-KAT'd. What is left is I/O Everything security-critical is landed and offline-KAT'd. What is left is I/O
integration that can only be gated **live** (a local `openssl s_server` / python integration that can only be gated **live** (a local `openssl s_server` / python
TLS server), so it is one cohesive slice, not further split. The four TLS server), so it is one cohesive slice, not further split. The integration
integration forks are settled below, each grounded in the existing runtime, not forks are settled below — the first four grounded in the existing runtime, the
assumed. This section is `ready`: the decisions are locked, the acceptance last two added 2026-09-09 from a comparison against **gofiber v3's client**
(fasthttp + Go `crypto/tls`/`crypto/x509`, in `.dev/reference/fiber`), which
bounds every request with a timeout and delegates full chain checks to
`crypto/x509`. This section is `ready`: the decisions are locked, the acceptance
criteria are stated, and code may start once a developer signs off the criteria are stated, and code may start once a developer signs off the
`review_pending` marker. `review_pending` marker.
### The four decisions, locked ### The locked decisions
1. **Blocking connect + blocking handshake, then park the data plane.** This 1. **Blocking connect + blocking handshake, then park the data plane.** This
mirrors `net.connect` exactly (`sysio.c` `WO_B_NET_CONNECT`): the socket is mirrors `net.connect` exactly (`sysio.c` `WO_B_NET_CONNECT`): the socket is
@ -128,6 +131,24 @@ criteria are stated, and code may start once a developer signs off the
with (2). Path: `/etc/ssl/certs/ca-certificates.crt` (confirmed present on the with (2). Path: `/etc/ssl/certs/ca-certificates.crt` (confirmed present on the
dev box), overridable by the `WO_CA_BUNDLE` environment variable — which is dev box), overridable by the `WO_CA_BUNDLE` environment variable — which is
also how the live gate points the client at its self-signed test CA. also how the live gate points the client at its self-signed test CA.
5. **A bounded handshake deadline (no unbounded shard stall).** The blocking
model of decision (1) would otherwise let a slow or hostile server stall the
shard's one thread indefinitely during connect + handshake — the DoS that
gofiber closes with `DoTimeout`. So `net.connect_tls` bounds the whole
connect+handshake by a deadline: **non-blocking `connect()` + `poll` for the
TCP step, and `SO_RCVTIMEO`/`SO_SNDTIMEO` on the blocking socket across the
handshake**, capping the stall without needing the full park refit. Default
from `WO_TLS_HANDSHAKE_MS` (10 000 ms if unset); expiry aborts and traps
`WO_T_IO` ("tls: handshake timeout"). A per-call `_dl` variant and the
park-based handshake remain the named follow-ups.
6. **Chain hardening: basicConstraints + EKU (not just signatures).** Signature
+ validity + SAN is not enough — Go's `crypto/x509` also enforces the
constraints that stop a leaf from masquerading as a CA. So the phase-E
extension walk and `wo_tls_verify_chain` gain: every **non-leaf** cert must
assert `basicConstraints` CA:TRUE and satisfy `pathLenConstraint`, and the
**leaf** must carry Extended Key Usage `id-kp-serverAuth` (or omit EKU
entirely). A `keyUsage` `keyCertSign` check on issuers is included where
present. Failure is a rejection like any other chain fault (no partial trust).
### The builtin surface ### The builtin surface
@ -139,8 +160,10 @@ Three new builtins on the `net` module (one numeric id space; `WO_B_MAX` moves
X25519 key + ClientHello random/session-id, run the sans-io driver over the X25519 key + ClientHello random/session-id, run the sans-io driver over the
blocking socket (frame each record: read the 5-byte header, then the body; blocking socket (frame each record: read the 5-byte header, then the body;
flush `take_output`) to ESTABLISHED, set the host on the driver so the leaf flush `take_output`) to ESTABLISHED, set the host on the driver so the leaf
SAN is enforced, then `wo_tls_verify_chain` against the lazily-loaded anchors. SAN is enforced, then `wo_tls_verify_chain` against the lazily-loaded anchors
Returns the fd (a slot is claimed for it); traps on any failure. (with the decision-6 basicConstraints/EKU checks). The whole connect+handshake
is bounded by the decision-5 deadline. Returns the fd (a slot is claimed for
it); traps on any failure.
- `net.read_tls(fd, max) -> Bytes` — id **116**, arity 2. Reads/decrypts one - `net.read_tls(fd, max) -> Bytes` — id **116**, arity 2. Reads/decrypts one
application record via the slot, returning up to `max` plaintext bytes (EOF is application record via the slot, returning up to `max` plaintext bytes (EOF is
the empty Bytes), buffering a partial record and parking on POLLIN, and the empty Bytes), buffering a partial record and parking on POLLIN, and
@ -165,6 +188,14 @@ consumer change beyond the id additions.
- **Given** a server whose certificate does not chain to a trusted anchor, whose - **Given** a server whose certificate does not chain to a trusted anchor, whose
SAN does not match the host, or is expired, **when** `net.connect_tls` runs, SAN does not match the host, or is expired, **when** `net.connect_tls` runs,
**then** it traps `WO_T_IO` — no connection is returned. **then** it traps `WO_T_IO` — no connection is returned.
- **Given** a server that accepts the TCP connection but then stalls (never
finishing the handshake), **when** the decision-5 deadline elapses,
**then** `net.connect_tls` aborts and traps `WO_T_IO` rather than stalling the
shard indefinitely — proven with a stub that connects then sleeps.
- **Given** a chain whose issuer lacks `basicConstraints` CA:TRUE (a leaf used
to sign another cert), or a leaf lacking EKU `serverAuth`, **when**
`net.connect_tls` validates it, **then** it is rejected — with negative KATs
in `test_tls` alongside the existing chain cases.
- **Given** two shards each dialing TLS, **when** they run concurrently, **then** - **Given** two shards each dialing TLS, **when** they run concurrently, **then**
neither reads the other's slot or bundle (per-shard, no locks), proven under neither reads the other's slot or bundle (per-shard, no locks), proven under
ASan/TSan. ASan/TSan.