diff --git a/docs/155-phase2/DECISIONS.md b/docs/155-phase2/DECISIONS.md new file mode 100644 index 0000000..d83ef53 --- /dev/null +++ b/docs/155-phase2/DECISIONS.md @@ -0,0 +1,48 @@ +# #155 phase 2: user decisions (2026-09-30) + +These are binding for every lane. If the tree contradicts one, report it to the supervisor. Do not work around it. + +1. **Storage:** the UCI DOS target on `/USB1` (persistent media). Never `/Temp`. +2. **P-384 stays closed for now.** It will be reopened later, so every reclaim is knob-gated with a re-arm switch, never deleted. +3. **No on-device CA-key (leaf<-intermediate) check in this phase.** Wikipedia (the demo) is covered by trust-on-first-use (TOFU) plus the name check. The bundle carries warn-only leaf pins. + - Consequence: reclaim R2 (`x509_parse_cert`) is taken. +4. **Bundle:** a separate, SIGNED file. It can be loaded from disk, or into the REU on REU profiles to save RAM. + - Signature: P-256 ECDSA-SHA256, verified on the C64 with the existing `ecdsa_verify_256`. + - The public key and a generation floor (anti-replay) are compiled into the PRG. + - The tree holds a test-only key, labelled as such. The production key is the user's call; do not generate one. +5. **Comb memory:** take R1, R2, R3 and R4 (the S2 reclaim patch, merged as #260). R3 does NOT help the Wikipedia demo build (`HTTPS_BODY_TO_REU=1` re-arms the viewer), so size against that build too. +6. **Torn writes (S1 vs S3):** use S3's ping-pong `TRUST.A` / `TRUST.B` slots, plus S1's read-back verify after close. + - Opening a file for write empties it, so a torn write only ever damages the older slot. No rename is needed. + - L2 confirms this on hardware. +7. **Semantics and record format:** exactly S3 §4 and §5 (`S3-trust-design.md`), except where 3 and 4 above override them. + - §4.4 (CA-assisted TOFU) is out of scope. + - §4.6 is replaced by 4 above. +8. **UPDATE 2026-09-30 (supervisor):** S4's trigger has fired. The Wikipedia demo build on comb does not fit the trust store even with L0: it needs about 1,300 B contiguous and the largest free block is 455 B. A lane after L0 and L2 merge builds a generic cold-code bank for comb: + - cold code (store, typed-host UI, trust prompts) is kept in the REU; + - on each call it is DMA'd into cert_buf, but only while no connection is open; + - REU placement: bank 3, or bank 2 at `$4000` and above. Never `reu_layout`'s `$20100` / `$22100`, which overlap comb's Lim-Lee table. + - onchip keeps the code resident, because it fits there. + - From L2: the `TRUST_STORE_CODE` segment (1,185 B) is self-contained, and its state lives separately in `TRUST_STORE_BSS`. The segment uses self-modifying code and addresses its own data absolutely, so it must be LINKED with `run = cert_buf` and `load = the REU image`, not copied after linking. `rig_trust_store` and the 6502 test poke internal labels and will need updating. + Original item 8 follows. +10. **Inputs for L3 from L4 (PR #262; unconfirmed until #262 clears review):** + - **Bundle format v1:** an 8 B header (`C6TB`, version `$01`, generation as u16 little-endian, N from 0 to 32), then N × 64 B records sorted by host key, then a 64 B r‖s trailer (big-endian) over SHA-256(header‖records). + - **C64 verify:** copy the trailer to `ecdsa_sig_r`, the digest to `ecdsa_hash` and the `.inc` public key to `ecdsa_pubkey_x/_y`, then `jsr ecdsa_verify_256`. C=0 means valid. + - **Mode split:** `$10` marks a bundle leaf pin, warn-only with flags `$01`. `$01`-`$0F` are left to the TOFU store. + - **Host key:** hash the ASCII host with only A-Z lowercased, never PETSCII. Do not strip a trailing dot; check this against L1's validator. + - **Shared SHA-256:** the only multi-block SHA-256 is `tls_transcript_*`, so borrow it only outside a handshake. + - **Buffers:** on comb, cold code occupies `cert_buf`, so the bundle needs another buffer. On ip65, hash the bundle and copy the trailer out before the verify, because `cert_buf` is unioned with the nistcurves BSS. + - **Check order:** structure, then signature, then generation floor. + - **Packaging guard (from adv-l4):** L3 must make `make package` fail if the bundle public key in a package build is the TEST-ONLY key, i.e. assert `TRUST_BUNDLE_KEY_IS_TEST_ONLY=0`. + - **Generation floor:** enforced host-side only so far. Enforcing it on the C64 is L3's job. +11. **L3 answers (user, 2026-10-01).** They answer the open questions in `L3-design.md` §6. + - **Q1, packaging:** turn on `TRUST_STORE=1` in the uci-onchip and uci-comb packages. The bundle stays out of packages until the user creates a production key, so a release never blocks on it. + - **Q2, build-time pin scope:** the pin applies ONLY when the typed host equals `HTTPS_HOST`. Other typed hosts go to the bundle, then trust-on-first-use. + - **Q3, first-use bundle mismatch:** show `BUNDLE MISMATCH`, finish the fetch, and after close ask `RECORD KEY? A=YES`. + - **Q4, bundle verify timing:** verify lazily at the first 'G', once per boot, and cache the digest. Later fetches re-hash the file and compare. + - **Q5, override (the user's choice, against the design recommendation):** + - **When it's offered:** a per-fetch `CONTINUE UNPINNED? Y/N` key, offered on any store failure (no medium, unreadable or corrupt store) AND on a changed key for a trust-on-first-use host. + - **Build pin:** the override never bypasses an enforcing build-time pin. Warn-only builds already exist (`HTTPS_PIN_WARN=1`). This is a supervisor interpretation; the user was told it can be changed. + - **Never records:** the override never records the new key. Recording happens only through the accept-exact-hash flow. + - **Banner:** every overridden fetch shows a persistent UNPINNED banner. + - **Scope:** it applies to that one fetch only, with nothing sticky. +9. **Future work (the S4 spike studies it, nobody builds it):** keep crypto code that is not running banked out in the REU and swap it in on demand. The crypto libraries are vendored as submodules. diff --git a/docs/155-phase2/L3-design.md b/docs/155-phase2/L3-design.md new file mode 100644 index 0000000..388cb6c --- /dev/null +++ b/docs/155-phase2/L3-design.md @@ -0,0 +1,172 @@ +# L3: trust policy design + sizing (#155 phase 2, UCI) + +2026-10-01, master `f20d484` (#260 L0, #262 L4, #261 L1 merged). Design only; implementation follows L5. +Labels: **[M]** measured today on `f20d484`, **[E]** estimate. Binding inputs: DECISIONS.md 3, 4, 7, 8, 10; S3 §4-§6. +**Scope: UCI only.** `TRUST_STORE=1` is `$(error)` on ip65 (no DOS target), so the bundle is UCI-only too. Item 10's ip65 `cert_buf`/nistcurves-BSS union note is moot for L3. + +## 1. State machine and precedence (build pin > bundle > TOFU) + +There is one resident hook, and it is the only L3 code that runs while a socket is open. It generalises `cert_pin_check`, which `x509_extract_pubkey` already tail-calls, and keeps the #152 interlock (`cert_pin_hs_keys` / `cert_pin_send_finished`). The hook: +- hashes the 91 B SPKI window, exactly as today; +- copies the hash to `tp_got[32]`; +- compares it against `tp_expect` (a pointer, or none) and `tb_spki` (advisory); +- sets `tp_status`; +- tail-calls `x509_verify_hostname`. + +Everything else runs **before `net_tcp_connect` or after `net_tcp_close`**. On comb that means the cold bank (§4). Nothing reads the keyboard between connect and close. + +**Per 'G', in `do_https_get`:** + +1. **Before connect** (`trust_pre`): + 1. `https_target_prompt` (L1) produces `tls_hostname`, lowercased, trailing dot refused. That matches L4's host key with nothing more to do. + 2. Clear the one-shot override unless `tp_ovr_key == SHA-256(host)[0:16]`. It is consumed by this attempt either way. + 3. **Build pin.** If `HTTPS_PIN_SPKI` is set and `tls_hostname == http_host_target`, then `tp_expect = cert_pin_expected` and the mode is ENFORCE, or WARN under `HTTPS_PIN_WARN`. **Skip the store and the bundle entirely.** Never record. (See Q2.) + 4. Otherwise, `trust_store_load(tls_hostname)`: + - **FAIL** (any reason): print `TRUST FAIL nn ` and `NOT DIALLED`, then return. Fail closed; no socket is opened. The tags are NOMEDIUM(02), DOS(03), READ(04), VERSION(06), TIE(08), CHANGED(11), BUSY(09: a bug). + - **EMPTY**: print `TRUST STORE EMPTY`, on every GET, so that deletion stays visible. Continue as first use. + - **VALID, and `trust_store_lookup` hits:** `tp_expect = ts_rec+16`, mode ENFORCE. `ts_rec` is resident BSS and survives the connection. + - **VALID, and the lookup misses:** first use. `tp_expect` = none, mode FIRSTUSE. + 5. **Bundle** (§2), run after the store load because it reuses the ring and `ts_key`. On success with a hit, `tb_spki = rec+16` and `tb_found = 1`. Any bundle failure prints its line and sets `tb_found = 0`. A bad bundle never blocks a connection, because its pins are warn-only (item 3). +2. **In the handshake** (the hook, at Certificate, before CertificateVerify): + + | mode | got == expect | got != expect | + |---|---|---| + | ENFORCE (store) | MATCH, continue | if override armed and got == `tp_override` (all 32 B): ACCEPT, continue; else MISMATCH: print `KEY CHANGED EXP xxxxxxxx GOT yyyyyyyy`, C=1 abort (no client Finished) | + | ENFORCE (build pin) | MATCH | `PIN FAIL …`, abort, exactly as today | + | FIRSTUSE | n/a | FIRSTUSE, continue | + + - **Bundle advisory, in any non-build-pin mode:** if `tb_found` and got != `tb_spki`, print `BUNDLE WARN EXP … GOT …` and set `tp_bwarn`. It never aborts. + - **The interlock is unchanged:** client Finished goes out only if the hook ran this connection and did not reject. +3. **After the handshake.** When `tls_connect` returns C=0, the server Finished is verified and the name check has passed, because the name check is the hook's own tail. Set `tp_pending = 1` iff the status is FIRSTUSE or ACCEPT. **Nothing is staged yet:** staging is cold code on comb. +4. **After close.** All exits of `do_https_get` after the dial are funnelled through one `@finish`: `net_tcp_close`, then `trust_post`. + - **Pending**, from FIRSTUSE or ACCEPT: build the record in `ts_rec`: + - SPKI = `tp_got`; + - mode = TOFU `$01` or ACCEPTED `$02`; + - flags bit 1 = bundle corroborated; + - uses = 0; + - display = the first 12 bytes of the host. + + Then call `trust_store_stage(ts_rec)` and `trust_store_save`. The host key is forced to `ts_key` from *this attempt's* load. Print `KEY RECORDED xxxxxxxx`, `TRUST STORE FULL`, or `TRUST SAVE FAIL nn`. FULL means no eviction. + - **Exception, FIRSTUSE with `tp_bwarn`:** don't auto-record. Ask instead (Q3). + - **MATCH:** no write. Don't bump the use count, because one save per connection is wear and time. + - **MISMATCH:** + 1. Print the whole GOT hash: 64 hex digits on 2 lines. Comparing only 32 bits is grindable. + 2. Flush `$C6`, then show `A=ACCEPT ON RETRY OTHER KEY=REFUSE`. + 3. On **A**, set `tp_override = tp_got` (32 B) and `tp_ovr_key = ts_key` (16 B), then print `ACCEPT ARMED: G, SAME HOST`. + 4. On a retry where got != `tp_override`, print `ACCEPTED KEY NOT SEEN` and abort. The override is already cleared. + - **Store state:** `stage` consumes the load, and every save leaves it NONE. The next GET reloads (step 1.4), so a stale lookup is never reused. + +## 2. Bundle + +- **File:** `$(TRUST_STORE_DIR)/TRUST.P`. It reuses `ts_set_name`'s letter slot. If the file is absent, print `NO BUNDLE` once per boot and carry on. +- **Load:** read it into the TCP ring at `$C000` with the same `dos_open`/`dos_read` path L2 uses. Read at most 2,120 B + 1, so an oversize file shows. This happens before connect: the store image is already consumed into `ts_rec`, and the ring is free (`ts_guard`). **It never touches `cert_buf`**, so the comb cold code that lives there and the ip65 union are both irrelevant. +- **Verify order** (item 10): + 1. **Structure.** Magic `C6TB`, version `$01`, N ≤ 32, size == 8+64N+64, keys strictly ascending, each record mode `$10`, flags `$01`, uses 0. Failure prints `BUNDLE BAD FORMAT`. + 2. **Signature.** `tls_transcript_init/update/hash` over header‖records, then: + - copy the digest to `ecdsa_hash`; + - copy the trailer (r‖s) to `ecdsa_sig_r`; + - copy `TRUST_BUNDLE_PUBKEY_BYTES` to `ecdsa_pubkey_x/y`; + - set `ecdsa_curve_id = 0`, then `jsr ecdsa_verify`. The dispatcher gets VIC blanking, about 6% faster [M-prior]. + + Failure prints `BUNDLE BAD SIGNATURE`. + 3. **Generation floor.** Unsigned `gen >= TRUST_BUNDLE_GEN_FLOOR`. Failure prints `BUNDLE OLD GEN`. + + Then look up `ts_key` linearly and copy the 32 B SPKI to `tb_spki`. +- **SHA-256 without clobbering the transcript:** every bundle and store hash runs before connect. `tls_send_client_hello` calls `tls_transcript_init` (`tls13.s:420`), so no handshake state is live and none is inherited. The `ecdsa_*` 160 B struct is also dead before connect; CertificateVerify rewrites it. +- **Cache: verify once per boot.** Keep `tb_digest[32]` (the verified e) and `tb_ok` resident. On later GETs, reload the file and re-hash it. If e == `tb_digest`, skip ECDSA; the floor is re-checked because it is cheap. + - **What it costs:** one 2 KB DOS read plus about 33 SHA-256 blocks per GET. That is tens of ms at 48 MHz [E]. + - **What it saves:** a 16.4 s (comb) or 28-30 s (onchip) [M-prior] ECDSA on every GET after the first. + - Re-hashing means a bundle swapped on disk mid-session is caught. The digest is not a cache key the attacker controls: e is recomputed from the bytes. + - **When:** lazily, at the first 'G' that finds `TRUST.P`. The screen shows `VERIFYING BUNDLE…` because the step is long. Never at boot (Q4). +- **Replay:** the floor blocks bundles older than the PRG's floor. An older bundle that is still at or above the floor is accepted. That is acceptable for warn-only pins. Persisting the highest generation seen into the store is deferred. + +## 3. Packaging guard + +- **Knob:** `TRUST_BUNDLE=1`, separate from `TRUST_STORE` (the bundle needs the store's DOS code and `ts_key`). It is UCI-only. +- **Key source:** `TRUST_BUNDLE_KEY_INC ?= tools/trust_bundle_TEST_ONLY_pubkey.inc`. The Makefile copies it to `build/trust_key.inc`, content-compared like `https_host.inc`, so changing the key or the floor rebuilds exactly `trust_bundle.o`. +- **Release flag:** `make package` passes `TRUST_RELEASE=1`, which becomes `-D TRUST_RELEASE=1` and so is stamped. +- **Guard, in three layers:** + 1. **Source.** `trust_bundle.s` carries `.assert .not (.defined(TRUST_RELEASE) .and TRUST_BUNDLE_KEY_IS_TEST_ONLY), error, "TEST-ONLY bundle key in a release build"`. + 2. **Makefile.** Under `TRUST_RELEASE=1`, a parse-time `$(error)` fires if the chosen `.inc`'s 64 key bytes equal the test `.inc`'s. That catches a hand-edited `IS_TEST_ONLY = 0` on the test key. + 3. **Manifest.** `write_manifest.sh` records the key's SPKI SHA-256 line from the `.inc`, and `package-verify` refuses the test key's `e8648ff4…`. +- **Production key:** the maintainer runs `trust_bundle.py inc --key --floor N -o /prod_key.inc` and builds with `TRUST_BUNDLE_KEY_INC=` that path. + - Only the **public** key and the floor enter the build; the private key never touches `make`. + - Committing the `.inc` would be harmless, because it is public. The guard does not depend on its location. + - Bump the floor at each release that re-signs. + +## 4. Sizing + +Measured free bytes (`tools/measure_margins.py`, hand-built per profile, `TRUST_STORE=1`, reclaims at their defaults) [M]: + +| profile (PRG sha256) | LOADER | NET_CODE | NET_BSS_TAIL | CRYPTO_OVERLAY | CRYPTO_HOT | +|---|---|---|---|---|---| +| uci, REU, not shipped (`33c15e51`) | 796 | 253 | 63 | 1,339 | 714 | +| **uci-onchip** (`44240b5f`) | 796 | 261 | 63 | 1,242 | 758 | +| **uci-comb** (`2dd1168a`) | 345 | 253 | 63 | 211 | 453 | +| **comb Wikipedia demo** (`HTTPS_BODY_TO_REU=1`) | — | — | — | — | **link FAILS: `LIB_NISTCURVES_P256_CODE` overflows CRYPTO_HOT by 736 B** | +| same demo without `TRUST_STORE` (`61333a5c`) | 329 | 253 | 43 | 277 | 455 | + +**The store today [M]:** +- `TRUST_STORE_CODE` is **1,191 B** (`$4A7`, store + `uci_dos`). L2 quoted 1,185. +- `TRUST_STORE_BSS` is 108 B. +- On onchip both sit in CRYPTO_OVERLAY. On comb the code sits in CRYPTO_HOT and the BSS in OVERLAY. + +**The existing pin [M]:** `CERT_PIN_CODE` 104 B + `CERT_PIN_UI` 191 B (including the 32 B pin) = 295 B. + +**L3's own bytes [E]:** + +| part | code | BSS | where | +|---|---|---|---| +| Resident hook (generalised `cert_pin_*` + selection + override compare + bundle warn) | ~400 | ~150 (`tp_got` 32, `tb_spki` 32, `tp_override` 32, `tp_ovr_key` 16, `tb_digest` 32, flags) | must be resident on every profile. Under `TRUST_STORE=1` it replaces `cert_pin.s`'s 263 B of code rather than adding to it. | +| `trust_pre` + `trust_post` (store driver, prompts, record build) | ~550 | 0 | cold unit **U_TS**, together with `TRUST_STORE_CODE`: 1,191 + 550 ≈ **1,750 B ≤ 2,048** (`cert_buf`) | +| Bundle (load, structure, hash, verify driver, floor, lookup) | ~400 | 0 | cold unit **U_TB** | +| Strings + pubkey 64 | ~300 | 0 | split across U_TS and U_TB | +| **Total** | **~1,650** | **~150** | | + +**Verdict per profile:** +- **uci-onchip fits without a REU [E].** Hook → CRYPTO_HOT (758). BSS + policy + bundle (~1,100) → CRYPTO_OVERLAY (1,242) with the overflow strings → LOADER (796). That leaves about 1.3 KB scattered. The largest single block, OVERLAY at 1,242 B, is the constraint, so the cold-candidate code needs two segments, as `cert_pin` already does. +- **uci (REU profile, not shipped):** same as onchip, with about 100 B more slack. +- **uci-comb needs L5.** It has 1,325 B free in total [M], against the ~1,650 [E] that L3 adds. Once L5 moves `TRUST_STORE_CODE` cold, CRYPTO_HOT is about 1,644 B free [E from M]. The resident hook + BSS (~550) then fits with about 1 KB spare [E]. U_TS and U_TB are each under 2,048 B. +- **comb Wikipedia demo needs L5.** It is 736 B over today [M]. After L5 moves the store cold, the free space returns to about 1,250 B total [E from M]: HOT 455, LOADER 329, NET_CODE 253, OVERLAY 169, NET_BSS_TAIL 43. The resident ~550 then fits with **about 700 B spare [E]**, provided the hook is split HOT/LOADER and the BSS goes into OVERLAY. That is tight: re-measure after L5. +- **No option beyond L5 is needed.** Further reclaims, cold-loading from `/USB1` (S4 advised against it) and requiring a REU on onchip are all unnecessary. If the demo still misses after L5, the next lever is to put `tb_digest`/`tb_spki` in the REU on comb, worth 64 B. + +**Item 8 rule this design relies on:** the cold units are called only from `trust_pre`/`trust_post`. Both run with no socket, and L5's swap must refuse while `net_tcp_state == CONNECTED`. The hook and its BSS are never cold. + +## 5. Test plan (every PR shows red then green; each check gets a mutation) + +**Three kinds of test:** +- **INTERP:** the 6502 interpreter + UCI DOS model behind `tools/test_trust_store_6502.py`. It has no hardware and runs the shipped bytes. Each case: + - pokes a 91 B SPKI window and calls the hook; + - stubs `ecdsa_verify_256`'s result; + - asserts on the DOS traffic and the commands sent to the socket. +- **VICE:** real `ecdsa_verify_256` with the bundle poked into `$C000` and the verify entry called directly. It extends `test_trust_bundle_c64.py`, which has no DOS. +- **U64E:** real `/USB1` + `tools/https_e2e` listener with switchable certificates, via the harness queue (run-the-rigs order). + +| behaviour / negative | INTERP | VICE | U64E | +|---|---|---|---| +| First use (EMPTY and VALID-miss): records TOFU with `SHA(typed host)` and `tp_got`; host-side `trust_store.py` dump matches byte for byte | ✓ | | ✓ | +| Match: proceeds, **no** DOS write (generation unchanged) | ✓ | | ✓ | +| **Changed key without accept is refused:** no client Finished (listener sees none, `tls_last_state`), no GET, store generation unchanged | ✓ | | ✓ | +| **Accept records only the shown hash:** arm on K2; retry with K3 refused and override cleared; retry with K2 records ACCEPTED/K2 exactly; a third attempt with no re-arm and K3 is refused | ✓ | | ✓ | +| **Typed host mismatch never records:** arm for A, next attempt types B → override discarded, nothing recorded for A or B; L1-refused host → no load, no save, no dial | ✓ | | ✓ (A/B) | +| **Delete the store → `TRUST STORE EMPTY`** on every GET, then first use | ✓ | | ✓ | +| Every FAIL reason → `TRUST FAIL nn`, **no TCP_CONNECT issued** | ✓ | | NOMEDIUM only | +| **Tampered bundle:** a flipped record byte or a flipped r/s → BAD SIGNATURE; bad magic, size, N=33 or unsorted → BAD FORMAT; `tb_found = 0`, connection unaffected | struct | sig ✓ | one case | +| **Replayed bundle:** validly signed gen < floor → OLD GEN; gen == floor accepted | ✓ | ✓ | | +| Bundle cache: second GET skips ECDSA (cycle count); file swapped on disk → re-verified | ✓ | | ✓ (wall-clock) | +| Bundle warn: TOFU match + bundle mismatch → `BUNDLE WARN`, proceeds, no write | ✓ | | ✓ | +| Build pin for its host skips the store (no DOS traffic); other host → TOFU | ✓ | | | +| Never blocks mid-handshake: no GETIN/`$C6` reads between connect and close (counted); the cold swap refuses while CONNECTED | ✓ | | | +| Torn save during `trust_post` (reset mid-write) → older slot wins | (L2 has it) | | L2 | +| Packaging: `make package` with the test key → non-zero; test bytes with a hand-set `IS_TEST_ONLY=0` → non-zero; throwaway non-test `.inc` in `$TMPDIR` → passes | host-only (no VICE) | | | + +- **Hardware-only:** real UCI DOS timing, the `/USB1` medium, and handshake silence. The pre/post work must add **0 s** to server-perceived silence; measure it against the W0 figure. +- **Mutation coverage:** each security negative has a mutation that must turn its test red. Candidates: drop the override compare, compare 4 B instead of 32, skip the floor, record on MISMATCH, ignore `tp_ovr_key`. + +## 6. Open questions for the user (each with a recommendation) + +1. **Ship it in packages?** **Rec:** `TRUST_STORE=1` on uci-onchip and uci-comb once L5 lands. `TRUST_BUNDLE=1` waits until you create a production key; until then packages build without the bundle rather than block a release. +2. **Build pin scope under typed targets.** **Rec:** bind the pin to `HTTPS_HOST` only (S3 §4.1's "for its host"), so other typed hosts use bundle/TOFU. Today a pinned build enforces the pin on every typed host, which means it can only reach that host. +3. **First use when the bundle leaf disagrees.** **Rec:** proceed (warn-only), but don't auto-record. After close, ask `RECORD KEY? A=YES`; the key has already passed CertificateVerify and Finished, so no retry is needed. The alternatives are auto-record, or treating it like a mismatch. +4. **Bundle verify timing.** **Rec:** lazily at the first 'G', once per boot, with the digest cached as in §2. Verifying at boot adds 16-30 s to every boot; verifying every connection adds it to every GET. +5. **No medium / store FAIL refuses every GET (S3 §4.3).** **Rec:** keep it strictly fail-closed, with no "continue unpinned" key. The message names the reason; deleting both slots (→ EMPTY, which is visible) is the explicit way to reset. diff --git a/docs/155-phase2/README.md b/docs/155-phase2/README.md new file mode 100644 index 0000000..d73ebdb --- /dev/null +++ b/docs/155-phase2/README.md @@ -0,0 +1,35 @@ +# #155 phase 2: design record (UCI trust store) + +The design inputs behind the UCI trust store, typed hostnames, the signed pin +bundle and the trust policy. The code is merged (#258, #260-#262, #266, #268, +#269); this folder is kept because the tree cites it (`DECISIONS N`, +`S3 §5`). Figures are pinned to the master commit each file names; re-measure +before reusing one. ip65 is out of scope (`TRUST_STORE=1` is an `$(error)` +there). + +| File | What it is | +|---|---| +| `DECISIONS.md` | The binding user decisions, items 1-11. Every `DECISIONS N` cite in the tree means this file. | +| `S3-trust-design.md` | Trust semantics (precedence, record/first-use rules) and the 64 B record format. Raw survey data in `data/`. | +| `L3-design.md` | The trust-policy state machine and sizing; §6 holds the questions DECISIONS 11 answers. | +| `S4-reu-banking.md` | REU code-banking study. Not built beyond the comb cold bank; read before banking more code. | + +## S1 storage findings (2026-09-30, U64E, master ac7099d) + +These were measured on hardware for the store's disk I/O and are not written +down anywhere else. + +- Use the UCI DOS target on persistent media (`/USB1`), never `/Temp`, which is a RAM disk lost at power-off [inferred]. +- Speed at 48 MHz: save ~130 ms and load ~77 ms on `/USB1` (~29/30 ms on `/Temp`). KERNAL drive 8 takes 2.5-3 s per save, drive-bound. +- Not found: "FILE DOESN'T EXIST" vs "PATH DOESN'T EXIST" (no medium). Classify on `buf[0]=='F' && buf[5]=='D'`. +- Opening an existing file for write empties it, and nothing is durable until close; a C64 reset mid-replace destroyed the old record. Hence DECISIONS 6: ping-pong `TRUST.A` / `TRUST.B` slots plus read-back verify. +- One WRITE_DATA stores at most 891 B, not 892. +- A read error can report a stale "00,OK" [inferred], so always check the byte count. +- A handle left open across a C64 reset leaks in the firmware [inferred]. +- VICE has no UCI emulation, so the DOS path is testable only on a device. + +## S2 memory + +The reclaims R1-R4 merged as #260, each knob-gated with a re-arm switch +(DECISIONS 2 and 5). Current margins come from `tools/measure_margins.py`, +never from the figures recorded at the time. diff --git a/docs/155-phase2/S3-trust-design.md b/docs/155-phase2/S3-trust-design.md new file mode 100644 index 0000000..9c7cbb8 --- /dev/null +++ b/docs/155-phase2/S3-trust-design.md @@ -0,0 +1,135 @@ +# S3: in-C64 trust design for #155 phase 2 (UCI) + +2026-09-30, master `ac7099d`. Labels: **[M]** measured today, **[M-prior]** measured earlier (source named), **[I]** inferred. Everything here runs on the 6510. The ESP32 TLS offload is not used anywhere. +Raw data is in `data/`: `survey_2026-09-30.jsonl` (43 hosts), `ct_history.txt` (CT via certspotter), and `survey.py`. +Vantage: this Mac, with Cloudflare WARP in full-tunnel mode (egress DFW). WARP is not intercepting TLS: all 39 chains that came back verify against the system store, each under its public CA **[M]**. Edge-dependent answers may differ from other locations. + +## 1. What "MAC-based CA validation" can mean + +A MAC cannot verify a CA signature, because it needs a secret shared with the signer. There are two realistic readings. + +- **(i) Validate chains on a trusted host at packaging time and ship integrity-protected pins.** This is sound, and it is how the bundle gets built: a host tool validates each target against the OS/Mozilla store, then emits leaf SPKI hashes and P-256 intermediate keys. Which integrity primitive to use depends on where the bundle lives: + - **Bundle compiled into the PRG:** needs no MAC at all. Anyone who can alter the bundle can also alter the code, so the bundle's integrity is the release's integrity (the MANIFEST sha256). + - **Bundle shipped as a separate file** (refresh without a new PRG): needs an **ECDSA P-256 signature, not a MAC**. The public key sits in the PRG, the maintainer holds the private key, and `ecdsa_verify_256` checks it. A MAC key in a public PRG is a published key, so anyone could forge the bundle. Checking the signature costs one verify per boot: 16.4 s (comb) or 30.5 s (onchip) at 48 MHz **[M-prior]**, or about 24 min for ip65 at 1 MHz **[M-prior]**. +- **(ii) HMAC over the on-device TOFU store.** What it buys depends on where the key lives: + - **Key compiled into the PRG, or kept on the same disk:** equivalent to a checksum. It catches torn writes and bit rot, and nothing more. + - **Key derived from a passphrase typed at boot:** stops offline edits of the store. But on UCI the realistic tamperer is a LAN host that can reach the Ultimate's REST/FTP. The device serves `writemem`/`run_prg` over REST, which the rigs use every day with no credential, and such an attacker can patch live code instead. I did not measure whether a firmware network password closes that. So a passphrase HMAC buys little on these devices. **Decision: SHA-256 checksum only**, reusing the linked code. Call it a checksum and never a MAC. + +**The closest in-C64 meaning of "validate the CA" is §2:** the 6510 itself verifies the leaf's signature under a pinned intermediate key. + +## 2. On-device leaf←intermediate verification: measured coverage + +The survey fetched each host using the client's exact offer: TLS 1.3, ChaCha20, X25519 only, and `-sigalgs ECDSA+SHA256`. A host counts as reachable if the handshake completes with a P-256 leaf and CertificateVerify scheme 0x0403 **[M]**. + +| result | hosts | +|---|---| +| **Covered:** leaf signed with ecdsa-with-SHA256 under a P-256 intermediate (7) | github.com (Sectigo DV E36); www.google.com and www.youtube.com (GTS WE2); ietf.org, www.cloudflare.com and lemon64.com (GTS WE1); example.com (Cloudflare/SSL.com ECC CA 3) | +| Not covered: P-384 intermediate, leaf signed with SHA-384 (11) | lwn.net, en.wikipedia.org, www.wikipedia.org, browserleaks.com, letsencrypt.org, sourceforge.net (LE YE2); news.ycombinator.com, stackoverflow.com, kernel.org, slashdot.org (LE YE1); www.netflix.com (DigiCert G3) | +| Not covered: RSA intermediate over a P-256 leaf (2) | www.facebook.com, www.instagram.com | +| Unreachable by this client at all (23) | 18 have RSA leaves (amazon, microsoft, apple, reddit, x.com, linkedin, bbc, nytimes, guardian, archive.org, mozilla, python, pypi, debian, duckduckgo, gitlab, developer.mozilla, gnu); bing refused the offer (no handshake, though it has a P-256 leaf); 4 are TLS 1.2 only (eff, c64-wiki, csdb, arstechnica) | + +- **Coverage:** 7 of 20 reachable hosts (35%), or 7 of 43 surveyed (16%) **[M]**. +- **Coverage after dropping Cloudflare-fronted hosts:** four of the seven are behind Cloudflare (`server: cloudflare` **[M]**), which CLAUDE.md puts out of scope because of its 15 s deadline. That leaves **3 of 16 (github, google, youtube)**. +- **The project's own four targets:** only github is covered. Let's Encrypt's current ECDSA intermediates are all P-384. +- **SHA-384 alone buys nothing:** all 11 SHA-384-signed leaves in the sample sit under P-384 keys **[M]**. +- **Bundle size:** 4 intermediate keys cover all 7 covered hosts, at 64 B each plus an 8 B AKI tag, about **288 B** **[I]**. +- **Intermediate lifetimes:** not_after runs from 2029-02 (GTS) to 2036 (Sectigo) **[M]**. This is certificate expiry, not how long the CA keeps issuing from a key, and I did not measure the latter. +- **The CA itself can change:** CT shows github.com issued by both Sectigo and Let's Encrypt in Aug 2026 **[M]**. A single-intermediate pin can fall off coverage without anything expiring. + +**Cost [I from M-prior figures]:** +- One extra verify: +16.4 s on comb or +30.5 s on onchip at 48 MHz. SHA-256 over a 0.8-1.5 KB TBS is negligible. +- Server-perceived silence on comb is 24.7 s today (W0) **[M-prior]**, so about **41 s** with the extra verify. Onchip goes from about 39 s to about 69 s. +- Measured patience is 90 s for github and 45 s for browserleaks **[M-prior]**. Comb fits both; onchip fits github only. Google's and YouTube's patience is unmeasured. + +**Mechanics [I, from source]:** +- The leaf is already whole in `cert_buf` (2 KB on UCI) when the deframer calls `tls_cert_stream_finish`. The intermediate on the wire never needs parsing, because its key comes from the bundle. +- Work to add: + - hash the TBS; + - parse the leaf's signatureValue (reusing `ecdsa_parse_der_sig`); + - select the intermediate by AKI; + - verify; + - re-extract the leaf key, since the verify overwrites `ecdsa_pubkey_x/y`. +- Estimated code: a few hundred bytes. It fits in the ~1.5 KB free on uci and uci-onchip. It does not fit uci-comb (92 B OVERLAY + 79 B HOT, #155), which is S2's problem. +- **It must be paired with the SAN check (`x509_name.s`, UCI-only).** Without the SAN check, a GTS certificate for the attacker's own domain passes. So this lane is UCI-only, and on ip65 it is worthless. +- **No clock, so no expiry or revocation checks.** An expired or revoked leaf from a bundled intermediate stays valid forever [I]. +- **Decision: use it only at first use and on key rotation**, never on every connection (§4). + +## 3. Leaf key rotation: re-capture one month after the baseline + +| host | 2026-08-30 → 2026-09-30 | current not_after | +|---|---|---| +| github.com, www.google.com, ietf.org, example.com, news.ycombinator.com | **ROTATED**. All five renewed early, with a fresh key. | 11-29, 12-11, 12-17, 12-25, 12-19 | +| en.wikipedia.org, lwn.net, browserleaks.com | same key, same cert (not renewed yet) | 11-03, 11-27, 11-04 | + +- **In one month, 5 of 5 renewals changed the key; 0 of 5 kept it** **[M]**. +- **CT history (certspotter):** across 10 hosts with history, consecutive issuances sharing a key: **0** **[M]**. Two hosts returned no CT data. +- **Many concurrent certificates:** google.com has 80 distinct keys and ietf.org 82. Six connections per host from this vantage all saw one key **[M]**, so other edges probably serve other keys **[I]**. +- **This corrects the baseline memory's "browserleaks reuses its key":** certspotter now lists 2 issuances with 2 keys. The earlier "each seen twice" was most likely precert/cert pairs **[I]**. +- **Policy consequence:** + - A leaf pin shipped in a bundle goes stale within about 60 days for every host measured. + - **Bundle leaf pins are warn-only.** + - **Enforcing bundle entries must be intermediate keys (§2).** + - A build pin (`HTTPS_PIN_SPKI_SHA256`) stays enforce-by-default, but only for a deliberate build aimed at one host. + +## 4. Semantics (decisions) + +1. **Precedence: build pin > bundle > TOFU.** + - A build pin disables store lookup for its host. + - A bundle intermediate entry cannot be overridden by the store. + - A bundle leaf entry is advisory: a mismatch goes to the accept flow with a louder "BUNDLE" tag. +2. **Record only after the server Finished verifies** (CertificateVerify proves possession of the key; Finished binds the transcript). On UCI, the name check must also have passed. Stage the new record in RAM and write it to disk after `tcp_close`. Writing to disk mid-handshake would eat into the server's patience. +3. **First use is exactly one state:** the store loaded and validated, and the host is absent from it. Everything else fails closed: + - checksum failure on both slots; + - a DOS error other than 62 FILE NOT FOUND; + - an unknown version; + - a device not present. + - Both slots returning 62 means "empty store". It is allowed, but the banner says `TRUST STORE EMPTY` so that deletion by an attacker is visible. Bundle and build pins still apply, because they live in the PRG. +4. **CA-assisted TOFU:** at first use or on rotation, if the leaf's AKI names a bundled intermediate and leaf←int verifies (plus SAN), record automatically. Otherwise show `NEW KEY host xxxxxxxx` and ask. This narrows the window for an on-path attacker at first use, for covered hosts only. +5. **Accepting a new key:** + - On a mismatch, abort. Never block mid-handshake for input: patience is 45-90 s. + - Show `KEY CHANGED EXP xxxxxxxx GOT yyyyyyyy` (the existing `cert_pin_report`) and offer "(A)ccept on retry". + - Accept arms a one-shot RAM override holding the full 32 B **GOT** hash. + - The retry records only if the key presented equals that hash, so the user accepts the key they saw, not whatever arrives next. + - The override clears after one attempt. +6. **Bundle refresh:** ships as a new PRG release, reusing the existing `make package` / MANIFEST integrity. A separate signed bundle file is optional, and only worth it if releases are too slow; it costs one verify per boot (§1). + +## 5. Record format (decisions; medium-agnostic, S1 picks the medium) + +- **Host key: 16 B, the truncated SHA-256 of the lowercased host.** + - The attack to defeat is a TOFU second preimage. The attacker registers a label (free under any domain they own) whose hash equals the victim's, gets it visited first, and their key is then served for the victim. + - With a *k*-bit key that costs 2^k hashes. 32 bits is trivial. 64 bits is about 1.8×10^19, which is weeks for a GPU cluster **[I]**. **128 bits** is out of reach. + - Full names would work too, but make records variable-length. +- **Record, 64 B fixed:** + - host key: 16 + - SPKI SHA-256: 32 + - mode: 1 (`TOFU` / `ACCEPTED` / `OVERRIDE`) + - flags: 1 + - use count: 2 + - display prefix: 12 (non-authoritative, for a trust-list screen) +- **File:** + - header: magic `C6TS` 4, version 1, generation 2, N 1 + - N records + - trailer: SHA-256(header‖records) truncated to 8 B + - With N ≤ 32 that is 2,064 B **[I]**. +- **Torn writes: ping-pong slots `TRUST.A` / `TRUST.B`.** + - Load both, validate each, and take the valid slot with the higher generation (serial comparison). + - Save to the *other* slot (scratch it, then write). A torn write only ever damages the older copy. + - Never use `@0:` save-replace, because of the 1541 save-with-replace bug. +- **The "no pin stored" state** is the absence of the host key in a valid store (§4.3). There is no sentinel value inside a record. + +## 6. Threat model + +| mechanism | on-path at first use | on-path later | disk / LAN tamper | stale bundle | +|---|---|---|---|---| +| Build pin (PRG) | stops | stops | n/a (PRG integrity) | n/a; the build goes stale on rotation (fails closed) | +| Bundle intermediate + on-C64 verify + SAN (UCI) | **stops** (7/20 hosts) | stops | n/a | intermediate retired → accept flow; no expiry check | +| Bundle leaf pin (warn) | warns only | warns only | n/a | stale in ≤60 days (§3) | +| TOFU store | **does not stop** | stops, until the user accepts | a checksum does not stop it; deletion resets to first use (banner shows it) | n/a | +| Store checksum / compiled-key HMAC | — | — | detects corruption only | — | +| Passphrase HMAC | — | — | stops offline edits; moot while the Ultimate's REST accepts `writemem` | — | +| Signed bundle file (P-256) | — | — | stops bundle forgery | a replay of an old bundle needs a generation floor in the PRG | + +**Residuals under every mechanism:** +- no expiry or revocation checks (no trusted clock); +- ip65 gets no name check and so no chain lane; +- mis-issuance by a bundled intermediate for the victim's name passes. diff --git a/docs/155-phase2/S4-reu-banking.md b/docs/155-phase2/S4-reu-banking.md new file mode 100644 index 0000000..a57fb68 --- /dev/null +++ b/docs/155-phase2/S4-reu-banking.md @@ -0,0 +1,125 @@ +# S4 — REU code banking (spike, #155 phase 2, DECISIONS item 8) + +Tree: master `ac7099d`, worktree `p2-s4`. **M** = measured, **E** = estimate. +Maps (M): uci-onchip `eb348698…` (`cfg/c64-https-uci.cfg`), uci-comb `ed681de1…`, +wiki-comb `7bccb256…` (`HTTPS_HOST=en.wikipedia.org HTTPS_PATH=/wiki/Commodore_64 +HTTPS_BODY_TO_REU=1`; both comb builds link `cfg/c64-https-uci-onchip.cfg`). Sizes +come from each map's Modules list. Sizes are pre-S2: R2 shrinks `der_decode`, R3 +gates `viewer`. + +## 1. Phase map (bytes of code+rodata, M) + +| phase | code (segment / object) | onchip | comb | +|---|---|---|---| +| boot | `reu_mul_init`, sqtab, banner (LOADER); comb precompute runs the **P-256 lib** | — | — | +| key exchange | **X25519 image**: `fe25519` 2,692 (CRYPTO_CODE) + `LIB_X25519_CODE` 719 + `X25519_GLUE_CODE` 148 + `X25519_RODATA` 64; `hmac_drbg` 831 (keygen) | 3,623 | 3,623 | +| record/AEAD (EE → end of body) | chacha20 602, poly1305 734, aead 398, word32 549, sha256 1,859, hkdf, tls_record* | resident | resident | +| cert parse + name | tls_cert (TLS_CODE 874), der_decode 694, x509_name 491, CERT_BUF_BSS 2,048 | | | +| ECDSA verify | **P-256 image**: `LIB_NISTCURVES_P256_CODE` + `_RODATA` 192 + `MUL_CODE` 162 | **8,716** (code 8,362) | **9,849** (code 9,495) | +| Finished / keys | tls_keyschedule 1,277 (TLS_CODE 973), hkdf, sha256 | | | +| HTTP body | http.o (CODE 850, AUX 520+204, SINK 190) | | | +| viewer (after close) | VIEWER_CODE 1,189 (live only in wiki after R3) | | | + +X25519 runs at CH (keygen) and after SH (shared). P-256 runs only at CertificateVerify +(and the comb boot precompute, and the future signed-bundle check). **The two never overlap.** +Call stack during verify (M, from source): `tls_deframe` pump (TLS_DEFRAME_CODE) → +`tls_handle_cert_verify` (TLS_CODE) → `ecdsa_verify` (CRYPTO_CODE) → lib. All other +crypto has returned by then. +Outward imports (M, map Imports list): the P-256 and X25519 objects depend only on +`poly1305.o` (`ct_mul_8x8`, `poly_prod_lo/hi`, `smc_*_imm` SMC operands), `data.o`/`mul_tables.o` +BSS, the REU register equates and ZP. **`poly1305.o` must stay resident.** Nothing +imports from the two libraries except `ecdsa_verify.o`, `boot.o` (comb precompute), +`tls_ecdh.o` and `x25519_tables.o`. +Free today (M, `measure_margins.py`, wiki-comb): 94+49+43+265+183 B tail + 101 B hole ≈ **0.7 KB total**. + +## 2. Swap cost (M, U64E "Ultimate 64 Elite", REU 16 MB, DeviceLock) + +The probe and its raw data (`probe_reu_dma.py`, `reu_dma.json`) were not kept; only the figures here survive. Each timed DMA is followed by a +clone of `reu_execute`'s REU_CONFIRM_LONG confirm + settle, and CIA2 timer A is +stopped before it is read. The setup was 3×3 at 48 MHz and 4×3 at 1 MHz. A CPU-loop +control confirms turbo: 273 ticks at 48 MHz against 13,677 at 1 MHz. The confirm +never expired. + +| bytes | 48 MHz REU→C64 | 48 MHz C64→REU | 1 MHz (either way) | +|---|---|---|---| +| 3,623 (X25519, interp.) | ~1.1 ms | ~0.8 ms | ~3.8 ms | +| 7,680 | 2.41 ms | 1.62 ms | 8.1 ms | +| 10,240 | 3.19 ms | 2.16 ms | 10.8 ms | +| 32,768 | 9.93 ms | 6.84 ms | 34.3 ms | + +At 48 MHz: about 3.3 MB/s REU→C64 and 4.8 MB/s C64→REU. At 1 MHz: 0.95 MB/s, +unblanked. The fixed overhead per transfer is 7 µs. **Contradiction:** the `crypto_swap.s` header and the +engineering notes say "~1 MB/s regardless of turbo"; on this U64E that is wrong by +about 3× at 48 MHz (n=1 device and firmware; I have not measured the cause). +Instrument trap: the first pass after `SYS` ran the CPU at 1 MHz (ctrl 13,634), while +its DMAs already ran at turbo rates. That pass is excluded. + +**Per handshake:** at most ~11 ms at 48 MHz and ~45 ms at 1 MHz. Against a 38–45 s +handshake, that is noise. + +**No-REU `/USB1` path (E):** `dos_read` (S1) pays two `uci_fence`s per byte (status + +data). That is 5 × 217 × 5 cy ≈ 114 µs each at 48 MHz, so ≈ 4.4 KB/s, plus ~40 ms per +512 B part (the SOCKET_READ round-trip figure). A P-256 image then costs ≈ 2.6 s and +X25519 ≈ 1.1 s. That adds 3.7–5.5 s per handshake (+8–12 % on 45.5 s). The image +reloads mid-handshake as DOS traffic while the TLS socket is open, which is an +interleave nobody has tested. + +## 3. Constraints + +- **Mechanism.** `crypto_swap.s` is P-384-shaped: one fixed `OVERLAY_SIZE=$1E00` + slot at `__CRYPTO_OVERLAY_START__`, and it DMAs through `reu_execute` (SPEC §8.2 + confirm + settle, already covered). S2's R1 deletes it (no importer). A banking + build would re-arm a leaner dispatcher (~80 B, E), gated as DECISIONS 2 requires. +- **REU banks (M, source).** Every profile's boot writes banks 0–1 (`reu_mul_init`). + Comb claims bank 2 `$0000-$3FFF`. P-384 reserves banks 6–7. Wiki writes from + `$10:0000` up (754 KB page ⇒ banks 16–27). **Trap:** `reu_layout.inc`'s + `REU_OVERLAY_P256[_VERIFY]=$22100` and `REU_OVERLAY_STORE_BASE=$20100` sit + *inside* comb's Lim-Lee table, so they must not be reused. Put the images in + bank 3 (reservation only; no writer since #245) or at bank 2 `$4000+`. That fits a + 512 KB REU. +- **Library segments.** No archive member needs editing (§6.1). `LIB_NISTCURVES_P256_*` + and `LIB_X25519_CODE` are whole segments that ld65 can place with `load=`/`run=` + plus `define=yes`. `fe25519` already goes through the wrapper knob + `X25519_SEG_FE25519` (`build_x25519.sh`), so it can get its own segment. The + constraint is granularity: a segment moves whole (9,495 B for comb's P-256 code). +- **Staging.** The PRG must carry the banked image somewhere free *at load time*. The + P-256 image (8.7–9.8 KB) does not fit `$E000-$FDFF` (7,680 B) or `$C000-$CFFF`. + It does fit if loaded over `$A000-$CFFF` (runtime BSS + TCP ring) and stashed + before boot zeroes `$A000`. The cost is a PRG that is ~10 KB longer. +- **SMC.** `fp256`, `mod256`, `points256_*`, `fe25519` and `x25519` are heavily + self-modifying (hundreds of `sta label+n`), and nobody has audited whether any of + it is written once at init. The safe design is to **stash the outgoing image + before fetching the incoming one**, which costs about 2 ms extra. It is also why + the `/USB1` variant (pristine images only) needs that audit first. +- **Resident floor.** These stay resident: ZP (the libraries already time-share it), + all BSS (sqtab/TABLES_BSS, `LIB_NISTCURVES_P256_BSS`, LIMLEE_BSS for the comb + anchor state), `poly1305.o`, `reu_execute`, and every routine on the call stack + above, including the whole deframer. +- **uci-onchip has no REU.** On U64/C64U the REU is a built-in *config toggle*, not + extra hardware: `_device_prep` already sets it. For UCI, then, "require an REU" + costs a menu setting plus the product's "no REU" claim, whereas `/USB1` costs + seconds per handshake (§2). The ip65 product is the only one where an REU is real + hardware. + +## 4. Candidate sets and recommendation + +| design | slot | bytes freed (E, from M sizes) | swaps / handshake | +|---|---|---|---| +| **D1: X25519 ⇄ P-256, one slot** at P-256's home | 8,716 / 9,849 | **≈3.5 KB** (3,623 − dispatcher) | 2 (≤6 ms) | +| **D2: verify-eviction.** State A holds everything off-stack during verify (X25519 3,623 + sha256 1,859 + hmac_drbg 831 + tls_keyschedule 973 + chacha 602 + word32 549 + x509_name 491 + aead 398 = 9,326; + der 694). State B is P-256. | same | **≈8.6 KB onchip / ≈9.7 KB comb** (≈ the P-256 image) | 2 exchanges (≤11 ms) | +| D3: + viewer / boot-only code | — | +1.2 KB on wiki. A viewer doesn't need REU banking; boot code can just be reused as BSS after boot. | — | + +**Recommendation.** Build only when a phase-2 tenant is actually refused a link. When +that happens, build **D1 first, for uci-comb** (including the wiki demo). Comb already +requires an REU, has free banks, and has only ~0.7 KB left. D1 moves three segments, +adds one small dispatcher and has a single call site per library. It gives 5× today's +total headroom for about 6 ms per handshake. D2 roughly triples that, but it relinks +about 10 objects into one contiguous 9.8 KB range in CRYPTO_HOT. Its correctness then +rests on an "off-stack during verify" invariant that the bundle-signature path +(DECISIONS 4) will exercise a second time, so hold it until D1 has shipped and an SMC +audit exists. **One slot, not several.** X25519 and P-256 are the only large, disjoint +pair; everything else is either live across the whole session (record layer) or too +small to pay for a second slot. **Do not bank uci-onchip via `/USB1`:** it is slower +by seconds, untested mid-handshake, and the REU toggle is free. **ip65** would gain the +most (it is "essentially full"), but only by becoming a new REU-required product. +That is a product decision, not a spike result. diff --git a/docs/155-phase2/data/ct_history.txt b/docs/155-phase2/data/ct_history.txt new file mode 100644 index 0000000..1c657f7 --- /dev/null +++ b/docs/155-phase2/data/ct_history.txt @@ -0,0 +1,72 @@ +github.com issuances=10 distinct_keys=9 consecutive_same_key=0/9 + 2026-07-03 b8e9b0a8 Sectigo + 2026-08-02 a7831b05 Let's Encrypt + 2026-08-04 5c00bad1 Sectigo + 2026-08-10 9ffc4eb6 Sectigo + 2026-08-16 08af26ff Sectigo + 2026-08-30 4b62d421 Sectigo + 2026-08-31 1fdba877 Sectigo + 2026-09-01 ff088be6 Sectigo +en.wikipedia.org issuances=0 distinct_keys=0 consecutive_same_key=0/0 +lwn.net issuances=3 distinct_keys=3 consecutive_same_key=0/2 + 2026-07-07 cd0ad9ae Let's Encrypt + 2026-08-29 86374d92 Let's Encrypt + 2026-09-06 2ab1b305 Let's Encrypt +browserleaks.com issuances=2 distinct_keys=2 consecutive_same_key=0/1 + 2026-08-06 86ccce59 Let's Encrypt + 2026-08-06 ce408b17 Let's Encrypt +www.google.com issuances=80 distinct_keys=80 consecutive_same_key=0/79 + 2026-09-28 27460835 Google Trust Services + 2026-09-28 81b6335e Google Trust Services + 2026-09-28 659bf8ef Google Trust Services + 2026-09-28 b3fe7cdf Google Trust Services + 2026-09-28 968b12a0 Google Trust Services + 2026-09-28 b1abd56d Google Trust Services + 2026-09-28 105198f3 Google Trust Services + 2026-09-28 e20b6454 Google Trust Services +ietf.org issuances=82 distinct_keys=82 consecutive_same_key=0/81 + 2026-09-18 ed33d673 Let's Encrypt + 2026-09-18 81ad82b6 Google Trust Services + 2026-09-18 7b2a4661 Google Trust Services + 2026-09-19 9b3d4131 Let's Encrypt + 2026-09-27 1b022be6 Google Trust Services + 2026-09-27 9f6b7adb Google Trust Services + 2026-09-27 a93b96ef Google Trust Services + 2026-09-27 30f44f2f Google Trust Services +example.com issuances=13 distinct_keys=13 consecutive_same_key=0/12 + 2026-07-29 9fbbc9b1 SSL.com + 2026-07-29 0856752f SSL.com + 2026-09-17 48613e13 Sectigo + 2026-09-24 3caa3576 Sectigo + 2026-09-26 3116c9c5 SSL.com + 2026-09-26 63dc72f0 SSL.com + 2026-09-26 62335ae3 SSL.com + 2026-09-26 b68c8c1a SSL.com +news.ycombinator.com issuances=2 distinct_keys=2 consecutive_same_key=0/1 + 2026-07-19 bd2735e6 Let's Encrypt + 2026-09-20 7cd959cf Let's Encrypt +www.youtube.com issuances=0 distinct_keys=0 consecutive_same_key=0/0 +lemon64.com issuances=5 distinct_keys=5 consecutive_same_key=0/4 + 2026-07-16 b0c0428e Google Trust Services + 2026-07-20 15bdd9c4 Google Trust Services + 2026-08-03 04d4691a SSL.com + 2026-09-13 af49b455 Google Trust Services + 2026-09-18 e572469e Google Trust Services +kernel.org issuances=12 distinct_keys=9 consecutive_same_key=0/11 + 2026-08-14 356a5634 Let's Encrypt + 2026-08-18 0047e9f6 Let's Encrypt + 2026-08-22 0f81f7eb Let's Encrypt + 2026-08-25 785d6b96 Let's Encrypt + 2026-08-27 86bc78a0 Let's Encrypt + 2026-09-20 f0d682b4 Let's Encrypt + 2026-09-20 ebf5fb80 Let's Encrypt + 2026-09-20 1422cf12 Let's Encrypt +stackoverflow.com issuances=19 distinct_keys=19 consecutive_same_key=0/18 + 2026-08-22 14ef49a6 Let's Encrypt + 2026-08-22 187fb524 Let's Encrypt + 2026-08-22 a09fad60 Let's Encrypt + 2026-08-22 59b3356e Google Trust Services + 2026-08-22 b30e65b3 Google Trust Services + 2026-09-25 0911f5a9 Let's Encrypt + 2026-09-25 d370635d Let's Encrypt + 2026-09-26 50f2198b Let's Encrypt diff --git a/docs/155-phase2/data/survey.py b/docs/155-phase2/data/survey.py new file mode 100644 index 0000000..417a63d --- /dev/null +++ b/docs/155-phase2/data/survey.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""S3 chain survey: fetch each host's chain with the C64 client's exact offer.""" +import hashlib, json, re, subprocess, sys, datetime +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec, rsa + +HOSTS = sys.argv[1:] +C64_OFFER = ["-tls1_3", "-ciphersuites", "TLS_CHACHA20_POLY1305_SHA256", + "-groups", "X25519", "-sigalgs", "ECDSA+SHA256"] + +def keydesc(k): + if isinstance(k, ec.EllipticCurvePublicKey): return "EC-" + k.curve.name + if isinstance(k, rsa.RSAPublicKey): return f"RSA-{k.key_size}" + return type(k).__name__ + +def spki(c): + return hashlib.sha256(c.public_key().public_bytes( + serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)).hexdigest() + +def fetch(host, offer): + cmd = ["openssl", "s_client", "-connect", f"{host}:443", "-servername", host, + "-showcerts", "-verify_return_error"] + offer + p = subprocess.run(cmd, input=b"", capture_output=True, timeout=25) + out = p.stdout.decode(errors="replace") + pems = re.findall(r"-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----", out, re.S) + ver = re.search(r"Verify return code: (\d+) \(([^)]*)\)", out) + sig = re.search(r"Peer signature type: (\S+)", out) + cph = re.search(r"New, TLSv1.3, Cipher is (\S+)", out) + fetch.last = {"peer_sig": sig.group(1) if sig else None, "cipher": cph.group(1) if cph else None, "rc": p.returncode} + return [x509.load_pem_x509_certificate(p.encode()) for p in pems], (ver.group(0) if ver else p.stderr.decode()[-200:].strip()) + +rows = [] +for h in HOSTS: + r = {"host": h} + try: + certs, ver = fetch(h, C64_OFFER) + r["verify"] = ver + r.update(fetch.last) + if not certs: + r["c64_reachable"] = False + certs, ver = fetch(h, ["-tls1_3"]) or ([], "") + r["verify_default"] = ver + else: + r["c64_reachable"] = True + if certs: + leaf = certs[0] + r["leaf_key"] = keydesc(leaf.public_key()) + r["c64_reachable"] = (r["c64_reachable"] and r["leaf_key"] == "EC-secp256r1" + and fetch.last.get("peer_sig") == "ecdsa_secp256r1_sha256" and fetch.last["rc"] == 0) + r["leaf_sigalg"] = leaf.signature_algorithm_oid._name + r["leaf_der_len"] = len(leaf.public_bytes(serialization.Encoding.DER)) + r["leaf_tbs_len"] = len(leaf.tbs_certificate_bytes) + r["leaf_spki"] = spki(leaf) + r["leaf_not_after"] = leaf.not_valid_after_utc.date().isoformat() + r["leaf_days"] = (leaf.not_valid_after_utc - leaf.not_valid_before_utc).days + r["issuer"] = leaf.issuer.rfc4514_string() + r["chain_len"] = len(certs) + if len(certs) > 1: + i = certs[1] + r["int_subject"] = i.subject.rfc4514_string() + r["int_key"] = keydesc(i.public_key()) + r["int_sigalg"] = i.signature_algorithm_oid._name + r["int_spki"] = spki(i) + r["int_not_after"] = i.not_valid_after_utc.date().isoformat() + # confirm leaf is really signed by certs[1] + try: + leaf.verify_directly_issued_by(i); r["leaf_by_int"] = True + except Exception as e: + r["leaf_by_int"] = f"NO: {e}" + except Exception as e: + r["error"] = repr(e)[:200] + rows.append(r) + print(json.dumps(r), flush=True) diff --git a/docs/155-phase2/data/survey_2026-09-30.jsonl b/docs/155-phase2/data/survey_2026-09-30.jsonl new file mode 100644 index 0000000..227e4dc --- /dev/null +++ b/docs/155-phase2/data/survey_2026-09-30.jsonl @@ -0,0 +1,43 @@ +{"host": "github.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 1009, "leaf_tbs_len": 920, "leaf_spki": "ff088be6f80e2e0c040f8d564b40cd17c4224c1551fcfe3529dd7aded985c4ad", "leaf_not_after": "2026-11-29", "leaf_days": 89, "issuer": "CN=Sectigo Public Server Authentication CA DV E36,O=Sectigo Limited,C=GB", "chain_len": 3, "int_subject": "CN=Sectigo Public Server Authentication CA DV E36,O=Sectigo Limited,C=GB", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "6526a0bc3ce396d2e47b05c406e0f1233a56fdda55c3526ebef99dd21864cdd6", "int_not_after": "2036-03-21", "leaf_by_int": true} +{"host": "lwn.net", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1048, "leaf_tbs_len": 925, "leaf_spki": "86374d92fa452d09b2e7ad0ab1805780c527b9c0a3f66f1119ea39df0d97d2af", "leaf_not_after": "2026-11-27", "leaf_days": 89, "issuer": "CN=YE2,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE2,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "en.wikipedia.org", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1636, "leaf_tbs_len": 1514, "leaf_spki": "c0a0573a0bfb1cc696f3ccb6a0be4e784fb9d887755b3e509a44adc41481c3fe", "leaf_not_after": "2026-11-03", "leaf_days": 89, "issuer": "CN=YE2,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE2,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "browserleaks.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 952, "leaf_tbs_len": 830, "leaf_spki": "ce408b17ac274c6f410b08882802c94bf036582378d2c8d38b49004124eaa78d", "leaf_not_after": "2026-11-04", "leaf_days": 89, "issuer": "CN=YE2,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE2,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.google.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 888, "leaf_tbs_len": 797, "leaf_spki": "ffef423b6e6444f0cc53446e504d81e529a11594cc4f9dc374f18d6495dad2fb", "leaf_not_after": "2026-12-11", "leaf_days": 83, "issuer": "CN=WE2,O=Google Trust Services,C=US", "chain_len": 3, "int_subject": "CN=WE2,O=Google Trust Services,C=US", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "be1efc292835472e0d6aa183575d30fdc4dbf551f7050519a0258d6bddd6fc46", "int_not_after": "2029-02-20", "leaf_by_int": true} +{"host": "ietf.org", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 892, "leaf_tbs_len": 802, "leaf_spki": "7b2a4661c30a69684199b40bc5d4019b09295269f7c9b4d8ab2b8a5e95455b22", "leaf_not_after": "2026-12-17", "leaf_days": 90, "issuer": "CN=WE1,O=Google Trust Services,C=US", "chain_len": 3, "int_subject": "CN=WE1,O=Google Trust Services,C=US", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "908769e8d34477cc2cba0632c88605b22d7294c0840f78596d247c645b1afc0e", "int_not_after": "2029-02-20", "leaf_by_int": true} +{"host": "example.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 1002, "leaf_tbs_len": 913, "leaf_spki": "b68c8c1a989386ea15693ca0bba41e09c9ee9b63ca2649e718eb076a51fb0735", "leaf_not_after": "2026-12-25", "leaf_days": 90, "issuer": "CN=Cloudflare TLS Issuing ECC CA 3,O=SSL Corporation,C=US", "chain_len": 4, "int_subject": "CN=Cloudflare TLS Issuing ECC CA 3,O=SSL Corporation,C=US", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "e38be21734c2fa1fcbfb7387460e11b39bf8f80729cc766f23d4e77b64433469", "int_not_after": "2035-05-27", "leaf_by_int": true} +{"host": "news.ycombinator.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 922, "leaf_tbs_len": 799, "leaf_spki": "7cd959cfca15d77283dc2b766e4b957414ff3af98ea9c3e8ab84efa44a41e25c", "leaf_not_after": "2026-12-19", "leaf_days": 89, "issuer": "CN=YE1,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE1,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "6ebcefb4210b088654a38b03fea3d7d1c711b4fb1ddc363a45f9b1a4e53da01e", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.cloudflare.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 923, "leaf_tbs_len": 833, "leaf_spki": "1e15aa6dd2d03cad4553aea8511d55f450c31dba1e864f184c1b12e5d568218d", "leaf_not_after": "2026-12-15", "leaf_days": 90, "issuer": "CN=WE1,O=Google Trust Services,C=US", "chain_len": 3, "int_subject": "CN=WE1,O=Google Trust Services,C=US", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "908769e8d34477cc2cba0632c88605b22d7294c0840f78596d247c645b1afc0e", "int_not_after": "2029-02-20", "leaf_by_int": true} +{"host": "www.youtube.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 1942, "leaf_tbs_len": 1852, "leaf_spki": "a5931ac636d44af062e630c7c0e90feedd253e488a3e9f620a580ea53f9fb975", "leaf_not_after": "2026-12-11", "leaf_days": 83, "issuer": "CN=WE2,O=Google Trust Services,C=US", "chain_len": 3, "int_subject": "CN=WE2,O=Google Trust Services,C=US", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "be1efc292835472e0d6aa183575d30fdc4dbf551f7050519a0258d6bddd6fc46", "int_not_after": "2029-02-20", "leaf_by_int": true} +{"host": "www.facebook.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1707, "leaf_tbs_len": 1427, "leaf_spki": "defc87658bb4297f5c06598ce3d5ade69214d3f5549caf1de1203cd09df56bd0", "leaf_not_after": "2026-10-09", "leaf_days": 90, "issuer": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "chain_len": 2, "int_subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "59e738e674221702af1edb87c5200c1a4b75f64fae3d2c3d265124c61bd83c79", "int_not_after": "2031-03-29", "leaf_by_int": true} +{"host": "www.amazon.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1931, "leaf_tbs_len": 1651, "leaf_spki": "16a25380e6df43e0af92daa91854722963fe9bd9f3eacb56a86d2f676a6e2e90", "leaf_not_after": "2027-03-21", "leaf_days": 197, "issuer": "CN=GeoTrust TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", "chain_len": 3, "int_subject": "CN=GeoTrust TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "4831b9a2b12ff225fa30d7a7200c5af2740536944e07febe965b197571d936ab", "int_not_after": "2027-11-02", "leaf_by_int": true} +{"host": "www.microsoft.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha384WithRSAEncryption", "leaf_der_len": 2478, "leaf_tbs_len": 1942, "leaf_spki": "dfd1a06be3dbe1fea78817bd8a9afdab57829819abf4c2bae78fdb34daae6193", "leaf_not_after": "2027-01-17", "leaf_days": 360, "issuer": "CN=Microsoft TLS G2 RSA CA OCSP 04,O=Microsoft Corporation,C=US", "chain_len": 3, "int_subject": "CN=Microsoft TLS G2 RSA CA OCSP 04,O=Microsoft Corporation,C=US", "int_key": "RSA-4096", "int_sigalg": "sha384WithRSAEncryption", "int_spki": "0a071daa9e2d5b0696ff9cd54f44b33372556abc11f6a673680a63805a9e5627", "int_not_after": "2029-06-03", "leaf_by_int": true} +{"host": "www.apple.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1917, "leaf_tbs_len": 1637, "leaf_spki": "6382171bc16bd2eb16448906aa7c37d303755bb69104d55b9e18b3bd0dad9572", "leaf_not_after": "2026-12-16", "leaf_days": 166, "issuer": "CN=Apple Public EV Server RSA CA 1 - G1,O=Apple Inc.,C=US", "chain_len": 2, "int_subject": "CN=Apple Public EV Server RSA CA 1 - G1,O=Apple Inc.,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "f42ee67f827bf3d2af2d7e7d95c332629b07e9ba5d9a80064c1c9936172eb0b0", "int_not_after": "2030-04-10", "leaf_by_int": true} +{"host": "www.reddit.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1753, "leaf_tbs_len": 1473, "leaf_spki": "7a470157af55fa75a3d9abe02f72a8e98510d8e4c621659c4ccb35e20ec9b0c7", "leaf_not_after": "2027-02-16", "leaf_days": 179, "issuer": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "chain_len": 2, "int_subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "59e738e674221702af1edb87c5200c1a4b75f64fae3d2c3d265124c61bd83c79", "int_not_after": "2031-03-29", "leaf_by_int": true} +{"host": "x.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 1, "c64_reachable": false, "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1340, "leaf_tbs_len": 1060, "leaf_spki": "360e0edb0cdb8ae022037e91bd70c66f87081939950a3b7b626be7dd2082feec", "leaf_not_after": "2026-12-27", "leaf_days": 89, "issuer": "CN=YR2,O=Let's Encrypt,C=US", "chain_len": 3, "int_subject": "CN=YR2,O=Let's Encrypt,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "9d637b3d27a9e570d07607b9ccadb80a70915c7af72afce12841b1b1da825fd1", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.linkedin.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 1, "c64_reachable": false, "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 2426, "leaf_tbs_len": 2146, "leaf_spki": "91fea367cb0e77d2af5be014f3234a6098b0d24e470cd26a5df0b358f3747558", "leaf_not_after": "2027-03-17", "leaf_days": 181, "issuer": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "chain_len": 3, "int_subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "59e738e674221702af1edb87c5200c1a4b75f64fae3d2c3d265124c61bd83c79", "int_not_after": "2031-03-29", "leaf_by_int": true} +{"host": "www.netflix.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1670, "leaf_tbs_len": 1548, "leaf_spki": "470d5506d84fb12b3bc837309f2b5e67d50c39bb7f9161370d5685573cd150e4", "leaf_not_after": "2027-02-18", "leaf_days": 365, "issuer": "CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", "chain_len": 2, "int_subject": "CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "a814636663a69123492f4a7bd337a4ee8752233aacfe6b91e0993dc58c823fe1", "int_not_after": "2031-04-13", "leaf_by_int": true} +{"host": "stackoverflow.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 938, "leaf_tbs_len": 816, "leaf_spki": "b611c0c9e2d08f2f5d45d318fef3cfc5283174b187520a1251346ab7750c38c2", "leaf_not_after": "2026-11-13", "leaf_days": 89, "issuer": "CN=YE1,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE1,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "6ebcefb4210b088654a38b03fea3d7d1c711b4fb1ddc363a45f9b1a4e53da01e", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.bbc.co.uk", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1997, "leaf_tbs_len": 1717, "leaf_spki": "7f48066ac28dfcc475f942dd46d0cfad017fa5c60ae46bfdb654a483d3b31362", "leaf_not_after": "2027-01-24", "leaf_days": 155, "issuer": "CN=GlobalSign GCC R46 OV TLS CA 2025,O=GlobalSign nv-sa,C=BE", "chain_len": 3, "int_subject": "CN=GlobalSign GCC R46 OV TLS CA 2025,O=GlobalSign nv-sa,C=BE", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "9d9e10b16c62bc172eb85908f1d5e07dad0f6f6a35b2367c84ac7a87eef6f71c", "int_not_after": "2029-06-23", "leaf_by_int": true} +{"host": "www.nytimes.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 2092, "leaf_tbs_len": 1812, "leaf_spki": "d397d2c0aeca2afd54d4f1a8fbd7d7f6364057c646ced1f0f758a10e75ac5430", "leaf_not_after": "2027-03-19", "leaf_days": 198, "issuer": "CN=Thawte TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", "chain_len": 2, "int_subject": "CN=Thawte TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "e366fd44d3a7c9bded942d0a62d34f03728aa4996eb24c94e9a1be0a2a5499a3", "int_not_after": "2027-11-02", "leaf_by_int": true} +{"host": "www.theguardian.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 2581, "leaf_tbs_len": 2045, "leaf_spki": "7a9fd406390b23b6122372273732a23c3f62190277792a778b9f63b797db18d1", "leaf_not_after": "2027-03-05", "leaf_days": 198, "issuer": "CN=GlobalSign Atlas R46 DV TLS CA 2026 Q3,O=GlobalSign nv-sa,C=BE", "chain_len": 3, "int_subject": "CN=GlobalSign Atlas R46 DV TLS CA 2026 Q3,O=GlobalSign nv-sa,C=BE", "int_key": "RSA-4096", "int_sigalg": "sha384WithRSAEncryption", "int_spki": "7305985bb5b15c3c946902a96af48d9e6a73d1b7b3bc116060ecb296a2696337", "int_not_after": "2028-04-22", "leaf_by_int": true} +{"host": "archive.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1681, "leaf_tbs_len": 1401, "leaf_spki": "72be73116fa44e8638b348117f1f35a95d21240558d5ec4ee7c8c61436aabe84", "leaf_not_after": "2027-02-09", "leaf_days": 397, "issuer": "CN=Go Daddy Secure Certificate Authority - G2,OU=http://certs.godaddy.com/repository/,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US", "chain_len": 4, "int_subject": "CN=Go Daddy Secure Certificate Authority - G2,OU=http://certs.godaddy.com/repository/,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "f11c3dd048f74edb7c45192b83e5980d2f67ec84b4ddb9396e33ff5173ed698f", "int_not_after": "2031-05-03", "leaf_by_int": true} +{"host": "www.mozilla.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1276, "leaf_tbs_len": 996, "leaf_spki": "c91089b5a0fb5b7527b9a2b31ab40314c99a801aeb06270844f497ee955e7829", "leaf_not_after": "2026-11-20", "leaf_days": 89, "issuer": "CN=YR1,O=Let's Encrypt,C=US", "chain_len": 3, "int_subject": "CN=YR1,O=Let's Encrypt,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "2e8307068b6db620e4a39d068b5dee5d6ef5788cbb2c0b6d23ead84fcc17178c", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.python.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1670, "leaf_tbs_len": 1390, "leaf_spki": "01e69070bdffa7de1fa20b8759307c7b313d4162fa3c3e906396a5b99edbb8a0", "leaf_not_after": "2027-02-14", "leaf_days": 396, "issuer": "CN=GlobalSign Atlas R3 DV TLS CA 2025 Q4,O=GlobalSign nv-sa,C=BE", "chain_len": 2, "int_subject": "CN=GlobalSign Atlas R3 DV TLS CA 2025 Q4,O=GlobalSign nv-sa,C=BE", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "6e221c831270ec73354db749c62a0d52d5d42f80c018fdefd5b2e25e5409c47c", "int_not_after": "2027-07-16", "leaf_by_int": true} +{"host": "pypi.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1666, "leaf_tbs_len": 1386, "leaf_spki": "723163c85973bfe28bfa03cb016d7db4e1683edd42de808dce1cb94e433f48b0", "leaf_not_after": "2027-01-29", "leaf_days": 396, "issuer": "CN=GlobalSign Atlas R3 DV TLS CA 2025 Q4,O=GlobalSign nv-sa,C=BE", "chain_len": 2, "int_subject": "CN=GlobalSign Atlas R3 DV TLS CA 2025 Q4,O=GlobalSign nv-sa,C=BE", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "6e221c831270ec73354db749c62a0d52d5d42f80c018fdefd5b2e25e5409c47c", "int_not_after": "2027-07-16", "leaf_by_int": true} +{"host": "www.debian.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-4096", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1527, "leaf_tbs_len": 1247, "leaf_spki": "a48e668619fca9ff7d9489d37d22c09a23aeacd7053e44c914ab7bd869127bbb", "leaf_not_after": "2026-10-31", "leaf_days": 89, "issuer": "CN=YR1,O=Let's Encrypt,C=US", "chain_len": 3, "int_subject": "CN=YR1,O=Let's Encrypt,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "2e8307068b6db620e4a39d068b5dee5d6ef5788cbb2c0b6d23ead84fcc17178c", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "kernel.org", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1066, "leaf_tbs_len": 944, "leaf_spki": "356a563421a5201bf609339f2940afa6a143dceaf940f893dafd38a3bbad3767", "leaf_not_after": "2026-11-12", "leaf_days": 89, "issuer": "CN=YE1,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE1,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "6ebcefb4210b088654a38b03fea3d7d1c711b4fb1ddc363a45f9b1a4e53da01e", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "letsencrypt.org", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1098, "leaf_tbs_len": 976, "leaf_spki": "c7c96daa1fb334f606ed1652f888c5ca69dda32e980162cf692929b6a79bcdf7", "leaf_not_after": "2026-12-03", "leaf_days": 89, "issuer": "CN=YE2,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE2,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.eff.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)"} +{"host": "duckduckgo.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1777, "leaf_tbs_len": 1497, "leaf_spki": "df53bbfab573879b33b3ece511fb2b5940f52db3202bde3af4d472c85cb15a9d", "leaf_not_after": "2026-11-19", "leaf_days": 198, "issuer": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "chain_len": 3, "int_subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "59e738e674221702af1edb87c5200c1a4b75f64fae3d2c3d265124c61bd83c79", "int_not_after": "2031-03-29", "leaf_by_int": true} +{"host": "www.bing.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1926, "leaf_tbs_len": 1803, "leaf_spki": "e67ae52839261a3716f692602770769e3a44faadfcfad93885b1d6ca4074b134", "leaf_not_after": "2027-04-03", "leaf_days": 198, "issuer": "CN=Microsoft TLS G2 ECC CA OCSP 02,O=Microsoft Corporation,C=US", "chain_len": 3, "int_subject": "CN=Microsoft TLS G2 ECC CA OCSP 02,O=Microsoft Corporation,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "25fa9b443b06549afbcad74632396341047fa433f804f8334ccc7d10237616a3", "int_not_after": "2029-06-03", "leaf_by_int": true} +{"host": "www.instagram.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1632, "leaf_tbs_len": 1352, "leaf_spki": "d2678d974ce2e4b6fc1c1d33e729c822c89d892db31a18d660b8933ef9a68576", "leaf_not_after": "2026-10-09", "leaf_days": 90, "issuer": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "chain_len": 2, "int_subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "59e738e674221702af1edb87c5200c1a4b75f64fae3d2c3d265124c61bd83c79", "int_not_after": "2031-03-29", "leaf_by_int": true} +{"host": "gitlab.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 1, "c64_reachable": false, "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1747, "leaf_tbs_len": 1339, "leaf_spki": "ef091dd1852fc8c885fc7ef823b14b19855c22c6fc7850e18c0f00aaad12dbe5", "leaf_not_after": "2026-11-10", "leaf_days": 198, "issuer": "CN=Sectigo Public Server Authentication CA DV R36,O=Sectigo Limited,C=GB", "chain_len": 4, "int_subject": "CN=Sectigo Public Server Authentication CA DV R36,O=Sectigo Limited,C=GB", "int_key": "RSA-3072", "int_sigalg": "sha384WithRSAEncryption", "int_spki": "6bd9212ce649c659c9cabc6cb60fcffac7a20c29be61fdceb2b5f2168701688d", "int_not_after": "2036-03-21", "leaf_by_int": true} +{"host": "sourceforge.net", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 957, "leaf_tbs_len": 835, "leaf_spki": "33f6f77f6fe9019c74fa8bf2b25b4889f2a1c89d636423402e6f8e6119ac27fd", "leaf_not_after": "2026-11-10", "leaf_days": 89, "issuer": "CN=YE2,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE2,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.c64-wiki.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)"} +{"host": "csdb.dk", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)"} +{"host": "lemon64.com", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA256", "leaf_der_len": 896, "leaf_tbs_len": 807, "leaf_spki": "af49b455a7f2e37cefb2d12a33dccb15ed8bac6faa524ef29dc13f075e7742b8", "leaf_not_after": "2026-12-12", "leaf_days": 90, "issuer": "CN=WE1,O=Google Trust Services,C=US", "chain_len": 3, "int_subject": "CN=WE1,O=Google Trust Services,C=US", "int_key": "EC-secp256r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "908769e8d34477cc2cba0632c88605b22d7294c0840f78596d247c645b1afc0e", "int_not_after": "2029-02-20", "leaf_by_int": true} +{"host": "arstechnica.com", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 0, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)"} +{"host": "slashdot.org", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 938, "leaf_tbs_len": 817, "leaf_spki": "2f384aede304e9cb0ce801190749abae51b4374bd50ae6c1c1c7ee5661887a7b", "leaf_not_after": "2026-11-10", "leaf_days": 89, "issuer": "CN=YE1,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE1,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "6ebcefb4210b088654a38b03fea3d7d1c711b4fb1ddc363a45f9b1a4e53da01e", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.wikipedia.org", "verify": "Verify return code: 0 (ok)", "peer_sig": "ecdsa_secp256r1_sha256", "cipher": "TLS_CHACHA20_POLY1305_SHA256", "rc": 0, "c64_reachable": true, "leaf_key": "EC-secp256r1", "leaf_sigalg": "ecdsa-with-SHA384", "leaf_der_len": 1636, "leaf_tbs_len": 1514, "leaf_spki": "c0a0573a0bfb1cc696f3ccb6a0be4e784fb9d887755b3e509a44adc41481c3fe", "leaf_not_after": "2026-11-03", "leaf_days": 89, "issuer": "CN=YE2,O=Let's Encrypt,C=US", "chain_len": 4, "int_subject": "CN=YE2,O=Let's Encrypt,C=US", "int_key": "EC-secp384r1", "int_sigalg": "ecdsa-with-SHA384", "int_spki": "b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "developer.mozilla.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1316, "leaf_tbs_len": 1036, "leaf_spki": "28805f82b4bd80ffe9e776a00b7f388117c4543b92a98c715921bd8fc98fdc9c", "leaf_not_after": "2026-12-22", "leaf_days": 89, "issuer": "CN=YR2,O=Let's Encrypt,C=US", "chain_len": 3, "int_subject": "CN=YR2,O=Let's Encrypt,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "9d637b3d27a9e570d07607b9ccadb80a70915c7af72afce12841b1b1da825fd1", "int_not_after": "2028-09-02", "leaf_by_int": true} +{"host": "www.gnu.org", "verify": "Verify return code: 0 (ok)", "peer_sig": null, "cipher": null, "rc": 1, "c64_reachable": false, "verify_default": "Verify return code: 0 (ok)", "leaf_key": "RSA-2048", "leaf_sigalg": "sha256WithRSAEncryption", "leaf_der_len": 1871, "leaf_tbs_len": 1591, "leaf_spki": "0a8fa025f0ebde1e6a5601cd23ef18e9d8ca5ee15b926bf8feba9c895de0bf90", "leaf_not_after": "2026-12-06", "leaf_days": 89, "issuer": "CN=YR1,O=Let's Encrypt,C=US", "chain_len": 3, "int_subject": "CN=YR1,O=Let's Encrypt,C=US", "int_key": "RSA-2048", "int_sigalg": "sha256WithRSAEncryption", "int_spki": "2e8307068b6db620e4a39d068b5dee5d6ef5788cbb2c0b6d23ead84fcc17178c", "int_not_after": "2028-09-02", "leaf_by_int": true} diff --git a/tools/trust_bundle.py b/tools/trust_bundle.py index 52de486..f2458db 100644 --- a/tools/trust_bundle.py +++ b/tools/trust_bundle.py @@ -3,7 +3,8 @@ A trust bundle is a separate file the C64 loads (from disk, or into the REU) and checks with the P-256 `ecdsa_verify_256` it already links. The public key -and a generation floor are compiled into the PRG (DECISIONS.md item 4). This +and a generation floor are compiled into the PRG (docs/155-phase2/DECISIONS.md +item 4; the DECISIONS cites in the tree all name that file). This tool is the host half only; loading and verifying on the C64 is not here. Byte format, version 1. Multi-byte integers are LITTLE-endian unless marked