Commit graph

442 commits

Author SHA1 Message Date
bd99da599d feat(tls): sans-io server handshake FSM (rv2 9 phase G2)
- wo_tls_server: the mirror of the client driver. parse ClientHello (pick
  suite, extract x25519 share, echo session id; reject no-x25519/no-1.3),
  build ServerHello, derive the role-symmetric keys, emit the encrypted
  flight (EncryptedExtensions + Certificate + a signed CertificateVerify +
  Finished), verify the client Finished, switch to application keys
- server_sign_cv signs the CertificateVerify with the phase-G1 primitives
  (RSA-PSS or ECDSA-P256 + a minimal DER SEQ{r,s} encoder); parse_client_hello
  + build helpers reuse the file's wire reader/writer
- wo_tls_server_start builds the Certificate message from a cert chain +
  private key (RSA n/d or EC scalar) + ephemeral; encrypt/decrypt over the
  application keys
- KAT: loopback — our client driver against our server driver, EC then RSA
  server identity, reaching ESTABLISHED with an app round-trip both ways.
  test_tls 123, ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 34d2b8f87cebe536cd2b1b33e6948251ec11684f)
2026-09-15 01:15:52 +02:00
cd779afa7f docs(rv2-tls): phase G1 signing landed (RSA-PSS + ECDSA-P256)
§G sub-phase G1: constant-time RSA-PSS + ECDSA-P256 signing landed and
KAT'd (RSA vs python from-spec; ECDSA vs RFC 6979 A.2.5). Remaining G1c
(private-key PEM/DER parse) folded into G3 (which reads key files); the
server FSM takes raw key material.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit f02518cdabab02043a04c6bdd28a81b86bb9fbd4)
2026-09-15 01:15:52 +02:00
df5054b07d feat(crypto): ECDSA-P256 signing, RFC 6979 nonce (rv2 9 phase G1b)
- wo_ecdsa_p256_sha256_sign: deterministic nonce (RFC 6979 HMAC-DRBG over
  the key + message — no RNG, no nonce-reuse/bias risk), then r = (k*G).x
  mod n and s = k^-1 (z + r*d) mod n
- constant-time in the secret: jmul_ct (double-and-add-always + point
  cmov) for k*G, and bn_modexp_ct for k^-1 mod n and the affine inversion.
  Known residual (documented): the ladder leaks k's leading-zero count (a
  bit-length hint, not the key) — a complete-formula/Montgomery-ladder
  upgrade is the named follow-up
- KAT: byte-for-byte vs the RFC 6979 A.2.5 P-256/SHA-256 vectors ("sample"
  + "test"), our sign verifies with our verify, determinism checked.
  test_crypto 115, ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 1bc6d04f9e18180dc7d0a6e5e7021dbd588e499a)
2026-09-15 01:15:52 +02:00
631506d36f feat(crypto): constant-time RSA-PSS signing (rv2 9 phase G1a)
- bn_modexp_ct: constant-time modexp for the SECRET exponent — squares and
  multiplies every bit, selects the product with a mask (bn_cmov), so the
  op sequence is independent of d (the existing bn_modexp branches on the
  bit, fine only for the public e)
- wo_rsa_pss_sha256_sign: EMSA-PSS-ENCODE (RFC 8017 §9.1.1) + modexp with d;
  caller supplies the salt (fresh in production; fixed makes the KAT
  deterministic). Private key (n,d)
- KAT: deterministic sign vs a python from-spec oracle byte-for-byte
  (fixed salt), our sign round-trips through our verify, tamper rejected.
  test_crypto 108, ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit cf8fdfcc47b2b076b63b9c63bf56411615b0d6f9)
2026-09-15 01:15:52 +02:00
82446652e4 docs(rv2-tls): brainstorm phase G (inbound TLS server) to ready
- §G written to READY (forks auto-approved, review_pending): the inbound
  server rung. Grounds what's reused (record layer, role-symmetric key
  schedule, X.509, slot table + data plane) vs new (server FSM, signing,
  key parsing, accept surface)
- six locked decisions: (1) constant-time private-key ops — the built
  modexp/scalar-mult are verify-only, not constant-time, so G adds a
  constant-time fixed-window modexp + Montgomery-ladder scalar mult;
  (2) both RSA-PSS + ECDSA-P256 server keys; (3) deterministic RFC 6979
  ECDSA nonce; (4) net.accept_tls(listener,cert,key) w/ per-path shard
  identity cache; (5) full 1-RTT server-auth only (no mTLS/resumption/HRR);
  (6) sans-io wo_tls_server FSM
- sub-phases G1 signing+key-parse, G2 server FSM (loopback KAT), G3
  net.accept_tls + live gate (openssl s_client); acceptance + out-of-scope
- ladder G row -> READY; may become its own runtime-v2 iteration

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9fcb4a96a137a897f0ce2be868c18e63a979c997)
2026-09-15 01:15:52 +02:00
fe352b63c2 docs(runtime): CODE-LOGIC — the hand-rolled TLS 1.3 client (rv2 9)
- new "Hand-rolled TLS 1.3 client" section: the crypto ladder in crypto.c,
  the tls.c layers (record / key schedule / messages / sans-io driver /
  chain validation / PEM), and the net.*_tls builtins in sysio.c —
  per-shard no-lock slot table, deadline-bounded blocking handshake then a
  parked data plane, getrandom ephemeral (the runtime's first RNG),
  WO_CA_BUNDLE trust store, loud WO_T_IO failures, the just tls gate
- files table: crypto.c entry updated, tls.c/.h added

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 5670304d8a7a54e291b81e99e27aa16e29aa1d3e)
2026-09-15 01:15:52 +02:00
cf31bb4ebd docs(rv2-tls,jarvis,status): outbound TLS client complete — jarvis unblocked
- rv2 9 §F3c-net marked LANDED + live-gated; phase-F row COMPLETE (client);
  frontmatter review_pending updated (client complete, remaining = G server
  + deferred park-handshake/TlsConn/pooling + doctrine-doc corrections)
