Repository navigation
UCI trust policy: build pin > signed bundle > TOFU (#155 phase 2, L3) - #268
Merged
Merged
Conversation
JC-000
force-pushed
the
feat/p2-l3-policy
branch
from
October 1, 2026 19:43
f29f264 to
04af8b9
Compare
JC-000
force-pushed
the
feat/p2-l3-policy
branch
from
October 6, 2026 02:35
04af8b9 to
d1bb592
Compare
The trust store (L2), the cold bank (L5) and the bundle format (L4) get their policy, per DECISIONS 3, 4, 7, 10 and 11: * src/cert_pin.s becomes the one resident hook under TRUST_STORE=1: the same SPKI-window hash and #152 interlock, compared per tp_mode with the store's record (KEY CHANGED refuses unless the operator armed exactly this 32 B hash), nothing (first use, UNPINNED), or the build pin. A connection trust_pre never prepared is refused. Without TRUST_STORE every byte is unchanged (ip65 and every non-trust UCI build: identical PRG hashes to master). * src/net/uci/trust_policy.s: trust_pre before the dial (build pin only for HTTPS_HOST; TRUST STORE EMPTY on every GET; store FAIL asks CONTINUE UNPINNED? Y/N) and trust_post after the close (records first use and accepted keys; KEY CHANGED shows the full hash and offers ACCEPT ON RETRY / CONTINUE UNPINNED; BUNDLE MISMATCH asks RECORD KEY?). Every keyboard read is there, so none can happen with a socket open. * src/net/uci/trust_bundle.s (TRUST_BUNDLE=1): TRUST.P from the store's directory, structure -> signature -> generation floor, verified once per boot with a cached digest; advisory leaf pins. * comb: the policy rides the cold bank's TRUST group, the bundle check its UI group; COLD_BANK=0 with TRUST_STORE=1 is now a build error. Accept-and-retry is a second handshake in one boot: it needs fix/tls-reconnect-state (per-connection TLS reset), which merges first. Tests: tools/test_trust_policy_6502.py (the shipped do_https_get on the 6502 interpreter, both profiles), tools/mutate_trust_policy.py (18 mutants), tools/test_trust_bundle_policy_c64.py (real ECDSA in VICE), tools/uci/rig_trust_policy.py (U64E). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…K_CODE) 1. MEDIUM-HIGH. The hook stored tp_bwarn from A after a failed tp_cmp_got, and A is then the bundle pin's byte where the hashes first differ (from byte 31 down). A $00 there left tp_bwarn clear: BUNDLE MISMATCH printed, RECORD KEY? was never asked, and trust_post recorded the server's key as TOFU -- free when the pin's byte 31 is $00, ~2^8 / 2^16 grinds for byte 30 / 29. Now `lda #1`. Regression: pins with byte 31 = $00, and byte 30 = $00 with 31 equal (red on 04af8b9: recorded without A); mutant. Audit: tp_bwarn was the only flag stored from an incidental A; the other stores are explicit, or clear (the safe way). 2. LOW. TRUST_POLICY_BSS is not boot-zeroed, so an accept armed before 'Q' survived RUN (or a reset and SYS). start now calls trust_state_init, which clears it and the bundle cache. Test + mutant (drop the call). 3. LOW, comb. A cold-bank refusal of trust_post (socket ERROR, latched DMA timeout) skipped its clears, keeping tp_ovr_armed and tp_mode. The resident stubs now clear both whenever the bank itself refuses. Red on 04af8b9 (armed=1 mode=1 survived); mutant. Under TRUST_STORE the hook's code is TRUST_HOOK_CODE, not CERT_PIN_CODE: the comb cfgs place it in NET_CODE (CRYPTO_HOT has no room for it beside #273's body-sink bound), the uci cfg where CERT_PIN_CODE already was. The plain pin keeps CERT_PIN_CODE, so non-trust pinned builds do not move. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On master + the queued PRs, the comb products overflowed LOADER (the prompts) and NET_CODE (the hook). Placement only, no code change: - comb: TRUST_PROMPT_CODE rides the cold TRUST group (only the policy, which runs there, calls it); LOADER_OVERFLOW moves to CRYPTO_HOT and TRUST_POLICY_BSS to NET_CODE, so the hook keeps NET_CODE on the Wikipedia demo, whose CRYPTO_HOT is nearly full. - TRUST_BUNDLE=1 fills the TRUST group and grows the state: the source switches both to TRUST_PROMPT_RES / TRUST_POLICY_BSS_RES (comb: CRYPTO_HOT). - uci: TRUST_PROMPT_CODE (and _RES) LOADER -> CRYPTO_OVERLAY, which the onchip bundle images need. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…RE (adv-d2 F1) D2 moved LOADER_OVERFLOW to CRYPTO_HOT on every cold-bank comb build, so images without the trust store changed for no gain, and the comb demo with HTTPS_PIN_SPKI (no store) stopped linking (CRYPTO_HOT -56). Under TRUST_STORE the three tenants now name it LOADER_OVERFLOW_TS (src/loader_overflow.inc, the _RES pattern): the comb cold cfg puts that in CRYPTO_HOT, the uci cfg beside LOADER_OVERFLOW in NET_CODE, and every image without the store keeps its pre-D2 placement and bytes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nto the cold prompt goes red (adv-d2 F2) _tls_connect wrote only the 91 B leaf window, so the TRUST group's copy in cert_buf survived a "handshake" and a resident JSR into the cold prompt still ran. A real flight fills cert_buf: the model now fills all 2048 B ($02, a JAM) first. New mutant: the resident trust_post stub calls tp_unpinned_banner (comb), killed by sc_bank_refuses_post. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dv-d2 F3) In VICE the trust store read fails (no UCI), so trust_pre asks CONTINUE UNPINNED? Y/N before the dial, after flushing the key buffer; the suite never answered and hung on every shipped TRUST_STORE image. On those images do_https_get now runs until it returns or parks on the question, types Y (so every accepted target still reaches the dial stub), and checks that it was asked; a refused target must never be asked. One more case answers N: NOT DIALLED, nothing dialled. call() is untouched (#283). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RIG_MODE=live streams the ~750 KB Wikipedia demo body; the shared 240 s budget expired mid-body at 48 MHz (U64E fw 3.15 3a1ff9ff, PRG 1baa43a9: KEY RECORDED never shown, 1/4). With FETCH_TIMEOUT=900 the same PRG passed 4/4. Local mode keeps 240 s; FETCH_TIMEOUT still overrides both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000
force-pushed
the
feat/p2-l3-policy
branch
from
October 6, 2026 17:39
d1bb592 to
011209b
Compare
JC-000
added a commit
that referenced
this pull request
Oct 6, 2026
#211 is closed: the stretch-goal bullet, the wikipedia-stall bullet and the rig list no longer describe it as open (no replacement claim). "There is no trust store, no root CAs" is false for the UCI images (#268/#269) and the M3 demo; the chain-validation bullet keeps only the issuer skip. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#155 phase 2, lane L3: the trust policy on UCI builds (
TRUST_STORE=1). Binding inputs:phase2-spikes/DECISIONS.md(all items; item 11 governs) andl3/L3-design.md.Merge order: #268 → #269. Rebased onto master
c26e268(#273 and #279-#286 merged).Behaviour
Precedence. The build pin applies only when the typed host equals
HTTPS_HOST(Q2). Then comes the signed bundle (advisory), then TOFU.One resident hook.
src/cert_pin.sserves as the trust hook underTRUST_STORE:tp_mode:trust_prenever prepared is refused.trust_preruns before the dial:TRUST STORE EMPTYis printed on every GET while the store is empty.TRUST FAIL nn, thenCONTINUE UNPINNED? Y/N(Q5).NOT DIALLED.trust_postruns after the close, on every exit after the dial:ACCEPT ON RETRY? A=YES. Otherwise it asksCONTINUE UNPINNED? Y/N.RECORD KEY? A=YES(Q3).$C6first.Bundle (
TRUST_BUNDLE=1, off in packages).TRUST_STORE_DIR/TRUST.P.tls_transcript_*before the dial.adv-268 fixes (
4414bae).tp_bwarnis now set explicitly (MEDIUM-HIGH: a mismatched pin byte of$00recorded silently).trust_state_initruns atstart.TRUST_HOOK_CODE.Placement (D2
3ceabfc+ adv-d2 F1affb456)On master plus #279-#282, the comb products overflowed LOADER (the prompts) and NET_CODE (the hook). The fix is placement only; no code changes.
TRUST_PROMPT_CODErides the cold TRUST group: onlyTRUST_POLICY_CODE, which runs there, calls it.TRUST_POLICY_BSS) moves to NET_CODE.LOADER_OVERFLOWtenants (boot/vic/crypto_swap) move to CRYPTO_HOT, underTRUST_STOREonly. They go throughsrc/loader_overflow.inc, which names the segmentLOADER_OVERFLOW_TS.TRUST_BUNDLE=1fills the TRUST group and grows the state, so two.ifdef TRUST_BUNDLEdirectives switch them toTRUST_PROMPT_RES/TRUST_POLICY_BSS_RES, placed in CRYPTO_HOT.TRUST_PROMPT_CODEand_RESmove from LOADER to CRYPTO_OVERLAY.LOADER_OVERFLOW_TSsits besideLOADER_OVERFLOWin NET_CODE.COLD_BANK=0+TRUST_STORE=1is still an$(error).Byte identity (master
c26e268+ this + #2698fdebe7; every hash below re-measured there, identical to the pre-rebase trial113415a)Every image without the trust store is byte-identical to the same stack without this PR:
0046561b1061e8ff26e032dd0f949c33HTTPS_PIN_SPKI5835f7c20a0b7a7878a10748adv-d2 F1: D2 alone had moved
LOADER_OVERFLOWfor every comb image and broke the demo + pin build (HOT -56).affb456restores it.Margins (
tools/measure_margins.py, free bytes, onc26e268+ #268 + #269)TRUST_RELEASE=1)da4d607eTRUST_RELEASE=1)e675010059bbc7bd0c76f1d19e46fd9a8cafb05d58f2214brig_trust_policylocal/live,test_rig_scratchdemo-trust,rig_trust_store) all link.Red-green
tools/test_trust_policy_6502.pyruns the shippeddo_https_geton the 6502 interpreter, with a real Python ECDSA check of the struct the C64 built. Default five images: 432/432.a0f53fa), a simulated handshake fills all 2048 B ofcert_buf, as a real flight does. A resident call into the cold prompt now runs$02.tools/mutate_trust_policy.py: 22/22 killed. That includes the new "resident stub calls into the cold TRUST group" mutant, which survived beforea0f53fa.tools/test_typed_target.pyon TRUST_STORE images (adv-d2 F3,d1bb592). The suite answersCONTINUE UNPINNED?when it appears: Y for accepted targets, and refused targets must never be asked. One case answers N and expectsNOT DIALLED.tools/test_trust_bundle_policy_c64.py(VICE, real 6502 SHA/ECDSA): good, cached, flipped record, wrong key and replay, 5/5.test_cold_bank118/118 (its cfg mirror includes the D2 edits),test_trust_store_6502128/128 on onchip and comb,test_reu_body_sink76/76,test_quit_basic_exitcomb + TS 68/68.LOADER_OVERFLOW_TSin HOT:test_ecdsa_kat_oraclecomb + TS 6/6.test_ip65_hw_checks_unit, which needs an ip65 build: build state only.Hardware
U64E, fw 3.15
3a1ff9ff, FPGA 126, core 1.50, 48 MHz. Each run went through DeviceLock, thenprepare_deviceandpreflight_reu, usingtools/uci/rig_trust_policy.pyin/USB1/c64https-test. The PRGs were built on8fdebe7, whose image bytes equal this branch's.TRUST_STORE_DIR=/USB1/c64https-test HTTPS_PORT=4433)d825488203f54d79…356f2b13589bd829…1baa43a96bd5f681…C0A0573A…recordedThe live run's first attempt (same PRG) was 1/4. The rig's 240 s fetch budget expired mid-body.
011209bmakes live mode wait 900 s, and the 4/4 run above used that.Deviations
COLD_BANK=0+TRUST_STORE=1is refused at build time.🤖 Generated with Claude Code