code wiki / _hdl_build / nx_tls12_handshake_gate.nx
nx_tls12_handshake_gate.nx source
↩ module page · 248 lines · 11117 B
1// nx_tls12_handshake_gate.nx -- SOVEREIGN, LIVE gate for PHASE 2 of the
2// TLS 1.2 client: a FULL handshake (ECDHE-RSA-AES128-GCM-SHA256) to a
3// real TLS-1.2-max host, proving the encrypted session works end-to-end.
4//
5// PRIMARY TARGET = api.porkbun.com (the real goal: a TLS-1.2-max host our
6// 1.3-only client cannot reach; needed for nx_porkbun_set_txt -> ACME
7// wildcard renew). The gate:
8// 1. Loads the real Mozilla CA bundle (data/mozilla_certdata.txt) into
9// a TrustStore.
10// 2. Connects + runs the full handshake (production path, no fault):
11// ClientHello -> server flight -> CERT-CHAIN VALIDATE -> SKE VERIFY
12// -> ECDHE -> key schedule -> ClientKeyExchange + ChangeCipherSpec
13// + encrypted client Finished -> server ChangeCipherSpec + encrypted
14// server Finished VERIFY. MUST reach CONNECTED.
15// 3. Over the established session sends a real
16// GET / HTTP/1.1\r\nHost: <h>\r\nConnection: close\r\n\r\n
17// decrypts the response, and asserts it begins with "HTTP/1.1 "
18// (ANY status proves the encrypted session works).
19// 4. NEG-CONTROLS (liar-kill -- the session MUST FAIL CLOSED if any
20// security check is bypassed):
21// a. EMPTY trust store -> cert validation FAILS -> no session
22// b. tampered SKE signature -> SKE verify FAILS -> no session
23// c. tampered server Finished -> Finished MAC FAILS -> no session
24//
25// expect_exit: 0
26// license_tier: ORIGINAL
27
28import "nx_syscalls.nx"
29import "nx_url.nx"
30import "nx_https_url_for_fetch.nx"
31import "nx_https_url_connect.nx"
32import "nx_x509_trust_store.nx"
33import "nx_trust_store_load_from_certdata.nx"
34import "nx_tls13_client_validate_certificate.nx"
35import "nx_tls12_client_session.nx"
36
37func g_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
38func g_n(v: i64) -> i64 {
39 var m: i64=v
40 if m<0 { g_w("-" as *u8); m=0-m }
41 let t: *u8=sys_mmap(24); var k: i64=0
42 if m==0 { t[0]=48 as u8; k=1 }
43 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
44 let o: *u8=sys_mmap(24); var i: i64=0
45 while i<k { o[i]=t[k-1-i]; i=i+1 }
46 sys_write(1,o,k); return 0
47}
48func g_row(id: *u8, ok: i64, pass: *i64) -> i64 {
49 g_w(" " as *u8); g_w(id); g_w(": " as *u8)
50 if ok==1 { g_w("OK\n" as *u8); pass[0]=pass[0]+1 } else { g_w("FAIL\n" as *u8) }
51 return 0
52}
53func g_cpy(dst: *u8, off: i64, s: *u8) -> i64 {
54 var n: i64=0
55 while s[n]!=(0 as u8) { dst[off+n]=s[n]; n=n+1 }
56 return off+n
57}
58func g_startswith(buf: *u8, n: i64, pat: *u8) -> i64 {
59 var i: i64=0
60 while pat[i]!=(0 as u8) {
61 if i>=n { return 0 }
62 if buf[i]!=pat[i] { return 0 }
63 i=i+1
64 }
65 return 1
66}
67
68// Connect to `url`, build a validation context bound to `store`, and run
69// the handshake with the given fault selector. Writes the connected fd to
70// fd_out[0] and the run result (positive session ptr | negative verdict)
71// is the return. host_out[0]/hostlen_out[0] expose the SNI host for the
72// positive path's HTTP request. Returns -9001/-9002 on connect failure.
73func g_handshake(url: *u8, store: *TrustStore, fault: i64,
74 fd_out: *i64, host_out: *i64, hostlen_out: *i64) -> i64 {
75 let url_p: *NxUrl = nx_url_new()
76 let target: *NxHttpsTarget = sys_mmap(32) as *NxHttpsTarget
77 target.url = url_p
78 target.port = 0
79 if nx_https_url_for_fetch(url, target) != NX_HTTPS_URL_OK { return 0 - 9001 }
80 let fd_p: *i64 = sys_mmap(16) as *i64
81 if nx_https_url_connect(target, url, 1781100037 + fault, fd_p) != NX_HTTPS_CONNECT_OK { return 0 - 9002 }
82 let fd: i64 = *fd_p
83 sys_set_socket_timeout(fd, 15)
84 let host: *u8 = (url as i64 + target.url.host_off) as *u8
85 let host_len: i64 = target.url.host_len
86 fd_out[0] = fd
87 host_out[0] = host as i64
88 hostlen_out[0] = host_len
89
90 let cr: *u8 = sys_mmap(32)
91 var i: i64=0
92 while i<32 { cr[i]=(0xC0+i) as u8; i=i+1 }
93 let seed: *u8 = sys_mmap(32)
94 i=0
95 while i<32 { seed[i]=(0x31+i) as u8; i=i+1 }
96
97 let vc: *TlsValidationContext = sys_mmap(64) as *TlsValidationContext
98 vc.store = store
99 vc.sni_host = host
100 vc.sni_host_len = host_len
101 vc.now_epoch = sys_now_realtime_sec()
102
103 return nx_tls12_client_session_run_faulted(fd, host, host_len, cr, seed, vc, fault)
104}
105
106// A neg-control passes ONLY if the handshake fails with the EXACT expected
107// security verdict (-want), proving that specific check gated the session
108// (not a transient network failure). Retries ride over transient connect/
109// flight-read flakiness; an actual ESTABLISHED session (r>0) despite the
110// tamper is an immediate hard FAIL (the security check was bypassed).
111func g_neg(url: *u8, store: *TrustStore, fault: i64, want: i64) -> i64 {
112 let fd_p: *i64 = sys_mmap(16) as *i64
113 let host_p: *i64 = sys_mmap(16) as *i64
114 let hlen_p: *i64 = sys_mmap(16) as *i64
115 var tries: i64 = 0
116 var observed: i64 = 0
117 while tries < 5 {
118 let r: i64 = g_handshake(url, store, fault, fd_p, host_p, hlen_p)
119 sys_close(fd_p[0])
120 observed = r
121 if r > 0 {
122 g_w(" session_run=") ; g_n(r); g_w(" (BYPASS!)\n" as *u8)
123 return 0
124 }
125 if r == 0 - want {
126 g_w(" session_run=") ; g_n(r); g_w(" (expected security verdict)\n" as *u8)
127 return 1
128 }
129 tries = tries + 1 // transient (connect/flight) -> retry
130 }
131 g_w(" session_run(after retries)=") ; g_n(observed); g_w(" (never hit the security check)\n" as *u8)
132 return 0
133}
134
135func main() -> i64 {
136 let pass: *i64 = sys_mmap(8) as *i64; pass[0]=0
137 var total: i64 = 0
138 g_w("=== NX-TLS12-HANDSHAKE GATE (LIVE full TLS 1.2 handshake; api.porkbun.com) ===\n" as *u8)
139
140 // ---- Load the real Mozilla CA trust store ----
141 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304)
142 if r <= 0 {
143 g_w(" trust store load FAILED (data/mozilla_certdata.txt) rc=" as *u8); g_n(r); g_w("\n" as *u8)
144 g_w("NX-TLS12-HANDSHAKE verdict=RED\n" as *u8); sys_exit(1); return 1
145 }
146 let store: *TrustStore = r as *TrustStore
147 let nca: i64 = trust_store_count(store)
148 g_w(" trust store CAs=" as *u8); g_n(nca); g_w("\n" as *u8)
149
150 let url: *u8 = "https://api.porkbun.com/\x00" as *u8
151
152 // =====================================================================
153 // 1) POSITIVE: full live handshake + encrypted HTTP round trip.
154 // =====================================================================
155 let fd_p: *i64 = sys_mmap(16) as *i64
156 let host_p: *i64 = sys_mmap(16) as *i64
157 let hlen_p: *i64 = sys_mmap(16) as *i64
158 g_w(" -- positive: live handshake to api.porkbun.com --\n" as *u8)
159 var sr: i64 = 0 - 1
160 var ptries: i64 = 0
161 while ptries < 5 {
162 sr = g_handshake(url, store, 0, fd_p, host_p, hlen_p)
163 if sr > 0 { break }
164 sys_close(fd_p[0]) // transient connect/flight failure -> reconnect
165 ptries = ptries + 1
166 }
167 g_w(" session_run=" as *u8); g_n(sr); g_w("\n" as *u8)
168 var http_ok: i64 = 0
169 var connected: i64 = 0
170 if sr > 0 {
171 connected = 1
172 let s: *Tls12ClientSession = sr as *Tls12ClientSession
173 let fd: i64 = fd_p[0]
174 let host: *u8 = host_p[0] as *u8
175 let host_len: i64 = hlen_p[0]
176
177 // Build GET / HTTP/1.1 request with the real Host header.
178 let req: *u8 = sys_mmap(512)
179 var q: i64 = 0
180 q = g_cpy(req, q, "GET / HTTP/1.1\r\nHost: " as *u8)
181 var hi: i64 = 0
182 while hi < host_len { req[q]=host[hi]; q=q+1; hi=hi+1 }
183 q = g_cpy(req, q, "\r\nConnection: close\r\n\r\n" as *u8)
184
185 let snd: i64 = nx_tls12_session_send(s, fd, req, q)
186 g_w(" app send rc=" as *u8); g_n(snd); g_w("\n" as *u8)
187 if snd == 0 {
188 // Drain the encrypted response until close_notify/EOF.
189 let acc: *u8 = sys_mmap(65536)
190 var acc_n: i64 = 0
191 let pt: *u8 = sys_mmap(20000)
192 let ctp: *i64 = sys_mmap(16) as *i64
193 var draining: i64 = 1
194 var rounds: i64 = 0
195 while draining == 1 {
196 if rounds >= 64 { draining = 0 }
197 else {
198 let pl: i64 = nx_tls12_session_recv(s, fd, pt, 20000, ctp)
199 rounds = rounds + 1
200 if pl < 0 { draining = 0 } // EOF / error -> stop
201 else {
202 if ctp[0] == 23 {
203 var j: i64 = 0
204 while j < pl {
205 if acc_n < 65536 { acc[acc_n]=pt[j]; acc_n=acc_n+1 }
206 j = j + 1
207 }
208 }
209 if ctp[0] == 21 { draining = 0 } // encrypted close_notify alert
210 }
211 }
212 }
213 g_w(" response bytes=" as *u8); g_n(acc_n); g_w("\n" as *u8)
214 // Print the status line for the record.
215 g_w(" RESPLINE=" as *u8)
216 var k: i64 = 0
217 while k < acc_n { if acc[k]==(13 as u8) { break } if k>=80 { break } sys_write(1,(acc as i64 + k) as *u8,1); k=k+1 }
218 g_w("\n" as *u8)
219 http_ok = g_startswith(acc, acc_n, "HTTP/1.1 " as *u8)
220 }
221 sys_close(fd)
222 }
223
224 g_row("LIVE: full TLS 1.2 handshake reaches CONNECTED (both Finished MACs verified)" as *u8, connected, pass); total=total+1
225 g_row("LIVE: encrypted GET over the session decrypts to an HTTP/1.1 response line" as *u8, http_ok, pass); total=total+1
226
227 // =====================================================================
228 // NEG-CONTROLS (liar-kill): each must fail with the EXACT security
229 // verdict that proves the corresponding mandatory check gated the
230 // session -- not a transient network failure (g_neg retries those).
231 // =====================================================================
232 g_w(" -- neg a: empty trust store (cert validation must gate) --\n" as *u8)
233 let empty_store: *TrustStore = trust_store_alloc(4)
234 let na_ok: i64 = g_neg(url, empty_store, 0, NX_TLS12_SESS_CERT_FAIL)
235 g_row("NEG: empty trust store -> cert chain validation FAILS (-CERT_FAIL), NO session" as *u8, na_ok, pass); total=total+1
236
237 g_w(" -- neg b: tampered ServerKeyExchange signature --\n" as *u8)
238 let nb_ok: i64 = g_neg(url, store, 2, NX_TLS12_SESS_SKE_VERIFY)
239 g_row("NEG: tampered SKE signature -> SKE RSA verify FAILS (-SKE_VERIFY), NO session" as *u8, nb_ok, pass); total=total+1
240
241 g_w(" -- neg c: tampered server Finished verify_data --\n" as *u8)
242 let nc_ok: i64 = g_neg(url, store, 1, NX_TLS12_SESS_SF_VERIFY)
243 g_row("NEG: tampered server Finished -> constant-time MAC FAILS (-SF_VERIFY), NO session" as *u8, nc_ok, pass); total=total+1
244
245 g_w("NX-TLS12-HANDSHAKE rows=" as *u8); g_n(total); g_w(" pass=" as *u8); g_n(pass[0])
246 if pass[0]==total { g_w(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
247 g_w(" verdict=RED\n" as *u8); sys_exit(1); return 1
248}