- jarvis 00-story + 01: the outbound-TLS blocker is cleared
  (net.connect_tls landed) — jarvis 1 (chat loop) is now buildable
- status board: rv2 9 row + NEXT PLAN rewritten to the completed client;
  next step is jarvis 1 or rv2 9 G

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 732c2216b501dd226d306f9abcbd1ddd846809dc)
2026-09-15 01:15:52 +02:00
72ed35773d test(tls): live acceptance gate for net.connect_tls (rv2 9 F3c-net phase 4)
- docs/examples/tls-client/main.wo: an outbound HTTPS client in .wo —
  net.connect_tls, write_tls a request, read_tls to EOF, print; connect
  failure caught with try/catch and reported (never a silent downgrade)
- scripts/tls-accept.sh + `just tls`: dials a local TLS 1.3 stub (python
  ssl, TLS1.3-only) with a generated test CA — proves the hand-rolled
  handshake + chain/host validation + an app round-trip end to end from
  .wo through the compiler, and refuses the untrusted-chain and
  hostname-mismatch negatives. No live network; log /tmp/tls.log
- gate: 5 checks, 0 failures

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 3d8bb140ec478167a4e660adf2caa964ff410ff1)
2026-09-15 01:15:52 +02:00
6d6c810695 feat(tls): net.connect_tls/read_tls/write_tls builtins (rv2 9 F3c-net)
The outbound TLS 1.3 client wired into the VM (ids 115-117, WO_B_MAX->117):

- net.connect_tls(host,port)->Int: DNS + non-blocking connect+poll bounded
  by WO_TLS_HANDSHAKE_MS (decision 5), then a blocking, SO_*TIMEO-bounded
  hand-rolled handshake over the sans-io driver, then wo_tls_verify_chain
  (chain + host + validity + basicConstraints/EKU) against the shard's
  lazily-loaded read-only CA bundle (decision 4). Any failure traps WO_T_IO
  loudly (decision 3). Returns the fd.
- net.read_tls / net.write_tls: application data over the parked data plane
  (decision 1) — O_NONBLOCK + park on POLLIN/POLLOUT like net.read/write,
  with record reassembly + leftover-plaintext + in-flight-record buffers in
  the per-fd slot so a park/retry never re-seals or loses progress.
- per-shard wo_tls_conn slot table keyed by fd, no locks (one thread per
  shard, the wo_child pattern; decision 2); net.close frees the slot;
  wo_vm_destroy reaps all slots + the CA bundle. getrandom ephemeral.
- driver keeps the whole Certificate message + wo_tls_client_chain() so the
  trust walk sees the full chain, not just the leaf.
- wiring: wob.h, loader.c arities, builtin.c dispatch (second net range),
  types.ml (net.connect_tls/read_tls/write_tls), sysio.c impl.

Builds; full runtime suite 0 fail; woc builds. Live behaviour is the
Phase-4 gate (next commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9a922b3245eaa9134efb60bfd46521952ed12a39)
2026-09-15 01:15:31 +02:00
040ec9c189 feat(tls): PEM trust-anchor decoder (rv2 9 F3c-net decision 4)
- wo_tls_pem_to_ders: scan a PEM bundle for CERTIFICATE blocks, base64-decode
  each into a caller arena, record DER spans as trust anchors for
  wo_tls_verify_chain. Pure (caller reads the file + owns the arena) so it is
  offline-testable; the file read + per-shard cache land with the builtin
- b64_decode helper (standard alphabet, skips whitespace/newlines)
- KAT: decode the real /etc/ssl/certs/ca-certificates.crt (>100 anchors,
  each parses, first is a CA), garbage PEM -> 0 with no over-read,
  skip-if-absent for CI. test_tls 107 pass, ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 6445d55aa83dbed831d84fd3cca3a74fe4609a00)
2026-09-15 01:15:31 +02:00
3a1ba15851 feat(tls): X.509 basicConstraints + EKU chain hardening (rv2 9 F3c-net decision 6)
- crypto.c: x509_find_ext (generic extension walker) + wo_x509_basic_constraints
  (cA / pathLenConstraint, absent => not a CA) + wo_x509_eku_serverauth_ok
  (EKU absent, serverAuth, or anyEKU => usable; else not)
- wo_tls_verify_chain enforces decision 6: the leaf must be server-usable
  (EKU), every server-sent issuer and the signing anchor must be a CA
  (basicConstraints CA:TRUE) with a pathLenConstraint covering the
  intermediates below it — stops a leaf masquerading as a CA
- gen_x509.py extended (folds in the wildcard leaf, adds EKU clientAuth-only,
  EKU serverAuth, a non-CA intermediate + a leaf issued under it); vectors
  regenerated
- KATs: extractors (test_crypto 104) + chain enforcement (test_tls 103) —
  EKU serverAuth accepted, clientAuth-only rejected, leaf-under-non-CA
  rejected though every signature verifies; existing chains still pass.
  ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 3811418014c2c8d9bc3a9464256f52104261b1b9)
2026-09-15 01:15:31 +02:00
66de570888 chore(workflow): add prebuild-feature — the pre-build research fan-out
A reusable named Workflow (.claude/workflows/) that runs the
brainstorm-to-ready groundwork this repo does before any feature code:

