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}