docs(rv2-tls): brainstorm runtime-v2 9 (in-process TLS) to ready — hand-rolled
- decision: HAND-ROLL TLS 1.3 (no vendored lib) per developer call; keeps the zero-external-dep single binary, and raises risk rather than lowering it — recorded, owned, with mandatory mitigations - 1.3-only; RSA-PSS/PKCS1 + ECDSA-P256 + full ASN.1/X.509 chain validation + trust store + hostname (the scope needed to reach real LLM APIs) - decomposed into a bottom-up phase ladder: A AEAD (=rv2 8, forces AES-GCM there) -> B HKDF -> C X25519 -> D signatures/RSA -> E X.509 -> F record+FSM client -> G inbound server; C/D/E may each split into own iterations - risk + test strategy section: constant-time, reference-tested (openssl + RFC 8448 vectors), negative tests first-class, no partial-trust states - deps: rv2 8 (AEAD), lang 34 (SHA/HMAC), net.connect (110, landed). Board synced (cherry picked from commit f1881cca8cd0cdd58b1ac844e3e3ea9c234bb99c)
This commit is contained in:
parent
cc1c82b2ef
commit
51addb504c
2 changed files with 74 additions and 44 deletions
|
|
@ -1543,7 +1543,7 @@ starts. Edges in [dependency graph section 6](../00-dependency-graph.md).
|
|||
| 6 | [term.size + term.width](runtime-v2/06-term-size-width.md) | ✅ **DONE 2026-09-02** — TIOCGWINSZ read twin (nil = not a tty) and libc wcwidth under C.UTF-8; the only runtime work the whole wmux parity ladder needs |
|
||||
| 7 | [observability](runtime-v2/07-observability.md) | ⬜ `refine` — **moved here 2026-09-06** from language iteration 30 (`was_language_iteration: 30`). Runtime metrics/gauges, a `pprof`-equivalent profile, stack-trace-on-trap; consumers named (porch [8](porch/08-static-and-lifecycle.md)/[39](language-runtime-database/39-web-framework-parity.md), databasev2 [5](databasev2/05-bounded-tables-eviction.md), the limiter's lazy expiry). Forks: counters-only vs profiling, exposition format, pull vs push, trace-on-trap as a separable first slice. Stretches the track's charter (instrumentation, not processes/terminals/signals) — noted in the story |
|
||||
| 8 | [symmetric cipher](runtime-v2/08-symmetric-cipher.md) | ⬜ `refine` — **created 2026-09-06** from the [porch↔fiber scope-gap](../plan/exploration/fiber/01-porch-vs-fiber-scope-gap.md); a cipher builtin fits the builtin-seam shape. AEAD for encrypted cookies (porch [2](porch/02-randomness-and-cookies.md) out-of-scope) + data-at-rest; extends [34](language-runtime-database/34-crypto-builtins.md)'s digests, needs porch 2's `random_bytes` for nonces. Load-bearing fork: AES-256-GCM (expected, hard constant-time in software) vs ChaCha20-Poly1305 (easier hand-roll, no-dep doctrine fit) |
|
||||
| 9 | [in-process TLS](runtime-v2/09-in-process-tls.md) | ⬜ `refine` — **created 2026-09-07** from the gap [jarvis](jarvis/00-story.md) surfaces. TLS **both directions** (outbound client + inbound termination), **retiring the "TLS is the proxy's job" doctrine** (recorded in 34/38/porch). The track's heaviest seam — **not** builtin-sized, and likely a **vendored-lib exception** to no-external-deps (TLS is the one thing not to hand-roll). Load-bearing fork (left open): vendor mbedTLS/BearSSL vs hand-roll a subset. Sits on language 38's `net.connect`; gates jarvis entirely |
|
||||
| 9 | [in-process TLS](runtime-v2/09-in-process-tls.md) | ✅ **`ready` 2026-09-07** — TLS **both directions**, **retiring the "TLS is the proxy's job" doctrine** (34/38/porch). Locked: **hand-roll TLS 1.3** (no vendored lib — keeps the zero-dep binary, raises the risk), **1.3-only**, **RSA+ECDSA+full X.509** cert verification (to reach real APIs). Decomposed into a bottom-up **phase ladder**: A AEAD (=rv2 8, forces AES-GCM there) → B HKDF → C X25519 → D signatures/RSA → E ASN.1/X.509 → F record+FSM client → G server. The project's **highest-risk** work; mandatory reference-tested/constant-time/negative-test gates. `net.connect` (110) landed; C/D/E may each split into own iterations |
|
||||
|
||||
### ▸ wmux — the terminal multiplexer track
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
track: runtime-v2
|
||||
iteration: "9"
|
||||
status: pending
|
||||
readiness: refine
|
||||
readiness: ready
|
||||
---
|
||||
|
||||
# runtime-v2 9 — in-process TLS: retiring the proxy-termination doctrine
|
||||
|
|
@ -11,9 +11,10 @@ readiness: refine
|
|||
> assistant must dial an LLM over HTTPS, and the runtime has no outbound TLS. The
|
||||
> developer chose the **full overturn**: the runtime gains TLS **both
|
||||
> directions**, and the standing "TLS is the proxy's job" doctrine is retired.
|
||||
> **`readiness: refine`** — the gap, its consumers and its forks are named here;
|
||||
> the implementation is deliberately left as this story's load-bearing fork, not
|
||||
> settled.
|
||||
> **Brainstormed to `ready` 2026-09-07**: hand-rolled TLS 1.3, RSA+ECDSA+X.509
|
||||
> cert verification, decomposed into the bottom-up phase ladder below. The load-
|
||||
> bearing implementation fork is settled — hand-roll, not vendor — with eyes open
|
||||
> to the risk (Info).
|
||||
|
||||
## Why this exists — and what it overturns
|
||||
|
||||
|
|
@ -44,39 +45,41 @@ X.509 certificate validation is a large, security-critical subsystem — the one
|
|||
place the runtime's hand-roll-everything habit (the sha256 precedent) should not
|
||||
be assumed to extend. That tension is the load-bearing fork below.
|
||||
|
||||
## What it should deliver (scope to be refined)
|
||||
## Decisions locked (brainstorm 2026-09-07)
|
||||
|
||||
- **Outbound TLS client** — a `.wo` program dials an HTTPS endpoint: a TLS
|
||||
handshake over the TCP socket `net.connect` (language 38) provides, with server
|
||||
certificate validation against a trust store. jarvis's direct path, and
|
||||
language 38's deliberately-excluded HTTPS half.
|
||||
- **Inbound TLS server** — porch terminates TLS on its own listener (cert + key
|
||||
loaded at startup), retiring the "put a proxy in front" requirement for a
|
||||
single-binary deployment.
|
||||
- **Certificate validation and a trust store** — X.509 chain verification,
|
||||
hostname/SNI checks outbound; certificate + private-key loading inbound. This
|
||||
is where most of the risk and most of the code live.
|
||||
1. **Hand-roll TLS 1.3 in C — no vendored library.** The developer chose the
|
||||
hand-roll over vendoring mbedTLS/BearSSL, extending the runtime's
|
||||
hand-roll-everything habit (the sha256 precedent) to the hardest place it has
|
||||
reached. This keeps the pure single-static-binary, zero-external-dependency
|
||||
story intact — and it is, stated plainly, the largest and highest-risk
|
||||
undertaking in the project. See the risk note in Info; it is not a caveat to
|
||||
bury.
|
||||
2. **TLS 1.3 only.** No 1.2 legacy — smallest attack surface, one handshake to
|
||||
get right.
|
||||
3. **Cert verification is full: RSA + ECDSA + X.509.** To reach real endpoints
|
||||
(Anthropic, OpenAI and most HTTPS servers present RSA-signed chains), the
|
||||
verifier does RSA-PSS and RSA-PKCS#1v1.5 plus ECDSA-P256, over a real
|
||||
ASN.1/DER + X.509 chain validator with a system trust store, validity-date and
|
||||
hostname (SAN) checks. This is the biggest, most CVE-prone slice, and it is in
|
||||
scope because EC-only cannot talk to the APIs jarvis needs.
|
||||
4. **Bottom-up, outbound-first.** Build the primitives before the protocol, and
|
||||
the client (jarvis's need) before the server (porch's), because the primitives
|
||||
are shared and only the role differs.
|
||||
|
||||
## Forks the brainstorm must settle
|
||||
## The phase ladder
|
||||
|
||||
1. **Implementation source — the load-bearing fork (left open by direction).**
|
||||
Vendor a small, audited TLS library (mbedTLS or BearSSL) compiled into the
|
||||
single static binary — correct and maintainable, but a build-time external
|
||||
dependency, an explicit exception to the no-external-deps doctrine the runtime
|
||||
otherwise holds — versus hand-rolling a TLS 1.3 subset plus X.509 in C, which
|
||||
matches the sha256 precedent but is thousands of security-critical lines and a
|
||||
hand-rolled certificate validator is a CVE factory (strongly discouraged). The
|
||||
honest lean is vendor-a-lib; TLS is exactly the thing not to hand-roll. This
|
||||
fork decides whether the whole "single static binary, no external deps" story
|
||||
gains a footnote.
|
||||
2. **Phasing.** Outbound first (jarvis's actual need) with inbound to follow, or
|
||||
both together since the handshake machinery and the vendored library are
|
||||
shared and only the client-vs-server role and validation direction differ.
|
||||
3. **Trust store and cert provisioning.** Where the outbound trust anchors come
|
||||
from (the system CA bundle, and its path across distros), and how the inbound
|
||||
side is handed its certificate and key (files, env, a reload story).
|
||||
4. **TLS version and cipher policy.** TLS 1.3 only (simplest, modern, smallest
|
||||
attack surface) versus 1.2+1.3 (broader reach). Leaning 1.3-only.
|
||||
Each rung is a security-critical slice; C, D and E are each large enough that
|
||||
they may split into their own runtime-v2 iterations as they are picked up.
|
||||
|
||||
| Phase | Delivers | Notes |
|
||||
| --- | --- | --- |
|
||||
| A — AEAD | AES-128/256-GCM (TLS 1.3 mandates AES-128-GCM) and ChaCha20-Poly1305 | **is runtime-v2 [8](08-symmetric-cipher.md)** — so 8 must include AES-GCM, not only ChaCha; this rung consumes it |
|
||||
| B — key schedule | HKDF-Extract/Expand on iteration 34's HMAC, HKDF-Expand-Label, the TLS 1.3 secret derivation | pure `.wo`-adjacent C over existing HMAC |
|
||||
| C — key exchange | X25519 (RFC 7748), constant-time | new primitive; the ECDHE shared secret feeding B |
|
||||
| D — signatures | RSA-PSS / RSA-PKCS#1v1.5 (bignum modexp) + ECDSA-P256, over the transcript and the chain | the hardest rung; RSA bignum + constant-time |
|
||||
| E — X.509 | ASN.1/DER parser, chain validation to a trust anchor, dates, hostname/SAN, system CA bundle | notoriously bug-prone; consumes D |
|
||||
| F — record + handshake (client) | TLS record framing, the ClientHello→Finished FSM, transcript hash, wiring A–E; `net.connect_tls` outbound | jarvis's path; the reason the story exists |
|
||||
| G — server (inbound) | the server handshake half, cert+key loading, signing CertificateVerify; porch terminates TLS | retires the inbound proxy requirement, and the doctrine docs |
|
||||
|
||||
## Consumers
|
||||
|
||||
|
|
@ -91,9 +94,13 @@ Named, so this is not a capability shipped as decoration:
|
|||
|
||||
## Dependencies
|
||||
|
||||
- **[language 38](../language-runtime-database/38-content-platform-capabilities.md)**
|
||||
— `net.connect` (outbound TCP) is the socket the outbound handshake runs over;
|
||||
the client half of this story sits directly on it.
|
||||
- **runtime-v2 [8](08-symmetric-cipher.md)** — the AEAD (phase A). This story
|
||||
forces 8 to include **AES-GCM** (TLS 1.3 mandates AES-128-GCM), not ChaCha
|
||||
alone — a consequence to record in 8's own fork.
|
||||
- **language [34](../language-runtime-database/34-crypto-builtins.md)** —
|
||||
SHA-256/HMAC for the key schedule (phase B) and the transcript hash.
|
||||
- **`net.connect`** (id 110, **landed 2026-09-07**) — the outbound TCP socket the
|
||||
client handshake runs over; the client half sits directly on it.
|
||||
|
||||
## Out of scope
|
||||
|
||||
|
|
@ -107,11 +114,34 @@ Named, so this is not a capability shipped as decoration:
|
|||
and [porch](../porch/00-story.md) when this lands — a follow-up bookkeeping
|
||||
pass, named here so it is not forgotten, not part of the runtime work.
|
||||
|
||||
## Risk and test strategy
|
||||
|
||||
**This is the highest-risk work in the project, and hand-rolling it raises that
|
||||
risk, not lowers it.** Hand-rolled RSA, ECDSA, X25519 and ASN.1/X.509 are the
|
||||
classic sources of real-world CVEs (timing side-channels, padding oracles, chain-
|
||||
validation bypasses, parser memory bugs). The decision to hand-roll is recorded
|
||||
and owned; the mitigations are non-negotiable:
|
||||
|
||||
- **Constant-time** for every secret-dependent operation (X25519, RSA/ECDSA,
|
||||
AEAD) — verified, not assumed.
|
||||
- **Reference-tested**: every phase gated against a reference implementation —
|
||||
`openssl s_client`/`s_server`, real published cert chains, and the RFC 8448
|
||||
TLS 1.3 test vectors — plus an ASan/UBSan leg on the parser and bignum code.
|
||||
- **Negative tests as first-class**: an expired cert, a wrong hostname, a broken
|
||||
chain, a tampered CertificateVerify and a downgrade attempt must each be
|
||||
refused, with a test that fails if they are accepted.
|
||||
- **No partial-trust states**: a validation that cannot complete refuses the
|
||||
connection; there is no "warn and continue".
|
||||
|
||||
## Info
|
||||
|
||||
This is the heaviest iteration in the runtime-v2 track and the only one that
|
||||
forces a doctrine reversal and, most likely, an external-dependency exception —
|
||||
both flagged above rather than buried. It is pure I/O-plane work (a handshake
|
||||
layer over the existing socket verbs); no actors, so it is not exposed to the
|
||||
lang-41 hang. It gates jarvis entirely: until it lands, jarvis cannot reach a
|
||||
model at all.
|
||||
This is the heaviest iteration in the runtime-v2 track by a wide margin — a
|
||||
subsystem, not a builtin-sized seam — and the only one that reverses a project
|
||||
doctrine. It is pure I/O-plane and compute work (a handshake layer over the
|
||||
existing socket verbs plus the crypto ladder); no actors, so it is not exposed to
|
||||
the lang-41 hang. It gates jarvis entirely: until at least phases A–F land,
|
||||
jarvis cannot reach a model at all. Realistically it is a multi-phase effort
|
||||
measured in weeks, and phases C (X25519), D (signatures/RSA) and E (X.509) may
|
||||
each become their own iteration when picked up. Implementation order is the
|
||||
ladder, bottom-up: A (via rv2 8) → B → C → D → E → F, with G (inbound server)
|
||||
last.
|
||||
|
|
|
|||
Loading…
Reference in a new issue