- Understand: read the target story (or find the NEXT-PLAN target) +
  scout relevant .dev/reference projects for the concern
- Analyze: one agent per reference project — how it handles the concern,
  gaps vs our planned approach, recommendations (the fiber/Go step,
  generalized)
- Audit: story-format/frontmatter/plans-no-raw-code + dependency-graph /
  status-board consistency
- Consolidate: settle open forks (KISS defaults), fold reference gaps as
  locked requirements, acceptance-criteria gaps, go/no-go on readiness

Parameterized via args {story?, concern?, references?}; grounds every
agent in on-disk files. Does the parallelizable research half; the
fork-settling stays an interactive brainstorm. Invoke:
Workflow({name:'prebuild-feature', args:{...}}) or /workflows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 2bfbb0ca5ca17cb2d1ead39f93aa42aeb50b1639)
2026-09-15 01:15:31 +02:00
53485a748c 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)
2026-09-15 01:15:31 +02:00
f1e355c4d4 docs(rv2-tls): brainstorm §F3c-net to ready — four integration forks locked
- §F3c-net rewritten to READY (decisions locked 2026-09-09), grounded in
  the runtime not assumed:
  1. blocking connect+handshake then park the data plane (mirrors
     net.connect's own "tolerable while rare" stance); park-based
     handshake a named follow-up
  2. per-shard fd-keyed wo_tls_conn slot table, no locks (the wo_child /
     one-thread-per-shard pattern); slot holds driver state + partial-record
     + leftover-plaintext buffers
  3. failures trap WO_T_IO loudly incl. chain + hostname (no silent nil)
  4. per-shard lazy read-only CA bundle (/etc/ssl/certs, WO_CA_BUNDLE)
- builtin surface: net.connect_tls/read_tls/write_tls (ids 115-117,
  WO_B_MAX->117), acceptance criteria (incl. concurrent-shard TSan),
  out-of-scope (park handshake, TlsConn object, HTTP layer, inbound G)
- frontmatter review_pending + phase-F row + status NEXT PLAN updated:
  F3c-net spec ready, next action is BUILD (live-gated)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 8b6e72171c5db9dfe5b7a5122be123bf7cc3cdd9)
2026-09-15 01:15:31 +02:00
2391553658 docs(rv2-tls,status): F3c-net plan + net.connect_tls object-model default; session NEXT PLAN
- rv2 9 §F3c-net: the remaining live-gated slice with auto-approved
  defaults — getrandom ephemeral, system CA-bundle loader, net.connect_tls
  builtin returning the TCP fd (fd-keyed side table, blocking model like
  net.connect) driving the sans-io driver, then wo_tls_verify_chain; plus
  net.read_tls/write_tls and a live gate
- status board NEXT PLAN: the TLS client security engine landed this
  session (E-F3c minus socket glue), F3c-net is the next rung, then jarvis

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit ad87974fc722268e278f957f1f3d607f5dafa50d)
2026-09-15 01:15:31 +02:00
5f0ac2d434 feat(tls): certificate chain validation (rv2 9 phase F3c-net security core)
- wo_tls_verify_chain: leaf-first DER chain — each cert signed by the
  next, the top trusted (equal to, or signed by, a trust anchor), the leaf
  SAN matching host, every cert temporally valid. Any failure rejects;
  no partial trust. Pure over the phase-D/E verifiers, so offline-testable
- KAT with the phase-E RSA + EC chains: leaf trusted via its issuing CA
  anchor; wrong-anchor / wrong-host / expired / broken-link / no-anchor
  all rejected; two-cert chain with a byte-equal root anchor; NULL host
  skips the SAN check. test_tls 100 pass, ASan/UBSan clean
- remaining F3c-net (live-gated): CA-bundle PEM loader, random ephemeral,
  the net.connect_tls builtin driving the sans-io driver over a real fd

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 9d40055108d301be32df0e1d4140c23446baa7c6)
2026-09-15 01:15:31 +02:00
ad088163cc docs(rv2-tls,jarvis): TLS ladder through F3c-core + SAN landed
- rv2 9 phase-F row + review_pending: F3c-core sans-io driver + SAN/host
  landed (KAT'd vs RFC 8448 record trace); remaining F3c-net = system CA
  trust-anchor walk + net.connect_tls VM plumbing (live-gated), then G
- jarvis 00-story + 01 blocker tables: crypto/handshake engine landed;
  jarvis now waits only on net.connect_tls (the socket glue)
- status board rv2 9 row updated to the full ladder state

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4fdf07196d01c32d0ab12d32d36fa4285ff34654)
2026-09-15 01:15:31 +02:00
d7a6888931 feat(tls): SAN/hostname verification + driver enforcement (rv2 9 phase E/F3c)
- wo_x509_check_host: match a hostname against the cert subjectAltName
  dNSNames (RFC 6125) — case-insensitive, single left-most wildcard that
  covers exactly one label; no SAN => refused; no legacy CN fallback.
  Completes the phase-E deferred hostname check (walks the [3] extensions)
- wo_tls_client_set_host + driver enforcement: with a host set, a leaf
  whose SAN does not match is refused at the Certificate step (MITM
  defense); unset skips the check (offline testing only, documented unsafe)
- KAT: exact/case-insensitive/mismatch, no-SAN refused, wildcard one-label
  (not zero, not sub-label) via a wildcard-SAN cert; driver refuses the
  RFC 8448 leaf (no SAN) once a host is set. test_crypto 95, test_tls 91,
  ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 319ce8bfcf608030f958b36ab2c8f62fd76e1740)
