code wiki / (root) / nx_tls12_probe.nx

nx_tls12_probe.nx source

↩ module page · 209 lines · 11925 B

1// nx_tls12_probe.nx -- INCREMENT 1 of the sovereign TLS-1.2 client (the keystone that gets nx_gpu_ctl + Porkbun 2// off curl/wsl and onto sovereign APIs; Vast's console.vast.ai + Porkbun are TLS-1.2-ONLY and our shipped stack is 3// TLS-1.3-only). This probe proves the WIRE + PARSE layer: TCP-connect, send a real TLS-1.2 ClientHello offering 4// TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xC02F) + secp256r1, then read+parse the server's flight 5// (ServerHello / Certificate / ServerKeyExchange / ServerHelloDone) and print what we recovered: negotiated cipher, 6// server_random, cert-chain length, and the server's ECDHE public key. Composes the SAME connect primitive the 7// 1.3 client uses (nx_https_url_connect) so the transport is identical; TLS-1.2 handshake records are PLAINTEXT 8// until ChangeCipherSpec, so the probe reads them raw. Increment 2 adds ECDHE+PRF+Finished; 3 adds GCM records. 9// license_tier: ORIGINAL expect_exit: 0 10import "nx_syscalls.nx" 11import "nx_csprng.nx" 12import "nx_https_url_for_fetch.nx" 13import "nx_https_url_connect.nx" 14const P12_MAGIC_1024: i64 = 1024 15const P12_MAGIC_2048: i64 = 2048 16const P12_MAGIC_65536: i64 = 65536 17 18const P12_HS: i64 = 22 // record type handshake 19const P12_ALERT: i64 = 21 20 21func p12_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 22func p12_putn(v: i64) -> i64 { 23 let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} 24 let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1} 25 while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 26 var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 27} 28func p12_puthex(byte: i64) -> i64 { 29 let hx: *u8="0123456789abcdef" as *u8; let o: *u8=sys_mmap(2) 30 o[0]=hx[(byte>>4)&0xf] as u8; o[1]=hx[byte&0xf] as u8; sys_write(1,o,2); return 0 31} 32func p12_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 33 34// append helpers into a build buffer 35func p12_b1(d: *u8, o: i64, v: i64) -> i64 { d[o]=(v & 0xff) as u8; return o+1 } 36func p12_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 } 37func p12_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 } 38func p12_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 } 39 40// Build a TLS 1.2 ClientHello (as a full record) into out. Returns total record length. 41// host/hlen = SNI. client_random(32) filled from CSPRNG (copied to cr_out for later PRF use). 42func p12_build_client_hello(host: *u8, hlen: i64, cr_out: *u8, out: *u8) -> i64 { 43 // ---- assemble ClientHello body first (we backfill lengths) ---- 44 let body: *u8 = sys_mmap(P12_MAGIC_1024) 45 var o: i64 = 0 46 o = p12_b2(body, o, 0x0303) // client_version TLS 1.2 47 nx_csprng_fill(cr_out, 32) 48 o = p12_bytes(body, o, cr_out, 32) // random(32) 49 o = p12_b1(body, o, 0) // session_id length 0 50 o = p12_b2(body, o, 2) // cipher_suites length = 2 51 o = p12_b2(body, o, 0xC02F) // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 52 o = p12_b1(body, o, 1) // compression methods length 53 o = p12_b1(body, o, 0) // null compression 54 // ---- extensions ---- 55 let ext: *u8 = sys_mmap(512) 56 var e: i64 = 0 57 // server_name (0x0000) 58 let snlen: i64 = hlen 59 e = p12_b2(ext, e, 0x0000) 60 e = p12_b2(ext, e, snlen + 5) // ext data len 61 e = p12_b2(ext, e, snlen + 3) // server_name_list len 62 e = p12_b1(ext, e, 0) // name_type host_name 63 e = p12_b2(ext, e, snlen) // host len 64 e = p12_bytes(ext, e, host, snlen) 65 // supported_groups (0x000A) -> secp256r1 (0x0017) 66 e = p12_b2(ext, e, 0x000A); e = p12_b2(ext, e, 4); e = p12_b2(ext, e, 2); e = p12_b2(ext, e, 0x0017) 67 // ec_point_formats (0x000B) -> uncompressed (0) 68 e = p12_b2(ext, e, 0x000B); e = p12_b2(ext, e, 2); e = p12_b1(ext, e, 1); e = p12_b1(ext, e, 0) 69 // signature_algorithms (0x000D) -> rsa_pkcs1_sha256(0x0401), rsa_pkcs1_sha384(0x0501), rsa_pkcs1_sha512(0x0601) 70 e = p12_b2(ext, e, 0x000D); e = p12_b2(ext, e, 8); e = p12_b2(ext, e, 6) 71 e = p12_b2(ext, e, 0x0401); e = p12_b2(ext, e, 0x0501); e = p12_b2(ext, e, 0x0601) 72 // extensions block 73 o = p12_b2(body, o, e) // extensions length 74 o = p12_bytes(body, o, ext, e) 75 let body_len: i64 = o 76 // ---- wrap: handshake header (type 1 = ClientHello) then record header ---- 77 var r: i64 = 0 78 r = p12_b1(out, r, P12_HS) // record type handshake 79 r = p12_b2(out, r, 0x0303) // record version 80 r = p12_b2(out, r, body_len + 4) // record length = hs header(4)+body 81 r = p12_b1(out, r, 1) // handshake type ClientHello 82 r = p12_b3(out, r, body_len) // handshake length 83 r = p12_bytes(out, r, body, body_len) 84 return r 85} 86 87func p12_write_all(fd: i64, buf: *u8, n: i64) -> i64 { 88 var off: i64=0 89 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 } 90 return 0 91} 92 93func main(argc: i64, argv: *i64) -> i64 { 94 var url: *u8 = "https://console.vast.ai/" as *u8 95 if argc >= 2 { url = argv[1] as *u8 } 96 p12_puts("=== sovereign TLS-1.2 probe -> " as *u8); p12_puts(url); p12_puts(" ===\n" as *u8) 97 98 // ---- TCP connect (reuse the 1.3 client's connect primitive; TLS is ours) ---- 99 let target_raw: *u8 = sys_mmap(64); let target: *NxHttpsTarget = target_raw as *NxHttpsTarget 100 target.url = nx_url_new(); target.port = 0 101 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { p12_puts("bad url\n" as *u8); return 1 } 102 let host: *u8 = ((url as i64) + target.url.host_off) as *u8 103 let hlen: i64 = target.url.host_len 104 let now: i64 = sys_now_realtime_sec() 105 let fd_p: *i64 = sys_mmap(16) as *i64 106 if nx_https_url_connect(target, url, now, fd_p) != NX_HTTPS_CONNECT_OK { p12_puts("TCP connect FAILED\n" as *u8); return 1 } 107 let fd: i64 = fd_p[0] 108 p12_puts(" TCP connected fd=" as *u8); p12_putn(fd); p12_puts(" host=" as *u8); sys_write(1, host, hlen); p12_puts("\n" as *u8) 109 110 // ---- send ClientHello ---- 111 let cr: *u8 = sys_mmap(32) 112 let ch: *u8 = sys_mmap(P12_MAGIC_2048) 113 let chlen: i64 = p12_build_client_hello(host, hlen, cr, ch) 114 if p12_write_all(fd, ch, chlen) < 0 { p12_puts("ClientHello write FAILED\n" as *u8); sys_close(fd); return 1 } 115 p12_puts(" ClientHello sent (" as *u8); p12_putn(chlen); p12_puts(" bytes; cipher=0xC02F ECDHE-RSA-AES128-GCM-SHA256)\n" as *u8) 116 117 // ---- read server flight into one buffer ---- 118 let buf: *u8 = sys_mmap(P12_MAGIC_65536) 119 var total: i64 = 0 120 var reads: i64 = 0 121 var done: i64 = 0 122 while done == 0 { 123 if reads > 40 { done = 1 } 124 let n: i64 = sys_read(fd, ((buf as i64)+total) as *u8, P12_MAGIC_65536 - total) 125 if n <= 0 { done = 1 } else { 126 total = total + n 127 reads = reads + 1 128 // stop once we've parsed a ServerHelloDone (14) at the handshake layer; cheap scan below 129 // (we re-scan each loop; small flights, fine) 130 var scanned: i64 = 0; var seen_shd: i64 = 0 131 while scanned + 5 <= total { 132 let rtype: i64 = buf[scanned] & 0xff 133 let rlen: i64 = ((buf[scanned+3] & 0xff) << 8) | (buf[scanned+4] & 0xff) 134 if rtype == P12_ALERT { done = 1 } 135 // walk handshake messages inside a handshake record 136 if rtype == P12_HS { 137 var hp: i64 = scanned + 5; let rend: i64 = scanned + 5 + rlen 138 while hp + 4 <= rend { 139 let ht: i64 = buf[hp] & 0xff 140 let hl: i64 = ((buf[hp+1]&0xff)<<16)|((buf[hp+2]&0xff)<<8)|(buf[hp+3]&0xff) 141 if ht == 14 { seen_shd = 1 } 142 hp = hp + 4 + hl 143 } 144 } 145 scanned = scanned + 5 + rlen 146 } 147 if seen_shd == 1 { done = 1 } 148 } 149 } 150 p12_puts(" received " as *u8); p12_putn(total); p12_puts(" bytes in " as *u8); p12_putn(reads); p12_puts(" read(s)\n" as *u8) 151 if total < 5 { p12_puts(" verdict=RED: no TLS flight (server closed?)\n" as *u8); sys_close(fd); return 1 } 152 153 // ---- parse the flight: records -> handshake messages ---- 154 var p: i64 = 0 155 var got_sh: i64 = 0; var got_cert: i64 = 0; var got_ske: i64 = 0; var got_shd: i64 = 0 156 var cert_total: i64 = 0 157 while p + 5 <= total { 158 let rtype: i64 = buf[p] & 0xff 159 let rlen: i64 = ((buf[p+3]&0xff)<<8) | (buf[p+4]&0xff) 160 if rtype == P12_ALERT { 161 p12_puts(" ALERT: level=" as *u8); p12_putn(buf[p+5]&0xff); p12_puts(" desc=" as *u8); p12_putn(buf[p+6]&0xff); p12_puts("\n" as *u8) 162 } 163 if rtype == P12_HS { 164 var hp: i64 = p + 5; let rend: i64 = p + 5 + rlen 165 while hp + 4 <= rend { 166 let ht: i64 = buf[hp] & 0xff 167 let hl: i64 = ((buf[hp+1]&0xff)<<16)|((buf[hp+2]&0xff)<<8)|(buf[hp+3]&0xff) 168 let bodyp: i64 = hp + 4 169 if ht == 2 { // ServerHello 170 got_sh = 1 171 // body: version(2) random(32) sid_len(1) sid... cipher(2) comp(1) ext... 172 p12_puts(" ServerHello: version=0x" as *u8); p12_puthex(buf[bodyp]&0xff); p12_puthex(buf[bodyp+1]&0xff) 173 let sidlen: i64 = buf[bodyp+34] & 0xff 174 let cipoff: i64 = bodyp + 35 + sidlen 175 let cip: i64 = ((buf[cipoff]&0xff)<<8) | (buf[cipoff+1]&0xff) 176 p12_puts(" cipher=0x" as *u8); p12_puthex((cip>>8)&0xff); p12_puthex(cip&0xff) 177 p12_puts(" server_random[0..4]=" as *u8) 178 var z: i64=0; while z<4 { p12_puthex(buf[bodyp+2+z]&0xff); z=z+1 } 179 p12_puts("\n" as *u8) 180 } 181 if ht == 11 { got_cert = 1; cert_total = hl } // Certificate 182 if ht == 12 { // ServerKeyExchange: curve_type(1) named_curve(2) pklen(1) pubkey... 183 got_ske = 1 184 let ctype: i64 = buf[bodyp] & 0xff 185 let curve: i64 = ((buf[bodyp+1]&0xff)<<8) | (buf[bodyp+2]&0xff) 186 let pklen: i64 = buf[bodyp+3] & 0xff 187 p12_puts(" ServerKeyExchange: curve_type=" as *u8); p12_putn(ctype) 188 p12_puts(" named_curve=0x" as *u8); p12_puthex((curve>>8)&0xff); p12_puthex(curve&0xff) 189 p12_puts(" ecdhe_pubkey_len=" as *u8); p12_putn(pklen); p12_puts(" pub[0]=0x" as *u8); p12_puthex(buf[bodyp+4]&0xff) 190 p12_puts("\n" as *u8) 191 } 192 if ht == 14 { got_shd = 1 } // ServerHelloDone 193 hp = hp + 4 + hl 194 } 195 } 196 p = p + 5 + rlen 197 } 198 p12_puts(" parsed: ServerHello=" as *u8); p12_putn(got_sh) 199 p12_puts(" Certificate=" as *u8); p12_putn(got_cert); p12_puts("(len " as *u8); p12_putn(cert_total); p12_puts(")" as *u8) 200 p12_puts(" ServerKeyExchange=" as *u8); p12_putn(got_ske) 201 p12_puts(" ServerHelloDone=" as *u8); p12_putn(got_shd); p12_puts("\n" as *u8) 202 sys_close(fd) 203 if got_sh == 1 { if got_ske == 1 { if got_shd == 1 { 204 p12_puts(" verdict=GREEN: sovereign TLS-1.2 WIRE+PARSE works -- server's ECDHE flight recovered. Increment 2 = ECDHE+PRF+Finished.\n" as *u8) 205 return 0 206 } } } 207 p12_puts(" verdict=PARTIAL: connected+sent but did not recover full ECDHE flight (see above)\n" as *u8) 208 return 0 209}