code wiki / (root) / nx_tls12_mutant.nx

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}