2026-09-15 01:15:31 +02:00
d7e7eff6b5 feat(tls): sans-io TLS 1.3 client handshake driver (rv2 9 phase F3c-core)
- wo_tls_client: a pure state machine (no sockets). Caller frames
  records; driver runs ClientHello->ServerHello->flight->Finished and
  hands back bytes to send. Keeps all I/O out of the security-critical FSM
- start_with (inject CH + ephemeral priv), push_record, take_output,
  encrypt/decrypt (application traffic keys). Handshake-message reassembly
  across records; per-message transcript timing (CertVerify signs CH..Cert,
  Finished MACs CH..CertVerify); constant-time Finished compare; every
  failure lands in FAILED (no warn-and-continue)
- verifies server CertificateVerify (phase E+D) + server Finished, emits
  the client Finished, switches to application keys
- KAT: whole handshake driven offline against the RFC 8448 record trace —
  client Finished record byte-for-byte, first client app record
  byte-for-byte, NewSessionTicket + server app data decrypt to plaintext,
  tampered flight -> FAILED. test_tls 90 pass, ASan/UBSan clean
- SECURITY TODO before live use (documented in tls.h + story): chain walk
  to a trust anchor + SAN/hostname match; random ephemeral for production
  start; the net.connect_tls socket glue

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 74c332d7efdb8bbfdbe90bd2fcc3defa2fe8da00)
2026-09-15 01:15:31 +02:00
c84d33c9ba docs(rv2-tls): rv2 9 phase F1-F3b landed (record, key schedule, messages, offline verify)
- phase-F row: F1 record layer, F2 key schedule, F3a message layer,
  F3b offline handshake verification all landed + KAT'd (RFC 8448 /
  real certs); F3c socket FSM + net.connect_tls plumbing remaining
- review_pending updated to the current ladder state

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit d49bc3866b6b620d00a8a57137ba211da25cd05b)
2026-09-15 01:15:31 +02:00
74b153aa0a feat(tls): offline handshake verification (rv2 9 phase F3b)
- wo_tls_verify_cert_verify: verifies a server CertificateVerify
  (RFC 8446 §4.4.3) — builds the 64-space || context || 0x00 ||
  transcript-hash content, parses the leaf SPKI (phase E) and dispatches
  to phase-D RSA-PSS / RSA-PKCS1 / ECDSA-P256; the scheme must match the
  leaf key type. ECDSA sig r/s pulled from its DER SEQ
- reuses wo_tls_finished_verify (phase F2) for server + client Finished
- KAT: the whole handshake crypto driven offline from the RFC 8448 §3
  recorded messages — CertificateVerify (RSA-PSS) VALID, wrong-transcript
  / tampered-sig / mismatched-scheme rejected, server Finished byte-exact,
  and the client Finished we would send byte-exact. test_tls 78 pass,
  ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit afd9f23508c648322712df91329aa117c975ccab)
2026-09-15 01:15:31 +02:00
c375110aac feat(tls): TLS 1.3 handshake message layer (rv2 9 phase F3a)
- bounded wire reader/writer (malformation -> reject, overflow -> fail;
  no over-read on attacker-controlled bytes)
- wo_tls_parse_server_hello: extracts negotiated suite + server x25519
  key share; rejects HelloRetryRequest, unsupported suite/group,
  non-1.3 selected_version, and any truncation
- wo_tls_build_client_hello: ClientHello offering TLS 1.3 / x25519 /
  RSA-PSS+RSA-PKCS1+ECDSA-P256, SNI, 32-byte legacy session id
- KAT: ServerHello parser vs RFC 8448 recorded message (suite 0x1301 +
  server pubkey byte-exact), malformed rejected; ClientHello builder
  structural + SNI/keyshare present + too-small refused, and validated
  byte-for-byte spec-valid by an independent python parser. test_tls 71
  pass, ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 541c71bca1655f370b145621d272d2e8bdb6c7ce)
2026-09-15 01:15:31 +02:00
25dda4cb2f feat(tls): TLS 1.3 key schedule (rv2 9 phase F2)
- wo_tls_derive_handshake: Early/Handshake/Master secrets + client/server
  handshake-traffic secrets from the ECDHE shared secret and the
  ClientHello..ServerHello transcript hash (RFC 8446 §7.1)
- wo_tls_derive_application: client/server application-traffic secrets
  from master_secret + the ClientHello..server-Finished transcript hash
- wo_tls_traffic_keys: record key + IV via HKDF-Expand-Label "key"/"iv"
- wo_tls_finished_verify: finished_key = Expand-Label(base,"finished"),
  verify_data = HMAC(finished_key, transcript_hash)
- all over phase-B HKDF (Extract/Expand-Label) + Derive-Secret helper
- KAT vs RFC 8448 §3 "Simple 1-RTT Handshake" byte-for-byte: c/s hs
  traffic, master, c/s ap traffic, server hs key+iv. Also validates the
  phase-B "tls13 " Expand-Label. test_tls 58 pass, ASan/UBSan clean

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 417fcc16f80c1dd31e84a0336944573902fde06e)
2026-09-15 01:15:31 +02:00
7e71c1a171 feat(tls): TLS 1.3 record layer (rv2 9 phase F1)
- new tls.c/tls.h on the crypto ladder: wo_tls_record_seal/open
  (RFC 8446 §5.2) — TLSInnerPlaintext (content||type, no padding),
  5-byte header as AEAD additional-data, per-record nonce = iv XOR
  seq big-endian (§5.3)
- suite dispatch: TLS_AES_128_GCM_SHA256 (mandatory) +
  TLS_CHACHA20_POLY1305_SHA256 (AES-NI-less fallback), over phase-A AEAD
- open() strips trailing zero padding to recover the inner content type;
  rejects a length-field lie before the AEAD, and auth failure after
