nx_tls12_mutant.nx source
↩ module page · 757 lines · 42864 B
1// nx_tls12_mutant.nx -- MUTATION PROOF for the SKE signature check. Regenerated from nx_tls12_client.nx,
2// identical EXCEPT one flipped byte of the signed payload. It MUST refuse. Test artifact, never live.
3// nx_tls12_client.nx -- INCREMENT 2 of the sovereign TLS-1.2 client: COMPLETE the ECDHE-RSA-AES128-GCM-SHA256
4// handshake and prove it by DECRYPTING + VERIFYING the server's Finished (definitive: our key schedule matches the
5// peer's iff its Finished MAC verifies). Composes shipped primitives -- p256_ecdh_* (ECDHE), hmac_sha256 (the TLS
6// 1.2 PRF P_SHA256), sha256_digest (transcript), nx_aes128_gcm_seal/open (record cipher). This is the keystone that
7// gets nx_gpu_ctl + Porkbun off curl (both hosts are TLS-1.2-only; our shipped stack is 1.3-only).
8// NOTE(security, INC2): cert-chain + ServerKeyExchange-signature validation are NOT yet wired (INC3 hardening) -- the
9// handshake completes and is confidential, but not yet authenticated against a MITM. Do NOT ship for auth'd traffic
10// until INC3 lands. license_tier: ORIGINAL expect_exit: 0
11import "nx_syscalls.nx"
12import "nx_csprng.nx"
13import "nx_https_url_for_fetch.nx"
14import "nx_https_url_connect.nx"
15import "nx_sha256.nx"
16import "nx_p256_ecdh.nx"
17import "nx_x509_trust_store.nx"
18import "nx_trust_store_load_from_certdata.nx"
19import "nx_tls13_client_validate_certificate.nx"
20import "nx_x509.nx"
21import "nx_u4096.nx"
22import "nx_x509_pubkey_rsa_4096.nx"
23import "nx_u2048.nx"
24import "nx_x509_pubkey_rsa.nx"
25import "nx_rsa_pkcs1_v1_5_sha512.nx"
26import "nx_x509_verify_rsa_pkcs1_sha256.nx"
27import "nx_x509_verify_rsa_pkcs1_sha256_4096.nx"
28import "nx_hmac.nx" // was hmac.nx -- CODE-IDENTICAL twin (49/49 stmts) on the LEGACY syscalls.nx+sha256.nx family.
29// Two files defining hmac_sha256 + main, with the expander deduping BY PATH NOT BY SYMBOL, made
30// every legacy importer a duplicate-symbol landmine for the nx_ family (debt 1785524913).
31import "nx_aes128_gcm.nx"
32const T12_MAGIC_1024: i64 = 1024
33const T12_MAGIC_16384: i64 = 16384
34const T12_MAGIC_2048: i64 = 2048
35const T12_MAGIC_65536: i64 = 65536
36const T12_MAGIC_4194304: i64 = 4194304
37const T12_MAGIC_4096: i64 = 4096
38const T12_MAGIC_1200: i64 = 1200
39const T12_MAGIC_1400: i64 = 1400
40const T12_MAGIC_131072: i64 = 131072
41
42const T12_HS: i64 = 22
43const T12_CCS: i64 = 20
44const T12_ALERT: i64 = 21
45
46func t12_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
47func t12_putn(v: i64) -> i64 {
48 let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}
49 let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}
50 while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}
51 var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0
52}
53func t12_puthex(byte: i64) -> i64 { let hx: *u8="0123456789abcdef" as *u8; let o: *u8=sys_mmap(2); o[0]=hx[(byte>>4)&0xf] as u8; o[1]=hx[byte&0xf] as u8; sys_write(1,o,2); return 0 }
54
55func t12_b1(d: *u8, o: i64, v: i64) -> i64 { d[o]=(v&0xff) as u8; return o+1 }
56func t12_b2(d: *u8, o: i64, v: i64) -> i64 { d[o]=((v>>8)&0xff) as u8; d[o+1]=(v&0xff) as u8; return o+2 }
57func t12_b3(d: *u8, o: i64, v: i64) -> i64 { d[o]=((v>>16)&0xff) as u8; d[o+1]=((v>>8)&0xff) as u8; d[o+2]=(v&0xff) as u8; return o+3 }
58func t12_bytes(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64=0; while i<n { d[o+i]=s[i]; i=i+1 } return o+n }
59func t12_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
60func t12_readfile(path: *u8, out: *u8, cap: i64) -> i64 {
61 let fd: i64 = sys_openat_rd(path); if fd <= 0 { return 0 }
62 var off: i64 = 0; var go: i64 = 1
63 while go == 1 { let r: i64 = sys_read(fd, ((out as i64)+off) as *u8, cap-off); if r <= 0 { go = 0 } else { off = off + r } }
64 sys_close(fd)
65 var trimming: i64 = 1
66 while trimming == 1 {
67 if off <= 0 { trimming = 0 } else {
68 let c: i64 = out[off-1] as i64
69 if c == 10 { off = off - 1 } else { if c == 13 { off = off - 1 } else { trimming = 0 } }
70 }
71 }
72 return off
73}
74func t12_u64be(d: *u8, o: i64, v: i64) -> i64 {
75 d[o]=((v>>56)&0xff) as u8; d[o+1]=((v>>48)&0xff) as u8; d[o+2]=((v>>40)&0xff) as u8; d[o+3]=((v>>32)&0xff) as u8
76 d[o+4]=((v>>24)&0xff) as u8; d[o+5]=((v>>16)&0xff) as u8; d[o+6]=((v>>8)&0xff) as u8; d[o+7]=(v&0xff) as u8
77 return o+8
78}
79
80func t12_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
81 var off: i64=0
82 while off<n { let w: i64=sys_write(fd, ((buf as i64)+off) as *u8, n-off); if w<=0 { return 0-1 } off=off+w }
83 return 0
84}
85
86// TLS 1.2 PRF = P_SHA256(secret, label||seed). out_len bytes into out.
87// A(0)=label||seed ; A(i)=HMAC(secret,A(i-1)) ; out = HMAC(secret,A(i)||label||seed) concatenated.
88func t12_prf(secret: *u8, secret_len: i64, label: *u8, label_len: i64, seed: *u8, seed_len: i64, out: *u8, out_len: i64) -> i64 {
89 let ls_len: i64 = label_len + seed_len
90 let ls: *u8 = sys_mmap(ls_len + 8)
91 var i: i64 = 0
92 while i < label_len { ls[i] = label[i]; i = i + 1 }
93 i = 0
94 while i < seed_len { ls[label_len + i] = seed[i]; i = i + 1 }
95 // a = A(1) = HMAC(secret, A(0)=ls)
96 let a: *u8 = sys_mmap(32)
97 let a_next: *u8 = sys_mmap(32)
98 hmac_sha256(secret, secret_len, ls, ls_len, a)
99 let tmp: *u8 = sys_mmap(32 + ls_len + 8)
100 let block: *u8 = sys_mmap(32)
101 var off: i64 = 0
102 while off < out_len {
103 // block = HMAC(secret, A(i) || ls)
104 var j: i64 = 0
105 while j < 32 { tmp[j] = a[j]; j = j + 1 }
106 j = 0
107 while j < ls_len { tmp[32 + j] = ls[j]; j = j + 1 }
108 hmac_sha256(secret, secret_len, tmp, 32 + ls_len, block)
109 var c: i64 = 32
110 if out_len - off < 32 { c = out_len - off }
111 j = 0
112 while j < c { out[off + j] = block[j]; j = j + 1 }
113 off = off + c
114 // A(i+1) = HMAC(secret, A(i))
115 hmac_sha256(secret, secret_len, a, 32, a_next)
116 j = 0
117 while j < 32 { a[j] = a_next[j]; j = j + 1 }
118 }
119 return 0
120}
121
122// Build a TLS 1.2 ClientHello record into out; client_random copied to cr_out. Returns record length.
123func t12_client_hello(host: *u8, hlen: i64, cr_out: *u8, out: *u8) -> i64 {
124 let body: *u8 = sys_mmap(T12_MAGIC_1024)
125 var o: i64 = 0
126 o = t12_b2(body, o, 0x0303)
127 nx_csprng_fill(cr_out, 32)
128 o = t12_bytes(body, o, cr_out, 32)
129 o = t12_b1(body, o, 0)
130 o = t12_b2(body, o, 2)
131 o = t12_b2(body, o, 0xC02F)
132 o = t12_b1(body, o, 1)
133 o = t12_b1(body, o, 0)
134 let ext: *u8 = sys_mmap(512)
135 var e: i64 = 0
136 e = t12_b2(ext, e, 0x0000); e = t12_b2(ext, e, hlen + 5); e = t12_b2(ext, e, hlen + 3); e = t12_b1(ext, e, 0); e = t12_b2(ext, e, hlen); e = t12_bytes(ext, e, host, hlen)
137 e = t12_b2(ext, e, 0x000A); e = t12_b2(ext, e, 4); e = t12_b2(ext, e, 2); e = t12_b2(ext, e, 0x0017)
138 e = t12_b2(ext, e, 0x000B); e = t12_b2(ext, e, 2); e = t12_b1(ext, e, 1); e = t12_b1(ext, e, 0)
139 e = t12_b2(ext, e, 0x000D); e = t12_b2(ext, e, 8); e = t12_b2(ext, e, 6); e = t12_b2(ext, e, 0x0401); e = t12_b2(ext, e, 0x0501); e = t12_b2(ext, e, 0x0601)
140 o = t12_b2(body, o, e); o = t12_bytes(body, o, ext, e)
141 let body_len: i64 = o
142 var r: i64 = 0
143 r = t12_b1(out, r, T12_HS); r = t12_b2(out, r, 0x0303); r = t12_b2(out, r, body_len + 4)
144 r = t12_b1(out, r, 1); r = t12_b3(out, r, body_len); r = t12_bytes(out, r, body, body_len)
145 return r
146}
147
148// find "\r\n\r\n" (end of HTTP headers); return offset just past it, or -1 if not present yet.
149func t12_hdr_end(b: *u8, n: i64) -> i64 {
150 var i: i64 = 0
151 while i + 3 < n {
152 if b[i]==(13 as u8) { if b[i+1]==(10 as u8) { if b[i+2]==(13 as u8) { if b[i+3]==(10 as u8) { return i+4 } } } }
153 i = i + 1
154 }
155 return 0 - 1
156}
157func t12_lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c }
158func t12_match_ci(b: *u8, i: i64, n: i64, targ: *u8, tlen: i64) -> i64 {
159 if i + tlen > n { return 0 }
160 var j: i64 = 0
161 while j < tlen { if t12_lc(b[i+j]&0xff) != (targ[j] as i64) { return 0 } j = j + 1 }
162 return 1
163}
164// parse the Content-Length header value from HTTP response bytes; -1 if absent.
165func t12_content_length(b: *u8, n: i64) -> i64 {
166 var i: i64 = 0
167 while i + 15 < n {
168 if t12_match_ci(b, i, n, "content-length:" as *u8, 15) == 1 {
169 var p: i64 = i + 15
170 var sk: i64 = 1
171 while sk == 1 { if p < n { if b[p]==(32 as u8) { p = p + 1 } else { sk = 0 } } else { sk = 0 } }
172 var val: i64 = 0; var any: i64 = 0; var go: i64 = 1
173 while go == 1 { if p < n { let c: i64 = b[p]&0xff; if c >= 48 { if c <= 57 { val = val*10 + (c-48); any = 1; p = p + 1 } else { go = 0 } } else { go = 0 } } else { go = 0 } }
174 if any == 1 { return val }
175 return 0 - 1
176 }
177 i = i + 1
178 }
179 return 0 - 1
180}
181
182// RFC 7230 4.1 chunked-body decoder. src[start..n) is the raw body; writes the de-chunked bytes to dst and
183// returns the decoded length. Needed because Socrata/CFTC (and many APIs) answer WITHOUT Content-Length, so
184// the chunk framing "<hexsize>[;ext]CRLF <data> CRLF" would otherwise land INSIDE the JSON the caller parses
185// (measured 2026-07-25: a stray "3b ," appeared mid-object in the USGS response).
186func t12_dechunk(src: *u8, n: i64, start: i64, dst: *u8) -> i64 {
187 var cp: i64 = start
188 var dp: i64 = 0
189 var run: i64 = 1
190 while run == 1 {
191 var sz: i64 = 0
192 var any: i64 = 0
193 var scanning: i64 = 1
194 while scanning == 1 {
195 if cp >= n { scanning = 0; run = 0 } else {
196 let c: i64 = src[cp] & 0xff
197 var h: i64 = 0 - 1
198 if c >= 48 { if c <= 57 { h = c - 48 } }
199 if c >= 97 { if c <= 102 { h = c - 87 } }
200 if c >= 65 { if c <= 70 { h = c - 55 } }
201 if h < 0 { scanning = 0 } else { sz = (sz * 16) + h; any = 1; cp = cp + 1 }
202 }
203 }
204 if any == 0 { run = 0 }
205 if run == 1 {
206 var sk: i64 = 1
207 while sk == 1 {
208 if cp >= n { sk = 0; run = 0 } else {
209 let c2: i64 = src[cp] & 0xff
210 cp = cp + 1
211 if c2 == 10 { sk = 0 }
212 }
213 }
214 }
215 if run == 1 {
216 if sz == 0 { run = 0 } else {
217 var k: i64 = 0
218 while k < sz {
219 if cp + k < n { dst[dp + k] = src[cp + k] }
220 k = k + 1
221 }
222 dp = dp + sz
223 cp = cp + sz + 2
224 }
225 }
226 }
227 return dp
228}
229
230func main(argc: i64, argv: *i64) -> i64 {
231 sys_alarm(20) // hard watchdog: a network client must NEVER hang forever. If any read stalls past 20s the
232 // default SIGALRM action terminates us -- so nx_sov_build_run's auto-run phase can't wedge.
233 var url: *u8 = "https://console.vast.ai/" as *u8
234 if argc >= 2 { url = argv[1] as *u8 }
235 t12_puts("=== sovereign TLS-1.2 FULL handshake -> " as *u8); t12_puts(url); t12_puts(" ===\n" as *u8)
236
237 // PRF self-test vs the standard TLS-1.2 SHA-256 PRF vector (expect e3f229ba727be17b...)
238 let tsec: *u8 = sys_mmap(16)
239 tsec[0]=0x9b as u8;tsec[1]=0xbe as u8;tsec[2]=0x43 as u8;tsec[3]=0x6b as u8;tsec[4]=0xa9 as u8;tsec[5]=0x40 as u8;tsec[6]=0xf0 as u8;tsec[7]=0x17 as u8
240 tsec[8]=0xb1 as u8;tsec[9]=0x76 as u8;tsec[10]=0x52 as u8;tsec[11]=0x84 as u8;tsec[12]=0x9a as u8;tsec[13]=0x71 as u8;tsec[14]=0xdb as u8;tsec[15]=0x35 as u8
241 let tsd: *u8 = sys_mmap(16)
242 tsd[0]=0xa0 as u8;tsd[1]=0xba as u8;tsd[2]=0x9f as u8;tsd[3]=0x93 as u8;tsd[4]=0x6c as u8;tsd[5]=0xda as u8;tsd[6]=0x31 as u8;tsd[7]=0x18 as u8
243 tsd[8]=0x27 as u8;tsd[9]=0xa6 as u8;tsd[10]=0xf7 as u8;tsd[11]=0x96 as u8;tsd[12]=0xff as u8;tsd[13]=0xd5 as u8;tsd[14]=0x19 as u8;tsd[15]=0x8c as u8
244 let tout: *u8 = sys_mmap(16)
245 t12_prf(tsec, 16, "test label" as *u8, 10, tsd, 16, tout, 16)
246 t12_puts(" PRF self-test = " as *u8); var tz: i64=0; while tz<8 { t12_puthex(tout[tz]&0xff); tz=tz+1 } t12_puts(" (want e3f229ba727be17b)\n" as *u8)
247
248 let target_raw: *u8 = sys_mmap(64); let target: *NxHttpsTarget = target_raw as *NxHttpsTarget
249 target.url = nx_url_new(); target.port = 0
250 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { t12_puts("bad url\n" as *u8); return 1 }
251 let host: *u8 = ((url as i64) + target.url.host_off) as *u8
252 let hlen: i64 = target.url.host_len
253 let now: i64 = sys_now_realtime_sec()
254 let fd_p: *i64 = sys_mmap(16) as *i64
255 if nx_https_url_connect(target, url, now, fd_p) != NX_HTTPS_CONNECT_OK { t12_puts("TCP connect FAILED\n" as *u8); return 1 }
256 let fd: i64 = fd_p[0]
257 sys_set_socket_timeout(fd, 6) // never block forever on a read: a kept-open connection returns after 6s idle
258
259 // transcript accumulator (handshake-layer bytes only)
260 let tr: *u8 = sys_mmap(T12_MAGIC_16384)
261 var trlen: i64 = 0
262
263 // --- send ClientHello ---
264 let cr: *u8 = sys_mmap(32)
265 let ch: *u8 = sys_mmap(T12_MAGIC_2048)
266 let chlen: i64 = t12_client_hello(host, hlen, cr, ch)
267 if t12_write_all(fd, ch, chlen) < 0 { t12_puts("CH write fail\n" as *u8); sys_close(fd); return 1 }
268 trlen = t12_bytes(tr, trlen, ((ch as i64)+5) as *u8, chlen-5) // hs bytes = record minus 5-byte header
269 t12_puts(" ClientHello sent (" as *u8); t12_putn(chlen); t12_puts("B)\n" as *u8)
270
271 // --- read server flight; append hs payloads to transcript; extract server_random + server ECDHE pub ---
272 let buf: *u8 = sys_mmap(T12_MAGIC_65536)
273 var total: i64 = 0
274 let sr: *u8 = sys_mmap(32)
275 let spub: *u8 = sys_mmap(65)
276 var got_sr: i64 = 0; var got_spub: i64 = 0; var got_shd: i64 = 0
277 var reads: i64 = 0
278 while got_shd == 0 {
279 if reads > 40 { got_shd = 2 }
280 let n: i64 = sys_read(fd, ((buf as i64)+total) as *u8, T12_MAGIC_65536-total)
281 if n <= 0 { got_shd = 2 } else {
282 total = total + n; reads = reads + 1
283 // scan records present so far for a ServerHelloDone to know when the flight is complete
284 var p: i64 = 0
285 while p + 5 <= total {
286 let rtype: i64 = buf[p] & 0xff
287 let rlen: i64 = ((buf[p+3]&0xff)<<8)|(buf[p+4]&0xff)
288 if p + 5 + rlen > total { p = total } else {
289 if rtype == T12_HS {
290 var hp: i64 = p+5; let rend: i64 = p+5+rlen
291 while hp + 4 <= rend {
292 let ht: i64 = buf[hp]&0xff
293 let hl: i64 = ((buf[hp+1]&0xff)<<16)|((buf[hp+2]&0xff)<<8)|(buf[hp+3]&0xff)
294 if ht == 14 { got_shd = 1 }
295 hp = hp + 4 + hl
296 }
297 }
298 p = p + 5 + rlen
299 }
300 }
301 }
302 }
303 // now walk the complete flight: append hs payloads to transcript, pull server_random + ECDHE pub
304 var p: i64 = 0
305 while p + 5 <= total {
306 let rtype: i64 = buf[p]&0xff
307 let rlen: i64 = ((buf[p+3]&0xff)<<8)|(buf[p+4]&0xff)
308 if rtype == T12_HS {
309 trlen = t12_bytes(tr, trlen, ((buf as i64)+p+5) as *u8, rlen)
310 var hp: i64 = p+5; let rend: i64 = p+5+rlen
311 while hp + 4 <= rend {
312 let ht: i64 = buf[hp]&0xff
313 let hl: i64 = ((buf[hp+1]&0xff)<<16)|((buf[hp+2]&0xff)<<8)|(buf[hp+3]&0xff)
314 let bp: i64 = hp+4
315 if ht == 2 { var z: i64=0; while z<32 { sr[z]=buf[bp+2+z]; z=z+1 } got_sr=1 }
316 if ht == 12 { var z: i64=0; while z<65 { spub[z]=buf[bp+4+z]; z=z+1 } got_spub=1 }
317 hp = hp + 4 + hl
318 }
319 }
320 p = p + 5 + rlen
321 }
322 if got_sr==0 { t12_puts(" RED: no server_random\n" as *u8); sys_close(fd); return 1 }
323 if got_spub==0 { t12_puts(" RED: no server ECDHE pubkey\n" as *u8); sys_close(fd); return 1 }
324 t12_puts(" flight parsed (transcript " as *u8); t12_putn(trlen); t12_puts("B); server ECDHE pub[0]=0x" as *u8); t12_puthex(spub[0]&0xff); t12_puts("\n" as *u8)
325
326 // ---- INC3 part 1 (2026-07-25): X.509 CHAIN VALIDATION ----
327 // Walk the TRANSCRIPT (a contiguous handshake stream, so a Certificate spanning several TLS records is
328 // handled correctly -- the per-record walk above cannot do that) and transcode the TLS-1.2 Certificate
329 // message into the TLS-1.3 shape that the SHIPPED, ALREADY-GATED RFC-5280 pipeline consumes:
330 // 1.2 body = [3-byte chain_len] [ per cert: 3-byte len + DER ]
331 // 1.3 body = [1-byte ctx=0][3-byte chain_len][ per cert: 3-byte len + DER + 2-byte ext_len=0 ]
332 // INTEGRATION, NOT INVENTION: hostname/SAN, validity window, chain DN+signature and trust-anchor
333 // resolution are all reused verbatim from nx_https_cert_pipeline via nx_tls13_client_validate_certificate.
334 var certoff: i64 = 0 - 1
335 var certlen: i64 = 0
336 var cw: i64 = 0
337 while cw + 4 <= trlen {
338 let cht: i64 = tr[cw]&0xff
339 let chl: i64 = ((tr[cw+1]&0xff)<<16)|((tr[cw+2]&0xff)<<8)|(tr[cw+3]&0xff)
340 if cht == 11 { if certoff < 0 { certoff = cw + 4; certlen = chl } }
341 cw = cw + 4 + chl
342 }
343 if certoff < 0 { t12_puts(" RED: no Certificate message in the server flight\n" as *u8); sys_close(fd); return 1 }
344 let t13: *u8 = sys_mmap(T12_MAGIC_65536)
345 var t13o: i64 = 0
346 t13[0] = 11 as u8
347 t13o = 4
348 t13[t13o] = 0 as u8
349 t13o = t13o + 1
350 let chain_lo: i64 = t13o
351 t13o = t13o + 3
352 var q: i64 = certoff + 3
353 let cend: i64 = certoff + certlen
354 var ncerts: i64 = 0
355 var bad12: i64 = 0
356 while q + 3 <= cend {
357 let dl: i64 = ((tr[q]&0xff)<<16)|((tr[q+1]&0xff)<<8)|(tr[q+2]&0xff)
358 if q + 3 + dl > cend { bad12 = 1; q = cend } else {
359 t13[t13o] = tr[q] ; t13[t13o+1] = tr[q+1] ; t13[t13o+2] = tr[q+2]
360 t13o = t13o + 3
361 var dz: i64 = 0
362 while dz < dl { t13[t13o+dz] = tr[q+3+dz]; dz = dz + 1 }
363 t13o = t13o + dl
364 t13[t13o] = 0 as u8 ; t13[t13o+1] = 0 as u8
365 t13o = t13o + 2
366 ncerts = ncerts + 1
367 q = q + 3 + dl
368 }
369 }
370 if bad12 == 1 { t12_puts(" RED: malformed TLS-1.2 certificate_list\n" as *u8); sys_close(fd); return 1 }
371 if ncerts < 1 { t12_puts(" RED: empty certificate chain\n" as *u8); sys_close(fd); return 1 }
372 let chain_bytes: i64 = t13o - (chain_lo + 3)
373 t13[chain_lo] = ((chain_bytes>>16)&0xff) as u8
374 t13[chain_lo+1] = ((chain_bytes>>8)&0xff) as u8
375 t13[chain_lo+2] = (chain_bytes&0xff) as u8
376 let hs_body: i64 = t13o - 4
377 t13[1] = ((hs_body>>16)&0xff) as u8
378 t13[2] = ((hs_body>>8)&0xff) as u8
379 t13[3] = (hs_body&0xff) as u8
380
381 let tsr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, T12_MAGIC_4194304)
382 if tsr <= 0 { t12_puts(" RED: trust-store load failed (data/mozilla_certdata.txt on daemon CWD?)\n" as *u8); sys_close(fd); return 1 }
383 let vraw: *u8 = sys_mmap(128)
384 let vctx: *TlsValidationContext = vraw as *TlsValidationContext
385 vctx.store = tsr as *TrustStore
386 vctx.sni_host = host
387 vctx.sni_host_len = hlen
388 vctx.now_epoch = now
389 vctx.cert_out = 0 as *u8
390 vctx.cert_out_cap = 0
391 vctx.cert_out_len = 0
392 vctx.cached_cert = 0 as *u8
393 vctx.cached_cert_len = 0
394 let cvv: i64 = nx_tls13_client_validate_certificate(t13, t13o, vctx)
395 var chain_ok: i64 = 0
396 if cvv == NX_TLS13_CLIENT_CV_OK { chain_ok = 1 }
397 if chain_ok == 1 {
398 t12_puts(" chain=VALID certs=" as *u8); t12_putn(ncerts)
399 t12_puts(" (X.509 path + SAN/hostname + validity window verified vs the Mozilla trust store)\n" as *u8)
400 } else {
401 t12_puts(" verdict=REFUSED rule=certificate-chain-validation-failed cv=" as *u8); t12_putn(cvv)
402 t12_puts("\n" as *u8)
403 sys_close(fd)
404 sys_exit(4)
405 return 4
406 }
407
408 // ---- INC3 part 2 (2026-07-25): SERVER-KEY-EXCHANGE SIGNATURE ----
409 // THIS is what actually closes the MITM hole. A validated chain alone proves only that SOMEONE holds a
410 // cert for this name -- certificates are PUBLIC, so an attacker can replay the real one while swapping in
411 // their OWN ECDHE key. The SKE signature is the only thing binding the ephemeral key to the certificate:
412 // the server signs client_random || server_random || curve_params with the private key of the leaf cert.
413 // Verify it and the peer has proven possession of that private key over THIS key exchange.
414 var skeoff: i64 = 0 - 1
415 var skelen: i64 = 0
416 var sw: i64 = 0
417 while sw + 4 <= trlen {
418 let sht: i64 = tr[sw]&0xff
419 let shl: i64 = ((tr[sw+1]&0xff)<<16)|((tr[sw+2]&0xff)<<8)|(tr[sw+3]&0xff)
420 if sht == 12 { if skeoff < 0 { skeoff = sw + 4; skelen = shl } }
421 sw = sw + 4 + shl
422 }
423 var authed: i64 = 0
424 if skeoff < 0 {
425 t12_puts(" verdict=REFUSED rule=no-serverkeyexchange-message\n" as *u8); sys_close(fd); sys_exit(4); return 4
426 }
427 let ctype: i64 = tr[skeoff]&0xff
428 if ctype != 3 {
429 t12_puts(" verdict=REFUSED rule=ske-not-named-curve curve_type=" as *u8); t12_putn(ctype)
430 t12_puts("\n" as *u8); sys_close(fd); sys_exit(4); return 4
431 }
432 let pklen: i64 = tr[skeoff+3]&0xff
433 let params_len: i64 = 4 + pklen
434 let so: i64 = skeoff + params_len
435 if so + 4 > skeoff + skelen {
436 t12_puts(" verdict=REFUSED rule=ske-truncated\n" as *u8); sys_close(fd); sys_exit(4); return 4
437 }
438 let sighash: i64 = tr[so]&0xff
439 let sigkind: i64 = tr[so+1]&0xff
440 let siglen: i64 = ((tr[so+2]&0xff)<<8)|(tr[so+3]&0xff)
441 let sigoff: i64 = so + 4
442 // signed payload = client_random(32) || server_random(32) || the EXACT curve_params bytes off the wire
443 let sd: *u8 = sys_mmap(T12_MAGIC_4096)
444 var sdo: i64 = 0
445 sdo = t12_bytes(sd, sdo, cr, 32)
446 sdo = t12_bytes(sd, sdo, sr, 32)
447 sdo = t12_bytes(sd, sdo, ((tr as i64)+skeoff) as *u8, params_len)
448 // leaf certificate = the FIRST entry of the chain we just validated
449 let leaf_dl: i64 = ((tr[certoff+3]&0xff)<<16)|((tr[certoff+4]&0xff)<<8)|(tr[certoff+5]&0xff)
450 let leaf_ptr: *u8 = ((tr as i64) + certoff + 6) as *u8
451 let leaf_raw: *u8 = sys_mmap(256)
452 let leaf: *X509Cert = leaf_raw as *X509Cert
453 if x509_parse(leaf_ptr, leaf_dl, leaf) < 0 {
454 t12_puts(" verdict=REFUSED rule=leaf-cert-parse-failed\n" as *u8); sys_close(fd); sys_exit(4); return 4
455 }
456 // RSA-PKCS1-v1.5 + SHA-256 is signature_algorithms 0x0401 (hash=4 sha256, sig=1 rsa). Anything else is
457 // REFUSED BY NAME rather than silently accepted -- fail-closed on an unimplemented signature algorithm.
458 // signature_algorithms: hash 4 = SHA-256, hash 6 = SHA-512 (RFC 5246 7.4.1.4.1). Anything else is
459 // REFUSED BY NAME rather than silently accepted. MEASURED 2026-07-25: bls/treasury/usgs sign SHA-256,
460 // publicreporting.cftc.gov signs SHA-512 -- which is why the SHA-512 path exists at all.
461 if sighash != 4 { if sighash != 6 {
462 t12_puts(" verdict=REFUSED rule=ske-sig-hash-unimplemented hash=" as *u8); t12_putn(sighash)
463 t12_puts(" sig=" as *u8); t12_putn(sigkind); t12_puts("\n" as *u8); sys_close(fd); sys_exit(4); return 4
464 } }
465 if sigkind != 1 {
466 t12_puts(" verdict=REFUSED rule=ske-sig-alg-unimplemented hash=" as *u8); t12_putn(sighash)
467 t12_puts(" sig=" as *u8); t12_putn(sigkind); t12_puts("\n" as *u8); sys_close(fd); sys_exit(4); return 4
468 }
469 // MODULUS-SIZE DISPATCH (mirrors nx_x509_verify_under_issuer): an RSA PKCS#1 signature is exactly the
470 // modulus byte-length. 256 = RSA-2048 (u2048 substrate), >256 = RSA-3072/4096 (k-aware u4096 substrate).
471 // MEASURED 2026-07-25: api.bls.gov signs with 512B (RSA-4096) while api.fiscaldata.treasury.gov and
472 // waterservices.usgs.gov sign with 256B (RSA-2048) -- a 4096-only path rejected them as BAD_LEN v=2.
473 let sigptr: *u8 = ((tr as i64)+sigoff) as *u8
474 sd[0] = (sd[0] ^ (1 as u8)) as u8 // MUTATION-PROOF ONLY
475 var skev: i64 = 0 - 1
476 if siglen == 256 {
477 let n2: *i64 = u2048_alloc()
478 let e2: *i64 = sys_mmap(16) as *i64
479 if nx_x509_pubkey_extract_rsa(leaf_ptr, leaf, n2, e2) != NX_X509_PUBKEY_RSA_OK {
480 t12_puts(" verdict=REFUSED rule=leaf-rsa2048-pubkey-extract-failed\n" as *u8); sys_close(fd); sys_exit(4); return 4
481 }
482 var v2: i64 = 0
483 if sighash == 6 {
484 let r5: i64 = rsa_pkcs1_v1_5_sha512_verify(sd, sdo, sigptr, n2, e2[0])
485 if r5 == NX_RSA_PKCS1_V15_512_OK { v2 = NX_X509_RSA_PKCS1_SHA256_OK } else { v2 = r5 }
486 } else {
487 v2 = nx_x509_verify_rsa_pkcs1_sha256(sd, sdo, sigptr, siglen, n2, e2[0])
488 }
489 if v2 == NX_X509_RSA_PKCS1_SHA256_OK { skev = NX_X509_RSA_PKCS1_SHA256_4096_OK }
490 else { skev = v2 }
491 } else {
492 let nmod: *i64 = u4096_alloc()
493 let ep: *i64 = sys_mmap(16) as *i64
494 if nx_x509_pubkey_extract_rsa_4096(leaf_ptr, leaf, nmod, ep) != NX_X509_PUBKEY_RSA_4096_OK {
495 t12_puts(" verdict=REFUSED rule=leaf-rsa-pubkey-extract-failed\n" as *u8); sys_close(fd); sys_exit(4); return 4
496 }
497 if sighash == 6 {
498 let r5b: i64 = rsa_pkcs1_v1_5_sha512_4096_verify(sd, sdo, sigptr, nmod, ep[0])
499 if r5b == NX_RSA_PKCS1_V15_512_OK { skev = NX_X509_RSA_PKCS1_SHA256_4096_OK } else { skev = r5b }
500 } else {
501 skev = nx_x509_verify_rsa_pkcs1_sha256_4096(sd, sdo, sigptr, siglen, nmod, ep[0])
502 }
503 }
504 if skev == NX_X509_RSA_PKCS1_SHA256_4096_OK {
505 authed = 1
506 t12_puts(" ske_sig=VALID (RSA-PKCS1-SHA" as *u8); if sighash == 6 { t12_puts("512" as *u8) } else { t12_puts("256" as *u8) }
507 t12_puts(", " as *u8); t12_putn(siglen)
508 t12_puts("B) -- the ephemeral key is BOUND to the validated certificate\n" as *u8)
509 } else {
510 t12_puts(" verdict=REFUSED rule=serverkeyexchange-signature-invalid v=" as *u8); t12_putn(skev)
511 t12_puts("\n" as *u8); sys_close(fd); sys_exit(4); return 4
512 }
513
514 // --- ECDHE: my ephemeral keypair + premaster ---
515 let seed: *u8 = sys_mmap(32); nx_csprng_fill(seed, 32)
516 let mypriv: *u8 = sys_mmap(32)
517 if p256_ecdh_derive_priv(seed, mypriv) != NX_P256_ECDH_OK { t12_puts(" RED: derive_priv\n" as *u8); sys_close(fd); return 1 }
518 let mypub: *u8 = sys_mmap(65)
519 if p256_ecdh_pub(mypriv, mypub) != NX_P256_ECDH_OK { t12_puts(" RED: pub\n" as *u8); sys_close(fd); return 1 }
520 let premaster: *u8 = sys_mmap(32)
521 if p256_ecdh_shared(mypriv, spub, 65, premaster) != NX_P256_ECDH_OK { t12_puts(" RED: ecdh_shared (bad server point?)\n" as *u8); sys_close(fd); return 1 }
522
523 // --- key schedule ---
524 let crsr: *u8 = sys_mmap(64); t12_bytes(crsr, 0, cr, 32); t12_bytes(crsr, 32, sr, 32) // client||server
525 let srcr: *u8 = sys_mmap(64); t12_bytes(srcr, 0, sr, 32); t12_bytes(srcr, 32, cr, 32) // server||client
526 let master: *u8 = sys_mmap(48)
527 t12_prf(premaster, 32, "master secret" as *u8, 13, crsr, 64, master, 48)
528 let kb: *u8 = sys_mmap(40)
529 t12_prf(master, 48, "key expansion" as *u8, 13, srcr, 64, kb, 40)
530 let cwk: *u8 = ((kb as i64)+0) as *u8 // client_write_key[16]
531 let swk: *u8 = ((kb as i64)+16) as *u8 // server_write_key[16]
532 let civ: *u8 = ((kb as i64)+32) as *u8 // client_write_iv[4]
533 let siv: *u8 = ((kb as i64)+36) as *u8 // server_write_iv[4]
534
535 // --- ClientKeyExchange (type 16): [pklen=65, mypub65] ; append to transcript, send plaintext ---
536 let cke: *u8 = sys_mmap(128); var ck: i64 = 0
537 ck = t12_b1(cke, ck, T12_HS); ck = t12_b2(cke, ck, 0x0303); ck = t12_b2(cke, ck, 4 + 66)
538 let cke_hs: i64 = ck
539 ck = t12_b1(cke, ck, 16); ck = t12_b3(cke, ck, 66); ck = t12_b1(cke, ck, 65); ck = t12_bytes(cke, ck, mypub, 65)
540 if t12_write_all(fd, cke, ck) < 0 { t12_puts(" RED: CKE write\n" as *u8); sys_close(fd); return 1 }
541 trlen = t12_bytes(tr, trlen, ((cke as i64)+cke_hs) as *u8, ck - cke_hs)
542
543 // --- ChangeCipherSpec ---
544 let ccs: *u8 = sys_mmap(8); var cc: i64=0
545 cc = t12_b1(ccs, cc, T12_CCS); cc = t12_b2(ccs, cc, 0x0303); cc = t12_b2(ccs, cc, 1); cc = t12_b1(ccs, cc, 1)
546 if t12_write_all(fd, ccs, cc) < 0 { t12_puts(" RED: CCS write\n" as *u8); sys_close(fd); return 1 }
547
548 // --- client Finished: verify_data = PRF(master,"client finished",SHA256(transcript),12) ---
549 let thash: *u8 = sys_mmap(32); sha256_digest(tr, trlen, thash)
550 let cvd: *u8 = sys_mmap(12); t12_prf(master, 48, "client finished" as *u8, 15, thash, 32, cvd, 12)
551 let fin: *u8 = sys_mmap(16); var fo: i64=0
552 fo = t12_b1(fin, fo, 20); fo = t12_b3(fin, fo, 12); fo = t12_bytes(fin, fo, cvd, 12) // Finished hs message (16B)
553 trlen = t12_bytes(tr, trlen, fin, 16) // include client Finished in transcript for server-Finished check
554 // GCM seal: nonce = civ(4)||seq0(8); aad = seq0(8)||type22||ver0303||len16
555 let nonce: *u8 = sys_mmap(12); var q: i64=0; while q<4 { nonce[q]=civ[q]; q=q+1 } while q<12 { nonce[q]=0; q=q+1 }
556 // AAD content-type = 22 (record type = handshake). NB: 22 is the RECORD type; the Finished's handshake-message
557 // type is 20 (first plaintext byte) -- do not confuse them. Using 20 here => GCM tag never matches => server drops.
558 let aad: *u8 = sys_mmap(13); q=0; while q<8 { aad[q]=0; q=q+1 } aad[8]=22 as u8; aad[9]=3 as u8; aad[10]=3 as u8; aad[11]=0 as u8; aad[12]=16 as u8
559 let ct: *u8 = sys_mmap(16); let tag: *u8 = sys_mmap(16)
560 nx_aes128_gcm_seal(cwk, nonce, aad, 13, fin, 16, ct, tag)
561 let frec: *u8 = sys_mmap(64); var fr: i64=0
562 fr = t12_b1(frec, fr, T12_HS); fr = t12_b2(frec, fr, 0x0303); fr = t12_b2(frec, fr, 8+16+16)
563 q=0; while q<8 { frec[fr]=0 as u8; fr=fr+1; q=q+1 } // explicit nonce = seq 0
564 fr = t12_bytes(frec, fr, ct, 16); fr = t12_bytes(frec, fr, tag, 16)
565 if t12_write_all(fd, frec, fr) < 0 { t12_puts(" RED: Finished write\n" as *u8); sys_close(fd); return 1 }
566 t12_puts(" sent ClientKeyExchange + CCS + encrypted Finished\n" as *u8)
567
568 // --- read server response: expect CCS + encrypted Finished (or an ALERT if our keys are wrong) ---
569 let rbuf: *u8 = sys_mmap(T12_MAGIC_16384); var rtot: i64 = 0; var rr: i64 = 0; var server_done: i64 = 0
570 while server_done == 0 {
571 if rr > 30 { server_done = 2 }
572 let n: i64 = sys_read(fd, ((rbuf as i64)+rtot) as *u8, T12_MAGIC_16384-rtot)
573 if n <= 0 { server_done = 2 } else {
574 rtot = rtot + n; rr = rr + 1
575 var pp: i64 = 0
576 while pp + 5 <= rtot {
577 let rtype: i64 = rbuf[pp]&0xff
578 let rlen: i64 = ((rbuf[pp+3]&0xff)<<8)|(rbuf[pp+4]&0xff)
579 if pp+5+rlen > rtot { pp = rtot } else {
580 if rtype == T12_ALERT { server_done = 3 } // alert -> keys/finished rejected
581 if rtype == T12_HS { server_done = 1 } // encrypted Finished record arrived
582 pp = pp + 5 + rlen
583 }
584 }
585 }
586 }
587 // diagnostic: what did the server actually send back?
588 t12_puts(" server response: " as *u8); t12_putn(rtot); t12_puts(" bytes, first record type=" as *u8)
589 if rtot >= 1 { t12_putn(rbuf[0]&0xff) } else { t12_puts("none(closed)" as *u8) }
590 if rtot >= 5 { t12_puts(" reclen=" as *u8); t12_putn(((rbuf[3]&0xff)<<8)|(rbuf[4]&0xff)); t12_puts(" bytes[0..7]=" as *u8); var dz: i64=0; while dz<8 { if dz<rtot { t12_puthex(rbuf[dz]&0xff) } dz=dz+1 } }
591 t12_puts("\n" as *u8)
592 // walk the response
593 var pp: i64 = 0
594 var verdict_ok: i64 = 0
595 while pp + 5 <= rtot {
596 let rtype: i64 = rbuf[pp]&0xff
597 let rlen: i64 = ((rbuf[pp+3]&0xff)<<8)|(rbuf[pp+4]&0xff)
598 if rtype == T12_ALERT {
599 t12_puts(" server ALERT level=" as *u8); t12_putn(rbuf[pp+5]&0xff); t12_puts(" desc=" as *u8); t12_putn(rbuf[pp+6]&0xff)
600 t12_puts(" (20=bad_record_mac 51=decrypt_error 40=handshake_failure => our key schedule is wrong)\n" as *u8)
601 }
602 if rtype == T12_HS {
603 // decrypt server Finished: explicit(8)||ct||tag ; nonce=siv||explicit ; aad=seq0||22||0303||ptlen
604 let ptlen: i64 = rlen - 8 - 16
605 let snonce: i64 = pp + 5
606 let sct: i64 = pp + 5 + 8
607 let stag: i64 = pp + 5 + 8 + ptlen
608 let snon: *u8 = sys_mmap(12); var z: i64=0; while z<4 { snon[z]=siv[z]; z=z+1 } while z<12 { snon[z]=rbuf[snonce + (z-4)]; z=z+1 }
609 let saad: *u8 = sys_mmap(13); z=0; while z<8 { saad[z]=0; z=z+1 } saad[8]=22 as u8; saad[9]=3 as u8; saad[10]=3 as u8; saad[11]=((ptlen>>8)&0xff) as u8; saad[12]=(ptlen&0xff) as u8
610 let spt: *u8 = sys_mmap(256); let stg: *u8 = sys_mmap(16); z=0; while z<16 { stg[z]=rbuf[stag+z]; z=z+1 }
611 let ov: i64 = nx_aes128_gcm_open(swk, snon, saad, 13, ((rbuf as i64)+sct) as *u8, ptlen, stg, spt)
612 if ov != 0 { t12_puts(" RED: server Finished GCM tag FAILED to verify (keys mismatch)\n" as *u8) } else {
613 // spt = [20, 00 00 0c, server_verify_data(12)] ; check against PRF("server finished", SHA256(transcript+clientFinished))
614 let sh2: *u8 = sys_mmap(32); sha256_digest(tr, trlen, sh2)
615 let svd: *u8 = sys_mmap(12); t12_prf(master, 48, "server finished" as *u8, 15, sh2, 32, svd, 12)
616 var vmatch: i64 = 1; z=0; while z<12 { if spt[4+z] != svd[z] { vmatch = 0 } z=z+1 }
617 if vmatch == 1 { verdict_ok = 1 } else { t12_puts(" RED: server Finished decrypted but verify_data MISMATCH\n" as *u8) }
618 }
619 }
620 pp = pp + 5 + rlen
621 }
622 if verdict_ok == 1 {
623 if authed == 1 {
624 t12_puts(" verdict=AUTHENTICATED: handshake complete AND the peer is PROVEN -- X.509 chain + SAN/\n" as *u8)
625 t12_puts(" hostname + validity verified vs the Mozilla trust store, and the ServerKeyExchange signature\n" as *u8)
626 t12_puts(" binds THIS ephemeral key to that certificate. A man-in-the-middle cannot produce this.\n" as *u8)
627 } else {
628 t12_puts(" verdict=HANDSHAKE-OK-BUT-UNAUTHENTICATED: confidential but the PEER IS UNPROVEN.\n" as *u8)
629 t12_puts(" A man-in-the-middle would produce EXACTLY this output. Do NOT carry real data over it.\n" as *u8)
630 }
631 // FAIL-CLOSED BY CONSTRUCTION (2026-07-25): refuse to fetch or print application data unless the caller
632 // types the explicit opt-in. The previous build printed the body and called it an "AUTHENTICATED HTTP
633 // response" -- it is not authenticated in any sense that matters, and that wording is exactly what would
634 // get an unauthenticated channel wired into a live data feed. A warning comment cannot prevent that; a
635 // refusal can. See CLAUDE rule 12 (defensive at boundaries) + the cynical-instruments law.
636 var unauth_ok: i64 = 0
637 if argc >= 3 {
638 let f: *u8 = argv[2] as *u8
639 if f[0]==(45 as u8) { if f[1]==(45 as u8) { unauth_ok = 1 } }
640 }
641 if authed == 0 {
642 if unauth_ok == 0 {
643 t12_puts(" REFUSED: app-data withheld. Pass --unauthenticated-ok to override (diagnostic ONLY).\n" as *u8)
644 t12_puts(" verdict=REFUSED rule=unauthenticated-channel-must-not-carry-real-data\n" as *u8)
645 sys_exit(4)
646 return 4
647 }
648 }
649 t12_puts(" --unauthenticated-ok given: proceeding for DIAGNOSTIC purposes only.\n" as *u8)
650 // ---- INC3: HTTP GET over the encrypted channel (app-data, content type 23). Client write seq starts at 1
651 // (Finished was seq 0); server read seq likewise starts at 1 (server Finished was its seq 0). ----
652 // ---- 2026-07-25 TWO BUGS FIXED HERE ----
653 // (1) The request line was HARDCODED to "GET /api/v0/users/current/" (left over from a vast.ai
654 // origin), so EVERY fetch hit the wrong resource regardless of the URL on argv -- which is why
655 // treasury/usgs answered 404 and bls answered 301. Now it uses the URL's real path + query.
656 // (2) SECURITY: it unconditionally read a Bearer token from a vault path and sent it as an
657 // Authorization header to WHATEVER host was on argv = credential exfiltration by construction.
658 // MEASURED: the vault dir does not exist on this host, so t12_readfile returned 0 and the header
659 // was empty -- nothing actually leaked -- but the shape would leak wherever that path resolves.
660 // A per-host opt-in is the only correct way to attach a credential; a blanket header is never it.
661 var gp: *u8 = ((url as i64) + target.url.path_off) as *u8
662 var gplen: i64 = target.url.path_len
663 if gplen == 0 { gp = "/" as *u8; gplen = 1 }
664 let reqb: *u8 = sys_mmap(T12_MAGIC_4096); var rq: i64 = 0
665 rq = t12_bytes(reqb, rq, "GET " as *u8, 4)
666 rq = t12_bytes(reqb, rq, gp, gplen)
667 if target.url.query_len > 0 {
668 rq = t12_bytes(reqb, rq, "?" as *u8, 1)
669 rq = t12_bytes(reqb, rq, ((url as i64) + target.url.query_off) as *u8, target.url.query_len)
670 }
671 let hl1: *u8 = " HTTP/1.1\r\nHost: " as *u8
672 let hl2: *u8 = "\r\nUser-Agent: nishi-tls12\r\nAccept: */*\r\nConnection: close\r\n\r\n" as *u8
673 rq = t12_bytes(reqb, rq, hl1, t12_strlen(hl1))
674 rq = t12_bytes(reqb, rq, host, hlen)
675 rq = t12_bytes(reqb, rq, hl2, t12_strlen(hl2))
676 let anonce: *u8 = sys_mmap(12); var az: i64=0; while az<4 { anonce[az]=civ[az]; az=az+1 } t12_u64be(anonce, 4, 1)
677 let aaad: *u8 = sys_mmap(13); t12_u64be(aaad, 0, 1); aaad[8]=23 as u8; aaad[9]=3 as u8; aaad[10]=3 as u8; aaad[11]=((rq>>8)&0xff) as u8; aaad[12]=(rq&0xff) as u8
678 let act: *u8 = sys_mmap(T12_MAGIC_1200); let atag: *u8 = sys_mmap(16)
679 nx_aes128_gcm_seal(cwk, anonce, aaad, 13, reqb, rq, act, atag)
680 let arec: *u8 = sys_mmap(T12_MAGIC_1400); var ar: i64=0
681 ar = t12_b1(arec, ar, 23); ar = t12_b2(arec, ar, 0x0303); ar = t12_b2(arec, ar, 8+rq+16)
682 ar = t12_u64be(arec, ar, 1); ar = t12_bytes(arec, ar, act, rq); ar = t12_bytes(arec, ar, atag, 16)
683 let wrc: i64 = sys_write(fd, arec, ar)
684 t12_puts(" app-data GET write rc=" as *u8); t12_putn(wrc); t12_puts(" (record " as *u8); t12_putn(ar); t12_puts("B, " as *u8); t12_putn(rq); t12_puts("B plaintext); reading response...\n" as *u8)
685 // read + decrypt server app-data INCREMENTALLY, stopping as soon as we have a COMPLETE HTTP response
686 // (headers + Content-Length body). This never blocks on a kept-open connection -- the correct fix, since
687 // SO_RCVTIMEO is not honored in this WSL kernel (a plain fixed-count read loop would block until the alarm).
688 let respbuf: *u8 = sys_mmap(T12_MAGIC_131072); var resptot: i64 = 0
689 let plainout: *u8 = sys_mmap(T12_MAGIC_131072); var plain_total: i64 = 0
690 var rp: i64 = 0; var rseq: i64 = 1
691 var reading: i64 = 1; var reads: i64 = 0
692 while reading == 1 {
693 // decrypt every COMPLETE TLS record currently buffered (rp only advances past finished records)
694 var more: i64 = 1
695 while more == 1 {
696 if rp + 5 > resptot { more = 0 } else {
697 let rt: i64 = respbuf[rp]&0xff
698 let rl: i64 = ((respbuf[rp+3]&0xff)<<8)|(respbuf[rp+4]&0xff)
699 if rp+5+rl > resptot { more = 0 } else {
700 if rt == 21 { reading = 0 } // alert -> stop
701 if rt == 23 {
702 let pl: i64 = rl - 8 - 16
703 let dn: *u8 = sys_mmap(12); var dz: i64=0; while dz<4 { dn[dz]=siv[dz]; dz=dz+1 } while dz<12 { dn[dz]=respbuf[rp+5+(dz-4)]; dz=dz+1 }
704 let dad: *u8 = sys_mmap(13); t12_u64be(dad, 0, rseq); dad[8]=23 as u8; dad[9]=3 as u8; dad[10]=3 as u8; dad[11]=((pl>>8)&0xff) as u8; dad[12]=(pl&0xff) as u8
705 let dtag: *u8 = sys_mmap(16); dz=0; while dz<16 { dtag[dz]=respbuf[rp+5+8+pl+dz]; dz=dz+1 }
706 let dpt: *u8 = sys_mmap(T12_MAGIC_65536)
707 if nx_aes128_gcm_open(swk, dn, dad, 13, ((respbuf as i64)+rp+5+8) as *u8, pl, dtag, dpt) == 0 {
708 var cz: i64=0; while cz<pl { plainout[plain_total+cz]=dpt[cz]; cz=cz+1 } plain_total=plain_total+pl
709 }
710 rseq = rseq + 1
711 }
712 rp = rp + 5 + rl
713 }
714 }
715 }
716 // complete HTTP response yet? (headers + full Content-Length body) -> done, don't wait for close
717 let he: i64 = t12_hdr_end(plainout, plain_total)
718 if he >= 0 {
719 let cl: i64 = t12_content_length(plainout, plain_total)
720 // Content-Length present => stop at the exact body length. ABSENT => chunked (or
721 // close-delimited), so KEEP READING until the peer closes. The old code stopped on the
722 // FIRST read, which is exactly why chunked bodies arrived truncated.
723 if cl >= 0 { if plain_total - he >= cl { reading = 0 } }
724 }
725 if reading == 1 {
726 reads = reads + 1
727 if reads > 40 { reading = 0 }
728 let n: i64 = sys_read(fd, ((respbuf as i64)+resptot) as *u8, T12_MAGIC_131072-resptot)
729 if n <= 0 { reading = 0 } else { resptot = resptot + n }
730 }
731 }
732 sys_close(fd)
733 t12_puts(" decrypted " as *u8); t12_putn(plain_total); t12_puts(" bytes of DECRYPTED (unauthenticated-peer) HTTP response:\n" as *u8)
734 t12_puts("---------------------------------------------\n" as *u8)
735 var emitted: i64 = 0
736 let hdre: i64 = t12_hdr_end(plainout, plain_total)
737 if hdre >= 0 {
738 if t12_content_length(plainout, plain_total) < 0 {
739 let dech: *u8 = sys_mmap(T12_MAGIC_131072)
740 let dn2: i64 = t12_dechunk(plainout, plain_total, hdre + 4, dech)
741 if dn2 > 0 {
742 sys_write(1, plainout, hdre + 4)
743 sys_write(1, dech, dn2)
744 emitted = 1
745 }
746 }
747 }
748 if emitted == 0 { sys_write(1, plainout, plain_total) }
749 t12_puts("\n---------------------------------------------\n" as *u8)
750 if plain_total > 0 { t12_puts(" verdict=FETCH-OK: sovereign TLS-1.2 GET decrypted real HTTP from the URL on argv (peer state above)\n" as *u8); return 0 }
751 t12_puts(" verdict=PARTIAL: handshake ok but no app-data decrypted\n" as *u8)
752 return 0
753 }
754 sys_close(fd)
755 t12_puts(" verdict=RED/PARTIAL: handshake did not confirm (see above)\n" as *u8)
756 return 1
757}