nx_tls13_ch_compat_test.nx source
↩ module page · 399 lines · 18930 B
1// nx_tls13_ch_compat_test.nx -- B2-HTTPBIN-CH gate row (browser arc).
2//
3// The B2 defect was "httpbin.org answers our ClientHello with an
4// alert". ROOT CAUSE (oracle-measured 2026-06-10, openssl census):
5// httpbin.org does NOT speak TLS 1.3 at all (-tls1_3 -> no cipher;
6// -tls1_2 -> ECDHE-RSA-AES128-GCM-SHA256, P-256 temp key). Its ELB
7// answers a 1.3-only CH with warning close_notify (AL=1 AD=0). Our
8// CH was never malformed -- the CH-compat suspicion is REFUTED.
9// TLS 1.2 client support = separate named rung (not B2's bar).
10//
11// So this row proves the CH-compat claim with two live legs:
12//
13// LEG A (httpbin census, live-updating): send the PRODUCT dual-
14// share CH to httpbin.org and classify the response:
15// - warning/any close_notify (AD=0) or protocol_version (AD=70)
16// alert, or a TLS 1.2 ServerHello (no supported_versions ext)
17// -> the measured TLS-1.2-only signature -> leg PASS
18// - a TLS 1.3 ServerHello -> httpbin enabled 1.3: the leg now
19// DEMANDS the full handshake to WAIT_CLIENT_FIN
20// - any other alert (decode_error 50, illegal_parameter 47,
21// handshake_failure 40) -> a REAL CH problem -> leg FAIL
22//
23// LEG B (P-256 live proof): secp256r1-ONLY CH to github.com
24// (census: completes TLS 1.3 + P-256 + TLS_AES_128_GCM_SHA256).
25// Server MUST take our P-256 share (GR=23 asserted) and the FULL
26// encrypted handshake walks to WAIT_CLIENT_FIN -- the live E2E
27// proof of rung B4's path against an independent implementation,
28// which dual-share connects can never exercise (servers prefer
29// x25519 when offered).
30//
31// Exit 0 iff BOTH legs pass. Markers: L1=/L2= leg verdicts,
32// AL=/AD= alert level/desc, CS=/GR= server cipher + group pick,
33// RC=/CT=/DV=/MT=/DW=/ST= per-record walk (B1 probe pattern).
34// Prereq: /tmp/mozilla_certdata.txt staged (gate self-stages).
35//
36// expect_exit: 0
37// license_tier: ORIGINAL
38
39import "nx_syscalls.nx"
40import "nx_https_url_for_fetch.nx"
41import "nx_https_url_connect.nx"
42import "nx_x509_trust_store.nx"
43import "nx_trust_store_load_from_certdata.nx"
44import "nx_https_cert_pipeline.nx"
45import "nx_tls13_client_session_recv_sh.nx"
46import "nx_tls13_client_session_recv_hs.nx"
47import "nx_tls13_read_record_from_fd.nx"
48
49func cc_dec(label0: i64, label1: i64, v: i64) -> i64 {
50 let lab: *u8 = sys_mmap(8)
51 lab[0] = label0 as u8; lab[1] = label1 as u8; lab[2] = 0x3D
52 sys_write(1, lab, 3)
53 var av: i64 = v
54 if av < 0 {
55 let neg: *u8 = sys_mmap(8); neg[0] = 0x2D; sys_write(1, neg, 1)
56 av = 0 - av
57 }
58 if av == 0 {
59 let z: *u8 = sys_mmap(8); z[0] = 0x30; sys_write(1, z, 1)
60 }
61 if av > 0 {
62 let buf: *u8 = sys_mmap(32)
63 var pos: i64 = 0
64 var x: i64 = av
65 while x > 0 { buf[pos] = (0x30 + (x % 10)) as u8; x = x / 10; pos = pos + 1 }
66 let out: *u8 = sys_mmap(32)
67 var oi: i64 = 0
68 while oi < pos { out[oi] = buf[pos - 1 - oi]; oi = oi + 1 }
69 sys_write(1, out, pos)
70 }
71 let nl: *u8 = sys_mmap(8); nl[0] = 0x0A; sys_write(1, nl, 1)
72 return 0
73}
74
75// Print CS=/GR= from a ServerHello body; returns chosen group,
76// negative if parse/find failed. Read-only census.
77func cc_sh_group_census(sh_body: *u8, sh_len: i64) -> i64 {
78 let p_lv: *i64 = sys_mmap(16) as *i64
79 let p_ro: *i64 = sys_mmap(16) as *i64
80 let p_cs: *i64 = sys_mmap(16) as *i64
81 let p_eo: *i64 = sys_mmap(16) as *i64
82 let p_el: *i64 = sys_mmap(16) as *i64
83 if tls13_server_hello_parse(sh_body, sh_len, p_lv, p_ro, p_cs, p_eo, p_el) != NX_TLS13_HELLO_VERDICT_OK {
84 return 0 - 1
85 }
86 cc_dec(0x43, 0x53, *p_cs) // CS=
87 let p_off: *i64 = sys_mmap(16) as *i64
88 let p_len: *i64 = sys_mmap(16) as *i64
89 if tls13_ext_find(sh_body + *p_eo, *p_el, EXT_KEY_SHARE, p_off, p_len) != NX_TLS13_HELLO_VERDICT_OK {
90 return 0 - 2
91 }
92 let d: i64 = *p_eo + *p_off
93 if *p_len < 2 { return 0 - 3 }
94 let group: i64 = ((sh_body[d] & 0xff) << 8) | (sh_body[d + 1] & 0xff)
95 cc_dec(0x47, 0x52, group) // GR=
96 return group
97}
98
99// Is this ServerHello a TLS 1.3 negotiation? RFC 8446: a 1.3 SH
100// carries supported_versions selecting 0x0304; a 1.2 server's SH
101// has no such extension. Returns 1 / 0 / negative on parse fail.
102func cc_sh_is_tls13(sh_body: *u8, sh_len: i64) -> i64 {
103 let p_lv: *i64 = sys_mmap(16) as *i64
104 let p_ro: *i64 = sys_mmap(16) as *i64
105 let p_cs: *i64 = sys_mmap(16) as *i64
106 let p_eo: *i64 = sys_mmap(16) as *i64
107 let p_el: *i64 = sys_mmap(16) as *i64
108 if tls13_server_hello_parse(sh_body, sh_len, p_lv, p_ro, p_cs, p_eo, p_el) != NX_TLS13_HELLO_VERDICT_OK {
109 return 0 - 1
110 }
111 let p_off: *i64 = sys_mmap(16) as *i64
112 let p_len: *i64 = sys_mmap(16) as *i64
113 if tls13_ext_find(sh_body + *p_eo, *p_el, EXT_SUPPORTED_VERSIONS, p_off, p_len) != NX_TLS13_HELLO_VERDICT_OK {
114 return 0
115 }
116 let sv: i64 = tls13_ext_parse_supported_versions_server(sh_body + *p_eo + *p_off, *p_len)
117 if sv == TLS_13_VERSION { return 1 }
118 return 0
119}
120
121// Connect to url (host_len chars of host). Returns fd or negative.
122func cc_connect(url: *u8, host_len: i64, out_host: *i64) -> i64 {
123 let url_p: *NxUrl = nx_url_new()
124 let target_raw: *u8 = sys_mmap(32)
125 let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
126 target.url = url_p
127 target.port = 0
128 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { return 0 - 90 }
129 let fd_p: *i64 = sys_mmap(16) as *i64
130 let ucv: i64 = nx_https_url_connect(target, url, 1781100001, fd_p)
131 cc_dec(0x55, 0x43, ucv) // UC=
132 if ucv != NX_HTTPS_CONNECT_OK { return 0 - 91 }
133 let fd: i64 = *fd_p
134 sys_set_socket_timeout(fd, 10)
135 // Flattened on purpose: the chained (url + target.url.host_off)
136 // as i64 store shape segfaulted on this lane (expression-shape
137 // miscompile class; same family as the 4-deep nested-if landmine).
138 let u: *NxUrl = target.url
139 let hoff: i64 = u.host_off
140 let hp: *u8 = url + hoff
141 *out_host = hp as i64
142 return fd
143}
144
145// Wrap CH bytes in a plaintext handshake record and send.
146func cc_send_ch(fd: i64, ch_buf: *u8, ch_n: i64) -> i64 {
147 let rec: *u8 = sys_mmap(1024 + NX_TLS13_RECORD_HEADER_LEN)
148 rec[0] = (NX_TLS13_CT_HANDSHAKE & 0xff) as u8
149 rec[1] = 0x03 as u8; rec[2] = 0x01 as u8
150 rec[3] = ((ch_n >> 8) & 0xff) as u8
151 rec[4] = (ch_n & 0xff) as u8
152 var ci: i64 = 0
153 while ci < ch_n { rec[NX_TLS13_RECORD_HEADER_LEN + ci] = ch_buf[ci]; ci = ci + 1 }
154 var sent: i64 = 0
155 let want: i64 = NX_TLS13_RECORD_HEADER_LEN + ch_n
156 while sent < want {
157 let w: i64 = sys_write(fd, rec + sent, want - sent)
158 if w <= 0 { cc_dec(0x57, 0x52, w); return 0 - 1 } // WR=
159 sent = sent + w
160 }
161 return 0
162}
163
164// Encrypted-handshake walk (B1 probe pattern): drives the session
165// from WAIT_EE to WAIT_CLIENT_FIN. Returns 0 on verified server
166// Finished, else a fail code (20-91).
167func cc_walk(s: *Tls13ClientSession, fd: i64, val_ctx: *TlsValidationContext) -> i64 {
168 var recno: i64 = 0
169 var fail: i64 = 0
170 var running: i64 = 1
171 while running == 1 {
172 if s.state == NX_TLS13_CSESSION_STATE_WAIT_CLIENT_FIN { running = 0 }
173 if recno >= 16 { running = 0; if fail == 0 { fail = 90 } }
174 if running == 1 {
175 let hs_record: *u8 = sys_mmap(17000)
176 let hs_total: i64 = nx_tls13_read_record_from_fd(fd, hs_record, 17000)
177 cc_dec(0x52, 0x43, recno) // RC=
178 cc_dec(0x54, 0x4C, hs_total) // TL=
179 if hs_total < 0 { fail = 20; running = 0 }
180 if running == 1 {
181 let oct: i64 = hs_record[0] & 0xff
182 cc_dec(0x43, 0x54, oct) // CT=
183 var skip: i64 = 0
184 if oct == 20 { skip = 1 }
185 if skip == 0 {
186 let header: *u8 = hs_record
187 let ctxt: *u8 = hs_record + NX_TLS13_RECORD_HEADER_LEN
188 let ct_len: i64 = hs_total - NX_TLS13_RECORD_HEADER_LEN - NX_TLS13_RECORD_TAG_LEN
189 let tag: *u8 = hs_record + hs_total - NX_TLS13_RECORD_TAG_LEN
190 let inner: *u8 = sys_mmap(ct_len + 16)
191 let real_ct_p: *i64 = sys_mmap(16) as *i64
192 let real_len_p: *i64 = sys_mmap(16) as *i64
193 let dv: i64 = nx_tls13_record_decrypt_v2(
194 s.cipher_suite, s.server_hs_traffic_key, s.server_hs_iv,
195 s.server_seq, header, ctxt, ct_len, tag,
196 inner, real_ct_p, real_len_p)
197 s.server_seq = s.server_seq + 1
198 cc_dec(0x44, 0x56, dv) // DV=
199 if dv != NX_TLS13_REC_VERDICT_OK { fail = 30; running = 0 }
200 if running == 1 {
201 cc_dec(0x49, 0x43, *real_ct_p) // IC=
202 if *real_ct_p == 21 {
203 if *real_len_p >= 2 { cc_dec(0x41, 0x44, inner[1] & 0xff) } // AD=
204 fail = 31; running = 0
205 }
206 if running == 1 { if *real_ct_p != NX_TLS13_CT_HANDSHAKE { fail = 32; running = 0 } }
207 }
208 if running == 1 {
209 let plen: i64 = *real_len_p
210 cc_dec(0x50, 0x4C, plen) // PL=
211 var off: i64 = 0
212 let new_state_p: *i64 = sys_mmap(16) as *i64
213 while off < plen {
214 if off + 4 > plen { fail = 33; off = plen }
215 if fail == 0 {
216 let mt: i64 = inner[off] & 0xff
217 let body_len: i64 = ((inner[off + 1] & 0xff) << 16) | ((inner[off + 2] & 0xff) << 8) | (inner[off + 3] & 0xff)
218 let msg_len: i64 = 4 + body_len
219 cc_dec(0x4D, 0x54, mt) // MT=
220 if off + msg_len > plen { fail = 34; off = plen }
221 if fail == 0 {
222 let cstate: i64 = s.state - NX_TLS13_CSESSION_STATE_WAIT_EE + NX_TLS13_CSTATE_WAIT_EE
223 let dw: i64 = tls13_client_dispatch_with_validation(
224 cstate, inner + off, msg_len,
225 s.server_hs_traffic_secret, s.transcript,
226 new_state_p, val_ctx)
227 cc_dec(0x44, 0x57, dw) // DW=
228 if dw != NX_TLS13_DWV_OK { fail = 40 + dw; off = plen }
229 if fail == 0 {
230 if *new_state_p == NX_TLS13_CSTATE_WAIT_EE { s.state = NX_TLS13_CSESSION_STATE_WAIT_EE }
231 if *new_state_p == NX_TLS13_CSTATE_WAIT_CERT { s.state = NX_TLS13_CSESSION_STATE_WAIT_CERT }
232 if *new_state_p == NX_TLS13_CSTATE_WAIT_CV { s.state = NX_TLS13_CSESSION_STATE_WAIT_CV }
233 if *new_state_p == NX_TLS13_CSTATE_WAIT_SF { s.state = NX_TLS13_CSESSION_STATE_WAIT_SF }
234 if *new_state_p == NX_TLS13_CSTATE_CONNECTED { s.state = NX_TLS13_CSESSION_STATE_WAIT_CLIENT_FIN }
235 cc_dec(0x53, 0x54, s.state) // ST=
236 off = off + msg_len
237 }
238 }
239 }
240 }
241 if fail != 0 { running = 0 }
242 }
243 }
244 }
245 recno = recno + 1
246 }
247 }
248 if fail == 0 {
249 if s.state != NX_TLS13_CSESSION_STATE_WAIT_CLIENT_FIN { fail = 91 }
250 }
251 return fail
252}
253
254// LEG A: httpbin census with the PRODUCT dual-share CH.
255// Returns 0 = PASS (TLS-1.2-only signature confirmed, OR full 1.3
256// handshake if they enabled it), nonzero = honest fail code.
257func cc_leg_httpbin(store: *TrustStore, epoch: i64) -> i64 {
258 let url: *u8 = "https://httpbin.org/\x00"
259 let host_len: i64 = 11
260 let host_p: *i64 = sys_mmap(16) as *i64
261 let fd: i64 = cc_connect(url, host_len, host_p)
262 if fd < 0 { return 13 }
263 // Flattened: `*host_p as *u8` in one expression miscompiles on
264 // this lane (deref+ptr-cast shape; same family as the 4-deep
265 // nested-if landmine). Two statements compile correctly.
266 let hv: i64 = host_p[0]
267 let host: *u8 = hv as *u8
268
269 let cr: *u8 = sys_mmap(32)
270 let priv: *u8 = sys_mmap(32)
271 var i: i64 = 0
272 while i < 32 { cr[i] = (0xC0 + i) as u8; priv[i] = (0xA0 + i) as u8; i = i + 1 }
273 let s: *Tls13ClientSession = nx_tls13_client_session_new(cr, priv)
274 let ch_buf: *u8 = sys_mmap(1024)
275 let ch_n: i64 = nx_tls13_client_session_emit_ch(s, host, host_len, ch_buf, 1024)
276 cc_dec(0x43, 0x48, ch_n) // CH=
277 if ch_n < 0 { sys_close(fd); return 14 }
278 if cc_send_ch(fd, ch_buf, ch_n) != 0 { sys_close(fd); return 15 }
279
280 let sh_record: *u8 = sys_mmap(16700)
281 let sh_total: i64 = nx_tls13_read_record_from_fd(fd, sh_record, 16700)
282 if sh_total < 0 { sys_close(fd); cc_dec(0x52, 0x44, sh_total); return 16 } // RD=
283
284 // Plaintext alert at the CH boundary: classify.
285 if (sh_record[0] & 0xff) == 21 {
286 let al: i64 = sh_record[NX_TLS13_RECORD_HEADER_LEN] & 0xff
287 let ad: i64 = sh_record[NX_TLS13_RECORD_HEADER_LEN + 1] & 0xff
288 cc_dec(0x41, 0x4C, al) // AL=
289 cc_dec(0x41, 0x44, ad) // AD=
290 sys_close(fd)
291 if ad == 0 { return 0 } // close_notify = no-1.3 signature (measured 2026-06-10)
292 if ad == 70 { return 0 } // protocol_version = no-1.3, also honest
293 return 18 // any other alert = REAL CH problem
294 }
295
296 let sh_body: *u8 = sh_record + NX_TLS13_RECORD_HEADER_LEN
297 let sh_body_len: i64 = sh_total - NX_TLS13_RECORD_HEADER_LEN
298 let is13: i64 = cc_sh_is_tls13(sh_body, sh_body_len)
299 cc_dec(0x56, 0x33, is13) // V3= (1 = TLS 1.3 SH)
300 if is13 == 0 {
301 // TLS 1.2 ServerHello = the no-1.3 signature, leg PASS.
302 sys_close(fd)
303 return 0
304 }
305 if is13 < 0 { sys_close(fd); return 19 }
306
307 // httpbin speaks TLS 1.3 now: the leg DEMANDS the full handshake.
308 cc_sh_group_census(sh_body, sh_body_len)
309 let rv: i64 = nx_tls13_client_session_recv_sh(s, sh_body, sh_body_len)
310 cc_dec(0x52, 0x56, rv) // RV=
311 if rv != NX_TLS13_RECV_SH_OK { sys_close(fd); return 17 }
312 let val_ctx_raw: *u8 = sys_mmap(64)
313 let val_ctx: *TlsValidationContext = val_ctx_raw as *TlsValidationContext
314 val_ctx.store = store
315 val_ctx.sni_host = host
316 val_ctx.sni_host_len = host_len
317 val_ctx.now_epoch = epoch
318 let wf: i64 = cc_walk(s, fd, val_ctx)
319 sys_close(fd)
320 return wf
321}
322
323// LEG B: live P-256 proof -- secp256r1-ONLY CH to github.com,
324// full encrypted handshake to verified server Finished.
325func cc_leg_p256(store: *TrustStore, epoch: i64) -> i64 {
326 let url: *u8 = "https://github.com/\x00"
327 let host_len: i64 = 10
328 let host_p: *i64 = sys_mmap(16) as *i64
329 let fd: i64 = cc_connect(url, host_len, host_p)
330 if fd < 0 { return 23 }
331 // Flattened: `*host_p as *u8` in one expression miscompiles on
332 // this lane (deref+ptr-cast shape; see leg A + landmine note).
333 let hv: i64 = host_p[0]
334 let host: *u8 = hv as *u8
335
336 let cr: *u8 = sys_mmap(32)
337 let priv: *u8 = sys_mmap(32)
338 var i: i64 = 0
339 while i < 32 { cr[i] = (0xD0 + i) as u8; priv[i] = (0x90 + i) as u8; i = i + 1 }
340 let s: *Tls13ClientSession = nx_tls13_client_session_new(cr, priv)
341
342 // P-256-only CH: emit2 with null x25519 share, then mirror
343 // emit_ch's transcript + state steps (the session emit keeps
344 // the product dual-share contract; this is the census mode).
345 let ch_buf: *u8 = sys_mmap(1024)
346 let ch_n: i64 = tls13_client_hello_emit2(
347 cr, host, host_len, 0 as *u8, s.p256_pub, ch_buf, 1024
348 )
349 cc_dec(0x43, 0x48, ch_n) // CH=
350 if ch_n < 0 { sys_close(fd); return 24 }
351 nx_tls13_transcript_update(s.transcript, ch_buf, ch_n)
352 s.state = NX_TLS13_CSESSION_STATE_CH_SENT
353 if cc_send_ch(fd, ch_buf, ch_n) != 0 { sys_close(fd); return 25 }
354
355 let sh_record: *u8 = sys_mmap(16700)
356 let sh_total: i64 = nx_tls13_read_record_from_fd(fd, sh_record, 16700)
357 if sh_total < 0 { sys_close(fd); cc_dec(0x52, 0x44, sh_total); return 26 } // RD=
358 if (sh_record[0] & 0xff) == 21 {
359 cc_dec(0x41, 0x4C, sh_record[NX_TLS13_RECORD_HEADER_LEN] & 0xff) // AL=
360 cc_dec(0x41, 0x44, sh_record[NX_TLS13_RECORD_HEADER_LEN + 1] & 0xff) // AD=
361 sys_close(fd)
362 return 28
363 }
364 let sh_body: *u8 = sh_record + NX_TLS13_RECORD_HEADER_LEN
365 let sh_body_len: i64 = sh_total - NX_TLS13_RECORD_HEADER_LEN
366 let gr: i64 = cc_sh_group_census(sh_body, sh_body_len)
367 // The whole point of leg B: server took the secp256r1 share.
368 if gr != 23 { sys_close(fd); return 29 }
369 let rv: i64 = nx_tls13_client_session_recv_sh(s, sh_body, sh_body_len)
370 cc_dec(0x52, 0x56, rv) // RV=
371 if rv != NX_TLS13_RECV_SH_OK { sys_close(fd); return 27 }
372
373 let val_ctx_raw: *u8 = sys_mmap(64)
374 let val_ctx: *TlsValidationContext = val_ctx_raw as *TlsValidationContext
375 val_ctx.store = store
376 val_ctx.sni_host = host
377 val_ctx.sni_host_len = host_len
378 val_ctx.now_epoch = epoch
379 let wf: i64 = cc_walk(s, fd, val_ctx)
380 sys_close(fd)
381 return wf
382}
383
384func main() -> i64 {
385 let epoch: i64 = sys_now_realtime_sec() // real clock (B1 lesson)
386 let r: i64 = nx_trust_store_load_from_certdata("/tmp/mozilla_certdata.txt\x00" as *u8, 300, 4194304)
387 if r <= 0 { cc_dec(0x4C, 0x4F, r); return 11 } // LO=
388 let store: *TrustStore = r as *TrustStore
389 cc_dec(0x43, 0x41, trust_store_count(store)) // CA=
390
391 let la: i64 = cc_leg_httpbin(store, epoch)
392 cc_dec(0x4C, 0x31, la) // L1=
393 let lb: i64 = cc_leg_p256(store, epoch)
394 cc_dec(0x4C, 0x32, lb) // L2=
395
396 if la != 0 { return la }
397 if lb != 0 { return 100 + lb }
398 return 0
399}