- KAT vs python AEAD oracle (test/gen_tls_record.py): sealed record
  byte-for-byte both suites, open() recovers it, 5-seq round-trip,
  tamper + wrong-seq + bad-suite rejected. test_tls 51 pass, ASan/UBSan

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 5021a99f8359f78a642bb0cc2b28ab66f6b624e2)
2026-09-15 01:15:31 +02:00
bb64278aa9 docs(rv2-tls): rv2 9 phase E (X.509 core) landed
- phase-E ladder row: core landed (DER reader + cert parse + verify_one
  + parse_spki + check_validity), KAT'd on real RSA + EC chains
- review_pending frontmatter: forks auto-approved 2026-09-08, SAN/
  hostname + CA-bundle walk deferred to phase F

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EOF2
git log --oneline -2

(cherry picked from commit 796ed88d76f1cf25ced1eb5e7bdb4e4ab3cf4e9b)
2026-09-15 01:15:31 +02:00
5f5d774d76 feat(crypto): X.509 chain-link verification (rv2 9 phase E core)
- defensive ASN.1/DER reader: every length/bound checked; malformation
  is rejection, never over-read (truncated input KAT-gated)
- x509_parse: tbsCertificate span, sig-alg OID, signature,
  SubjectPublicKeyInfo (RSA n/e or EC P-256 x/y), validity dates
- wo_x509_verify_one: one chain link's signature, dispatching to
  phase-D RSA-PKCS1/PSS + ECDSA-P256 by the issuer key type
- wo_x509_parse_spki + wo_x509_check_validity (caller supplies time)
- KAT against real python-generated chains (test/gen_x509.py):
  RSA CA+leaf (SHA256withRSA), EC P-256 CA+leaf (ecdsa-with-SHA256);
  leaf-vs-CA, self-signed CA, wrong-issuer/tampered/truncated reject,
  validity window, SPKI extraction. test_crypto 84 pass, ASan/UBSan clean
- deferred to phase F: SAN/hostname match + multi-cert chain walk to a
  system CA bundle (both need the target host / trust store, known at
  handshake time)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4ec1c75f889df3612e31b9a1a77c4c927fb546c1)
2026-09-15 01:15:31 +02:00
9d1689be55 docs(jarvis): create iteration stories 1 (ready) + 2/3 (refine)
- jarvis 1 (chat loop) brainstormed to ready with forks AUTO-APPROVED for
  autonomous execution and flagged in `review_pending` frontmatter for the
  developer's second review: Anthropic Messages API backend, env-var API key,
  actor-per-conversation SSE relay, durable @table history, session-gated routes
- jarvis 2 (tool use) + 3 (retrieval/RAG) created at refine with forks named
- 00-story iterations table linked to the new files
- blocked until rv2 9 TLS reaches phase F; pure .wo on porch 2/3/6/7 + the seam

(cherry picked from commit 8e160c3fcf9c50a05af073c036c693b192c9e595)
2026-09-15 01:15:31 +02:00
ca572086ee docs(rv2-tls): rv2 9 phase D complete (RSA + ECDSA-P256 verify)
- ECDSA-P256 verify landed; phase D done (both signature verifiers)
- rv2 9 story, board ladder, jarvis 00-story deps, and dependency-graph §7
  synced: A-D landed, E-F remain

(cherry picked from commit 3eab98cc268e524c7b4e2ab72a4b093692a67d03)
2026-09-15 01:15:31 +02:00
c085c7390c feat(crypto): ECDSA-P256 verification (rv2 9 phase D part 2)
- wo_ecdsa_p256_sha256_verify: NIST P-256 signature verify for EC-cert chains
  and TLS 1.3 CertificateVerify
- Jacobian point arithmetic (double a=-3, general add with the H==0 special
  cases), double-and-add scalar mult; field/scalar arithmetic reuses the
  bignum Montgomery multiply and modexp (Fermat inverses mod p and mod n)
- validates r,s in [1,n-1] and that Q is on the curve (invalid-curve guard)
- verify-only public data -> not constant-time by design
- renamed the P-256 field mul to fpmul to avoid the clash with X25519's fmul
- VERIFIED against a python ECDSA-P256 vector; tamper + wrong-hash rejected;
  test_crypto 69/0; ASan/UBSan clean; battery green
- phase D COMPLETE (RSA PKCS1+PSS + ECDSA-P256). Next E: ASN.1/X.509

(cherry picked from commit 92c996ba96d785d098c173d4ac6ace9052ef6a2f)
2026-09-15 01:15:31 +02:00
4e70509001 docs(rv2-tls): rv2 9 phase D part 1 (RSA verify) landed
- RSA PKCS#1 v1.5 + PSS verify over SHA-256, bignum Montgomery modexp,
  KAT-gated vs python RSA-2048. ECDSA-P256 (D2) remains. Board synced

(cherry picked from commit ae42943c97949224d883e9ddcf5ae2117fef62b3)
2026-09-15 01:15:31 +02:00
d0bd66c704 feat(crypto): RSA signature verification (rv2 9 phase D part 1, PKCS1 + PSS)
- wo_rsa_pkcs1_sha256_verify + wo_rsa_pss_sha256_verify (SHA-256), for the
  server cert chain and TLS 1.3 CertificateVerify
- bignum: Montgomery multiply (CIOS, 64-bit limbs, __int128), modexp with the
  public exponent (R^2 via 128k modular doublings, no division); MGF1-SHA256
- verification is public data only -> NOT constant-time by design (correct and
  much simpler than a private-key op)
