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}