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).**
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
the runtime, not assumed): (1) blocking connect+handshake then park the data
plane, mirroring `net.connect` — park-based handshake a named follow-up; (2) a
per-shard fd-keyed `wo_tls_conn` slot table, no locks (the `wo_child` pattern);
(3) failures trap `WO_T_IO` loudly incl. chain/hostname; (4) per-shard lazy
read-only CA bundle (`/etc/ssl/certs/…`, `WO_CA_BUNDLE` override). Builtins:
brainstormed 2026-09-09 with **six** integration forks **locked** (four grounded
in the runtime, two from a gofiber/Go `crypto/x509` comparison): (1) blocking
connect+handshake then park the data plane, mirroring `net.connect` — park-based
handshake a named follow-up; (2) a per-shard fd-keyed `wo_tls_conn` slot table,
no locks (the `wo_child` pattern); (3) failures trap `WO_T_IO` loudly incl.
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
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

View file

@ -3,7 +3,7 @@ track: runtime-v2
iteration: "9"
status: in-progress
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
@ -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
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
integration forks are settled below, each grounded in the existing runtime, not
assumed. This section is `ready`: the decisions are locked, the acceptance
TLS server), so it is one cohesive slice, not further split. The integration
forks are settled below — the first four grounded in the existing runtime, the
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
`review_pending` marker.
### The four decisions, locked
### The locked decisions
1. **Blocking connect + blocking handshake, then park the data plane.** This
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
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.
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
@ -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
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
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.
SAN is enforced, then `wo_tls_verify_chain` against the lazily-loaded anchors
(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
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
@ -165,6 +188,14 @@ consumer change beyond the id additions.
- **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,
**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**
neither reads the other's slot or bundle (per-shard, no locks), proven under
ASan/TSan.