- assumes a full-length modulus for PSS emBits (standard RSA-2048/3072/4096)
- VERIFIED against python cryptography RSA-2048 vectors (PKCS#1 v1.5 + PSS,
  salt 32); tamper + wrong-hash rejected; test_crypto 66/0; ASan/UBSan clean;
  battery green
- internal C, no builtin/compiler change. Remaining in D: ECDSA-P256 (D2)

(cherry picked from commit 9118177fbfd03eff9757defea6931afdd68830c4)
2026-09-15 01:15:31 +02:00
d02befd6bf docs(jarvis): sync jarvis dependencies to landed state + dependency graph
- jarvis 00-story: net.connect marked landed (id 110); outbound-TLS blocker
  now rv2 9 in-progress (A AEAD / B HKDF / C X25519 done; D-G remain);
  architecture + blocker table updated
- dependency-graph: new §7 jarvis dependency graph (phase-level TLS ladder +
  porch framework path); §5a net.connect node marked landed

(cherry picked from commit a615ee80238fb384c0b33fc2b54bcd6bd4078078)
2026-09-15 01:15:31 +02:00
8e652800fe docs(rv2-tls): rv2 9 phase C (X25519) landed
- constant-time X25519 (RFC 7748), curve25519-donna radix-2^51; KAT-gated incl.
  the 1000-iteration vector. Ladder A+B+C done; next D. Board synced

(cherry picked from commit b929a20b7482d7137a45b77a2179fe7ef03c52b6)
2026-09-15 01:15:31 +02:00
aee98661d2 feat(crypto): X25519 key exchange (rv2 9 phase C, RFC 7748)
- wo_x25519: constant-time Montgomery ladder + mask-based conditional swap,
  radix-2^51 field arithmetic with __int128 products (curve25519-donna-c64,
  public domain); scalar clamped, u-coord high bit masked per RFC 7748
- internal C (consumer is the TLS ECDHE handshake); no builtin/compiler change
- KAT-gated in test_crypto: RFC 7748 §5.2 both direct vectors AND the
  1000-iteration base-point test; test_crypto 61/0; ASan/UBSan clean; battery green
- fixed one transcription bug found via the KAT: crecip needs 5 final squarings
  (p-2 = 2^255-21 = (2^250-1)*2^5 + 11), not 3
- rv2 9 ladder: A (AEAD) + B (HKDF) + C (X25519) done; next D signatures/RSA

(cherry picked from commit f41b1c5f56caa841d1904382830baff0f75525d9)
2026-09-15 01:15:31 +02:00
aff8ffdf14 docs(rv2-tls): rv2 9 phase B (HKDF key schedule) landed
- HKDF-Extract/Expand + Expand-Label over hmac_sha256, KAT-gated (RFC 5869 +
  8446); status -> in-progress; ladder A+B done. Board synced

(cherry picked from commit e24b8ec6d838fc66848864c2a0974205aaa9b4be)
2026-09-15 01:15:31 +02:00
36ce2332ef feat(crypto): HKDF-SHA256 for the TLS 1.3 key schedule (rv2 9 phase B)
- wo_hkdf_sha256_extract/expand (RFC 5869) + expand_label (RFC 8446 §7.1),
  internal C over the existing hmac_sha256; SHA-256 (mandatory-suite hash;
  SHA-384 a later add for the AES-256 suite)
- no builtin, no compiler change -- no .wo consumer yet (the TLS handshake
  is the consumer); exposed for the C unit test
- KAT-gated in test_crypto: RFC 5869 Test Case 1 (PRK + 42-byte OKM) and
  three HKDF-Expand-Label vectors (key/iv/derived-secret shape); 57/0,
  ASan/UBSan clean; runtime battery green
- rv2 9 ladder: A (AEAD, = rv2 8) and B (HKDF) now done; next C X25519

(cherry picked from commit c8d27b6c89a80cd97a996ff7d8b64ff4895e4b26)
2026-09-15 01:15:31 +02:00
2db3766b66 docs(rv2-aead): rv2 8 phase C (software AES-GCM fallback) landed
- portable constant-time AES-GCM software path; AES-GCM now on any CPU
  (hw-or-sw dispatch), NIST-KAT-gated both paths (48/0). Remaining D/E;
  ARMv8 hw path deferred. Board synced

(cherry picked from commit 138de17988e6bd274abe5e1e2d444ff2689747cd)
2026-09-15 01:15:31 +02:00
94d5f176f7 feat(crypto): portable constant-time AES-GCM software fallback (rv2 8 phase C)
- no-intrinsics AES: S-box = GF(2^8) inverse via a fixed-exponent power ladder
  (constant-time in the input, no tables), constant-time gf8_mul, byte-oriented
  ShiftRows/MixColumns/key-expansion (AES-128 and AES-256)
- constant-time GHASH: bit-by-bit GF(2^128) multiply (mask-driven, no tables)
- aes_gcm_seal/open now dispatch: AES-NI path when present (and not forced
  software), else this portable fallback -> AES-GCM works on ANY CPU, so the
  phase-B no-AES-NI trap is retired
- wo_aes_force_software test hook; both hw and sw paths verified against NIST
  SP 800-38D cases 4 (AES-128) and 16 (AES-256) byte-for-byte; test_crypto 48/0;
  ASan/UBSan clean; full runtime battery green
- ARMv8 crypto-extension hardware path deferred (untestable on x86-64 host)

(cherry picked from commit dccf650899798401a9adac8489f34c85ed9304af)
2026-09-15 01:15:31 +02:00
6a38ad7cb0 docs(rv2-aead): rv2 8 phase B (AES-GCM) landed
- phases A + B done; B = AES-128/256-GCM via AES-NI/PCLMULQDQ, NIST-KAT-gated,
  ASan clean, portable binary (CPUID-gated). Phase C now owns the software
  fallback AND the ARMv8 hardware path (deferred, untestable on x86-64 host)

