Repository navigation
PQ P3: sectioned ClientHello (16-bit lengths) + HelloRetryRequest refusal - #312
Merged
Merged
Conversation
…Request (PQ P3) Table-driven ClientHello builder, optional bulk key-share section streamed as one record (UCI only, CH_SECTIONED), per-section transcript folds, and tls_sh_err=1 for an HRR-magic ServerHello random. X25519-only wire bytes are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…heck to LOADER, arming contract, comment/CLAUDE.md fixes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…AUDE.md fixes 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.
PQ hybrid, phase P3: sectioned ClientHello + HelloRetryRequest refusal
No ML-KEM dependency. Default (X25519-only) ClientHello bytes are unchanged.
What changed
tls_build_client_hellois table-driven (length-prefixed templates inch_tmpl, CODE) and stages head+tail intls_rec_buf. Real 16-bit extensions/handshake/record lengths.tls_ch_ks_ptr/len(16-bit; BSS zeroed at boot; cleared by every send, success or failure) +tls_ch_split.tls_ch_send_sectionssends record header, head, bulk (e.g. the 1,184 B ek fromcert_buf), tail, folding each into the transcript in wire order. With a bulk section the hello offers groups11EC,001Dand key_share11EC(bulk||x25519)+001D(x25519). Length 0 (every caller today) = classic X25519-only hello.CH_SECTIONED(src/constants.inc, followsTLS_STREAM_DEFRAME, i.e. UCI). Not a Makefile knob. ip65 keeps the table-driven X25519-only builder (no sectioned sender).tls_sh_chk_hrr(called fromtls_parse_server_hello, both backends) refuses a ServerHello whose random is SHA-256("HelloRetryRequest"): C=1,tls_sh_err = $01.tls_sh_erris cleared at parse entry; it is diagnostics-only and can go stale across a connection that fails before SH parse. On ip65 the check lives in CODE (LOADER), on UCI in TLS_CODE.tls_recv_server_hellois single-record; servers fragment it under MFL=512). That is the next phase.Record framing decision
One TLS record carries the whole (up to ~1.4 KB) ClientHello. RFC 8446 5.1 permits fragmenting handshake messages, but the CH precedes max_fragment_length negotiation (MFL is ext 0x0001, RFC 6066 4; it binds records only after the server echoes it), so the 2^14 default applies and a single record is legal; it also avoids needing reassembly in the peer.
Arming contract (S3)
Caller sets
tls_ch_ks_ptr/lenimmediately beforejsr tls_connect, after DNS/TCP connect succeed.tls_connectreaches the sender with no branch in between and the sectioned sender clears the length on every exit, so nothing stays armed past one ClientHello. I did not clear attls_connectentry: the caller arms before the call, so an entry clear would discard a fresh arm; and a failure beforetls_connectis never reached cannot be fixed from inside it. Test [2k] fails each of the four sends in turn (k=1..4) and asserts C=1 andtls_ch_ks_len=0.P4 will move ML-KEM KeyGen + arming inside
tls_connect(betweentls_ecdh_generate_keypairandtls_send_client_hello), retiring the caller contract and using the per-connection DRBG reseed.Byte identity (default builds)
tools/test_tls_handshake.py[2d-2f] capture the wire via a recordingnet_tcp_sendstub (C64_CH_DUMP=<dir>writes it). sha256 identical master vs branch on both backends:The adversarial review's 64-host-length sweep (host lengths 0..63 -> 64 cases) was byte-identical to master on uci, ip65 and uci-onchip-store: 64/64 each.
PRG hashes (default
make, frommeasure_margins.py --build, head of this PR)Margins (tail free, B; master -> branch)
--build: scratchpadmargins_before.txt/margins_after2.txt. The only region that got worse is UCI NET_CODE (-197: sectioned sender + HRR check); ip65 CRYPTO_OVERLAY is unchanged (HRR check moved to LOADER on ip65).Tests (red/green)
New in
test_tls_handshake.py: [2d-2k] ClientHello wire vs an independent Python reference through a recording net_tcp_send (classic x3; sectioned bulk=1184/448/182/40, no-SNI, 63-char SNI; bulk at the non-page-aligned$C433, notcert_buf; each of the 4 sends failing in turn clearstls_ch_ks_len; checks 16-bit lengths, 4 sends, bulk sent from its own address, transcript == sha256 of the message); [3d] HRR magic -> C=1/err=1, byte-31 near-miss, byte-0 near-miss and a normal random -> C=0/err=0.--seed 7): 24/26, FAILlabel 'tls_ch_ks_ptr' missingand FAILlabel tls_sh_err missing; ip65 master 24/25 (HRR). The red is missing labels, not wrong bytes.bpl :-->bne :-intls_sh_chk_hrr(skips byte 0): 3dFAIL: (C,err) near-miss0=(1, 1), suite 32/33.lda #$00 / ldx #$42for the bulk pointer intls_ch_send_sections: 2jFAIL: bulk not sent from its own buffer, suite 32/33.tls_handshake35/36):ptr_lo_hardcoded(lda #$00 / ldx tls_ch_ks_ptr+1): [2j]wire != reference ... bulk not sent from its own buffer(bulk at$C433).failclear_head_skip(return without clearing when send Implement TLS 1.3 record layer #2 fails): [2k]FAIL: carry=1 sends=2 ks_len after=a004.failclear_bulk_skip(same for send Implement TLS 1.3 handshake with x25519 ECDH #3): [2k]FAIL: carry=1 sends=3 ks_len after=a004.BACKEND=uci python3 tools/run_all_tests.py:TOTAL: 455/455 passed, 0 failed -- 2 suite(s) SKIPPED: net, ip65_exports.python3 tools/run_all_tests.py(first round, before this fixup):TOTAL: 443/443 passed, 0 failed -- 1 suite(s) SKIPPED: hs_sequence; re-run this round:test_tls_handshake.pyip65 25/25 only.OpenSSL interop (adversarial review, OpenSSL 3.6.5)
The hybrid CH (1364 B record, 11EC entry 1216 B) drew: an
11ecServerHello (key_exchange 1120 = ct 1088 + 32) for groups X25519MLKEM768 (and both orderings with X25519); a001dServerHello under X25519 only; ahandshake_failurealert under P-256 only.Hardware (U64E 10.43.23.81, 48 MHz, via DeviceLock)
rig_https_local.py, uci-onchip +HTTPS_SNI=www.foo.invalid: PASS on this head, PRG sha256bb323255875629e0b587b0f7ef042e856348806ce11e908b554f28135983341c(round 1, previous head:3a519db6...PASS).rig_https_live.pyagainst github.com, ALL runs: (1) round 1 PR build with theHTTPS_SNI=www.foo.invalidPRG by my mistake (rig_live.txt 20:45): FAILED in Certificate,tls_state=$FF,tls_last_state=$04,http_status=0(name mismatch from the override, not the code); (2) PR build24477609without override: HTTP 200 + carry=1; (3, 4) the adversary re-ran24477609twice: HTTP 200 + carry=1 each. Tally: 3 x HTTP200/carry=1, 1 x CERT fail. Unmodified master (5e709d88) also gives HTTP200/carry=1 on github.com (body truncation, rig_https_live against github.com fails ~1 run in 3 on the U64E, on master as well as on branches #147/http_get reports success (carry=0, HTTP 200) on a truncated body — silent data loss #211). The live rig prints no PRG hash (it links via label addresses), so those hashes are from my build step, not rig output.tls_last_state=$06,net_last_error=$87).bb323255...), so no rig was re-run.Not in this PR
Only X25519 is offered by any shipped code path; nothing sets
tls_ch_ks_len. The sectioned path is exercised only by tests. ServerHello with 11EC and the key schedule are separate phases.🤖 Generated with Claude Code