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