(cherry picked from commit 249b1dbd72122ed6c05f4f0aadb7e1a5e87f844c)
2026-09-15 01:15:31 +02:00
1ef4e463ec feat(crypto): AES-GCM via AES-NI + PCLMULQDQ (rv2 8 phase B, ids 113/114)
- aes_gcm_seal/open, AES-128 and AES-256 (variant by key length 16/32),
  nonce 12 bytes, out = ciphertext||tag; open returns nil on auth failure
- hardware path only (phase B): AES-NI key schedule (128/256) + block, GHASH
  via PCLMULQDQ with the fast GF(2^128) reduction, GCM mode (J0, CTR from
  counter 2, GHASH over aad|pad|ct|pad|len, tag = GHASH ^ AES(J0))
- constant-time by hardware; target-attributed functions + __builtin_cpu_supports
  gate so the binary stays portable -- no AES-NI traps with a clear message
  (bitsliced software + ARMv8 paths are phase C)
- wiring: wob.h ids + WO_B_MAX 114; builtin.c crypto range; loader arity 4;
  emit.ml (ids/arity/return/name); types.ml (register + return type)
- VERIFIED: matches NIST SP 800-38D cases 4 (AES-128) and 16 (AES-256) and the
  python cryptography reference byte-for-byte; KAT-gated in test_crypto (36/0);
  ASan/UBSan clean; runtime battery + compiler 557/0 green

(cherry picked from commit f12a745a3c1313847f9d7f65e53bcd8093758af9)
2026-09-15 01:15:31 +02:00
da840a5be6 docs(rv2-aead): rv2 8 phase A (ChaCha20-Poly1305) landed
- status -> in-progress; phase A marked landed (matches RFC 8439 §2.8.2,
  KAT-gated in test_crypto, ASan clean). Remaining B/C/D/E. Board synced

(cherry picked from commit db5bdf3c3cac31c0d2be60027e0e2bf9fe8309c1)
2026-09-15 01:15:31 +02:00
ac52c3fdb5 feat(crypto): ChaCha20-Poly1305 AEAD (rv2 8 phase A, ids 111/112)
- hand-rolled ChaCha20 + poly1305-donna-32 + RFC 8439 §2.8 AEAD in crypto.c;
  constant-time (add/xor/rotate + limb math, no tables, no data-dep branches),
  constant-time tag compare
- two bare-name crypto-family builtins beside sha256/hmac:
  chacha20poly1305_seal(key,nonce,aad,pt) -> Bytes (ct||tag)
  chacha20poly1305_open(key,nonce,aad,ct||tag) -> ?Bytes (nil on auth fail)
  key 32B, nonce 12B (caller-supplied, per TLS's per-record nonce need)
- wiring: wob.h enum + WO_B_MAX 112; builtin.c crypto dispatch range; loader.c
  arity 4; emit.ml (ids, arity_of 4-case, return type, is_builtin_name,
  name->id); types.ml (registration + return type)
- VERIFIED: matches RFC 8439 §2.8.2 byte-for-byte (vs python cryptography +
  the RFC vector); test_crypto 24/0 (Poly1305 §2.5.2 + AEAD seal/open/tamper);
  ASan/UBSan clean; runtime battery + compiler 557/0 green
- first rung of the TLS ladder (rv2 9 phase A)

(cherry picked from commit 961854a8f4e9e632b6fa17f7f2e519e2d08f4936)
2026-09-15 01:15:31 +02:00
7f7a601e2f docs(rv2-aead): brainstorm runtime-v2 8 (AEAD ciphers) to ready
- a second consumer (rv2 9 TLS phase A) reshaped the forks since the draft
- locked: BOTH AES-GCM (128/256, TLS-mandatory per RFC 8446) AND
  ChaCha20-Poly1305 (RFC 8439, easy constant-time, cookie default)
- AES constant-time via AES-NI/ARMv8 hardware + bitsliced software fallback
  (compiler intrinsics, zero external dep)
- caller-supplied nonce (TLS builds its own per-record nonce); random-nonce
  is a cookie WRAPPER (phase D) not the primitive. shape:
  seal(key,nonce,aad,pt)->Bytes / open->?Bytes; AES variant by key length
- raw key + length check; hand-rolled (matches rv2 9); ids from 111
- phases A ChaCha -> B hardware AES-GCM -> C software AES -> D cookie wrapper
  -> E gate (RFC 8439 + NIST GCM vectors, ASan, reference cross-check)
- risk/test: constant-time mandatory, KAT-gated, reused-nonce documented
- retires the stale "TLS proxy-terminated" OOS line (rv2 9 overturned it)

(cherry picked from commit c8a5a31c39a5d14952056cd0af1b8c1e42d4ed2d)
2026-09-15 01:15:31 +02:00
51addb504c 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)
2026-09-15 01:15:31 +02:00
cc1c82b2ef docs: jarvis track, runtime-v2 7/8/9, lang-41 fix design, fiber scope-gap
- jarvis (00-story): 6th track, 2nd software built with writeonce — an AI
  assistant; direct-HTTPS design; blockers named (net.connect + TLS)
- runtime-v2 7 observability + 8 symmetric cipher: moved from the language
  track (were 30/43); 9 in-process TLS: created from the gap jarvis surfaces,
  RETIRES the "TLS is the proxy's job" doctrine (both directions)
- language 41 (arena hang): fix design to ready — marshal cross-shard
  messages (root), align the shard_id % nshards route/compare + assert bound;
  poison-on-free + minimal fixture as follow-ups
