code wiki / (root) / nx_tls12_client.nx

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}