- fiber scope-gap analysis (plan/exploration/fiber/01): porch vs fiber, what
  porch lacks, would developers prefer porch
- board + dependency-graph synced (porch 2-8 ready; rv2 table; §5/§5a graphs)

(cherry picked from commit 203470ceb2a151fe3584931cd4237af3f96a9f29)
2026-09-15 01:15:31 +02:00
e91a3704fe feat(net): net.connect outbound TCP client (id 110)
- new builtin net.connect(host, port) -> Int: the outbound-socket gap
  language 38 named and jarvis surfaced; the client half of the net verbs
- getaddrinfo for DNS (v4/v6, numeric or hostname), blocking connect with
  the same EINTR/stop handling as net.connect_unix, then O_NONBLOCK for the
  park plane; returns the same fd-scalar accept yields
- wob.h enum + WO_B_MAX 110; types.ml registration; loader.c arity;
  builtin.c sysio dispatch range extended to WO_B_NET_CONNECT; sysio.c impl
- verified: numeric IP + hostname (DNS) connect to a local listener, closed
  port traps cleanly; ASan-clean; runtime battery 0 fail
- deferred (next slice): net.connect_dl deadline/park variant (no shard
  stall during handshake), on the accept_dl pattern

(cherry picked from commit 13c6f124428243b4956fbb4eceb1e7d0206d45f2)
2026-09-15 01:15:31 +02:00
b932e0cb87 docs(porch-static): brainstorm story 8 (static + lifecycle) to ready
- whole porch track (2-8) now brainstormed and locked (all ready)
- four decisions: three hooks (on-listen/on-shutdown/on-route-registered);
  healthcheck ships BOTH /livez + /readyz; directory listing off-by-default,
  documented; Last-Modified via a new small time.utc(ms)->TimeParts builtin
- language enhancement: YES, one small builtin -- time.utc, a gmtime sibling
  of time.local (time.local is local-tz, time.iso is UTC-but-ISO); IMS by
  string-equality, no date parser. The track's third + smallest language touch
- byte ranges/large files via fs.read_at + iteration 6 writer; not lang-41-exposed
- track language bill now explicit: random_bytes (2), deflate+crc32 (7),
  time.utc (8) -- each a builtin with a named consumer, none decoration
- validated against .dev/reference/fiber. Board: whole track marked ready

(cherry picked from commit 9801fceade799e25718606177f09e4306a579e98)
2026-09-15 01:15:31 +02:00
4d3e4261e1 docs(porch-sse): brainstorm story 7 (SSE + compression) to ready
- five decisions: refuse incoherent heartbeat/idle_ms pair at construction;
  codec = two C builtins deflate+crc32 (perf over pure-.wo; hand-rolled, no
  zlib dep; gzip framing in .wo); ETag over uncompressed bytes + Vary;
  Last-Event-ID explicitly unsupported (not silently ignored); Vary via
  comma-join
- language enhancement: YES, two builtins -- the track's SECOND language
  dependency after iteration 2's random_bytes. CRC32 finally gets its
  consumer; inflate deliberately not built (request-body decompression OOS)
- corrected stale dependency: Vary uses iteration 5's comma-join, so story 7
  depends on 6 + 5, NOT 2; codec is pure compute, not lang-41-exposed
- confirmed CRC32 absent + iteration 36 bit operators landed (pure-.wo was
  viable, traded for hot-path speed)
- validated against .dev/reference/fiber. Board synced

(cherry picked from commit 07f53574dd90f502235b79d4920eab3d684c8b77)
2026-09-15 01:15:31 +02:00
641703903c docs(porch-streaming): brainstorm story 6 (streaming core) to ready
- re-scoped to OUTBOUND streaming only
- three decisions: separate StreamHandler/BodyProducer parallel path (Resp
  path untouched -> existing responses byte-identical); streaming routes opt
  out of the after-chain, framework refuses at registration to combine with
  header-mutating middleware (loud, never silent), security_headers() helper
  lets handlers stamp them; chunked REQUEST bodies split into their own future
  iteration (parse.wo refusal stays, smuggling cases enumerated for later)
- no language enhancement (net.write framing, fs.read_at/actor source,
  interfaces for producer); rides the fiber loop not the actor pool, so not
  lang-41-exposed
- fixed title inconsistency: "three iterations wait on" -> "two" (7 and 8)
- validated against .dev/reference/fiber + the app.wo/serve.wo pipeline. Board synced

(cherry picked from commit 15205408e03c3c02a38e00e5d2017a8a11f62f28)
2026-09-15 01:15:31 +02:00
6b820fdd5f docs(porch-routing): brainstorm story 5 (routing + response ergonomics) to ready
- five decisions: head auto-registers with opt-out (+ patch/options/all);
  request ids mirror limiter trust model with a NON-crypto source; per-route
  body_limit is a SECOND check after routing (global BODY_MAX stays the
  pre-routing ceiling, over-limit = 413); Route fields are corpus-free;
  Vary accumulates by comma-join
- key finding: story 5 has NO upstream dependency, not even iteration 2 --
  request ids are not secrets, so a non-crypto source (time.ticks+counter)
  keeps it startable today; the one porch slice buildable right now
- three story assumptions corrected: per-route limit cannot replace the
  global (body read before routing); the container-owned-move corpus fixture
  has its OWN Route (adding fields is free); Vary needs no iteration 2
- validated against .dev/reference/fiber; zero language enhancement. Board synced

(cherry picked from commit 0589a13db1f3b7c220d9d9fdc76142af6a63c390)
2026-09-15 01:15:31 +02:00