code wiki / _hdl_build / nx_tls12_porkbun_e2e_gate.nx

nx_tls12_porkbun_e2e_gate.nx source

↩ module page · 205 lines · 9776 B

1// nx_tls12_porkbun_e2e_gate.nx -- PHASE 3 (final) LIVE e2e gate for the 2// sovereign TLS 1.3 -> 1.2 fallback transport. 3// 4// PROVES the exact capability the expired nishifamily.com wildcard cert 5// renewal needs: drive the REAL Porkbun DNS API over sovereign TLS 1.2 to 6// 1. CREATE a UNIQUE test TXT record _acme-challenge.nishifamily.com 7// -> assert the Porkbun API returns status SUCCESS (over TLS 1.2), then 8// 2. DELETE it -> assert SUCCESS. The delete ALWAYS runs (guaranteed 9// cleanup -- the test record is fully reversible). 10// The Porkbun calls go through nx_acme_req, which tries TLS 1.3 first, sees 11// api.porkbun.com refuse 1.3 (version-negotiation alert), reconnects, and 12// completes over the proven sovereign TLS 1.2 client. 13// 14// REGRESSION row 0: a TLS 1.3 host (the Let's Encrypt directory) must STILL 15// work through the SAME transport -- a harmless read-only GET (no account, 16// order, or challenge is created) that proves the 1.3 path is intact and the 17// 1.3-vs-1.2 selection is correct. This gate NEVER triggers an ACME 18// challenge; it stays entirely in the Porkbun / TLS layer. 19// 20// Creds come from the sovereign vault: run 21// ./_offc/nx_secret_cli.elf get porkbun 22// first (writes /tmp/nxsecret.out: line1=apikey, line2=secretapikey); this 23// gate reads that file (never a flat-file secret store, never hardcoded). 24// 25// expect_exit: 0 26// license_tier: ORIGINAL 27 28import "nx_syscalls.nx" 29import "nx_x509_trust_store.nx" 30import "nx_trust_store_load_from_certdata.nx" 31import "nx_acme_http.nx" 32import "nx_acme_porkbun.nx" 33import "nx_http_response_parse.nx" 34 35func 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 } 36func g_n(v: i64) -> i64 { 37 var m: i64=v 38 if m<0 { g_w("-" as *u8); m=0-m } 39 let t: *u8=sys_mmap(24); var k: i64=0 40 if m==0 { t[0]=48 as u8; k=1 } 41 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 42 let o: *u8=sys_mmap(24); var i: i64=0 43 while i<k { o[i]=t[k-1-i]; i=i+1 } 44 sys_write(1,o,k); return 0 45} 46func g_row(id: *u8, ok: i64, pass: *i64) -> i64 { 47 g_w(" " as *u8); g_w(id); g_w(": " as *u8) 48 if ok==1 { g_w("OK\n" as *u8); pass[0]=pass[0]+1 } else { g_w("FAIL\n" as *u8) } 49 return 0 50} 51// append decimal of v into buf at off; return new off 52func g_dec(buf: *u8, off: i64, v: i64) -> i64 { 53 var m: i64 = v 54 if m == 0 { buf[off]=48 as u8; return off+1 } 55 let t: *u8 = sys_mmap(24); var k: i64 = 0 56 while m > 0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 57 var o: i64 = off; var i: i64 = k-1 58 while i >= 0 { buf[o]=t[i]; o=o+1; i=i-1 } 59 return o 60} 61// read up to cap bytes of `path` into out; return bytes read or -1 62func e2e_read_file(path: *u8, out: *u8, cap: i64) -> i64 { 63 let fd: i64 = sys_openat_rd(path) 64 if fd <= 0 { return 0 - 1 } 65 let n: i64 = sys_read(fd, out, cap) 66 sys_close(fd) 67 return n 68} 69// print the JSON body of an HTTP response (falls back to whole buffer) 70func e2e_print_body(label: *u8, resp: *u8, n: i64) -> i64 { 71 g_w(label) 72 if n <= 0 { g_w("(empty)\n" as *u8); return 0 } 73 let r: *i64 = nx_http_resp_alloc() 74 if nx_http_response_parse(resp, n, r) == 0 { 75 let bo: i64 = r[6] 76 if bo >= 0 { if bo < n { 77 sys_write(1, (resp as i64 + bo) as *u8, n - bo); g_w("\n" as *u8); return 0 78 } } 79 } 80 sys_write(1, resp, n); g_w("\n" as *u8) 81 return 0 82} 83 84func main() -> i64 { 85 let pass: *i64 = sys_mmap(8) as *i64; pass[0]=0 86 var total: i64 = 0 87 g_w("=== NX-TLS12-PORKBUN-E2E GATE (LIVE Porkbun set+delete TXT over sovereign TLS 1.2; nishifamily.com) ===\n" as *u8) 88 89 // ---- real Mozilla CA trust store ---- 90 let lr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304) 91 if lr <= 0 { 92 g_w(" trust store load FAILED (data/mozilla_certdata.txt) rc=" as *u8); g_n(lr); g_w("\n" as *u8) 93 g_w("NX-TLS12-PORKBUN-E2E verdict=RED\n" as *u8); sys_exit(1); return 1 94 } 95 let store: *TrustStore = lr as *TrustStore 96 g_w(" trust store CAs=" as *u8); g_n(trust_store_count(store)); g_w("\n" as *u8) 97 98 let now: i64 = sys_now_realtime_sec() 99 100 // ---- creds from the sovereign vault (/tmp/nxsecret.out) ---- 101 let raw: *u8 = sys_mmap(512) 102 let rawn: i64 = e2e_read_file("/tmp/nxsecret.out\x00" as *u8, raw, 512) 103 if rawn <= 0 { 104 g_w(" VAULT creds read FAILED (/tmp/nxsecret.out missing -- run ./_offc/nx_secret_cli.elf get porkbun)\n" as *u8) 105 g_w("NX-TLS12-PORKBUN-E2E verdict=RED\n" as *u8); sys_exit(1); return 1 106 } 107 // Sanitize the vault bytes at the boundary: strip a leading UTF-8 BOM 108 // (EF BB BF) that the secret store prepends -- left in place it corrupts 109 // the FIRST field (apikey -> Porkbun "Invalid API key"). The secret on 110 // line 2 is unaffected. Also tolerate a trailing CR on either line. 111 var base: i64 = 0 112 if rawn >= 3 { if raw[0] == (0xEF as u8) { if raw[1] == (0xBB as u8) { if raw[2] == (0xBF as u8) { base = 3 } } } } 113 let creds: *u8 = (raw as i64 + base) as *u8 114 let clen: i64 = rawn - base 115 var nl: i64 = 0 116 while nl < clen { if creds[nl] == 0x0a { break }; nl = nl + 1 } 117 var ak_end: i64 = nl 118 if ak_end > 0 { if creds[ak_end - 1] == 0x0d { ak_end = ak_end - 1 } } 119 let ak: *u8 = creds 120 let akn: i64 = ak_end 121 let s2: i64 = nl + 1 122 var nl2: i64 = s2 123 while nl2 < clen { if creds[nl2] == 0x0a { break }; nl2 = nl2 + 1 } 124 var sk_end: i64 = nl2 125 if sk_end > s2 { if creds[sk_end - 1] == 0x0d { sk_end = sk_end - 1 } } 126 let sk: *u8 = (creds as i64 + s2) as *u8 127 let skn: i64 = sk_end - s2 128 g_w(" vault creds loaded (apikey len=" as *u8); g_n(akn); g_w(", secret len=" as *u8); g_n(skn); g_w(")\n" as *u8) 129 if akn <= 0 { 130 g_w(" apikey empty\nNX-TLS12-PORKBUN-E2E verdict=RED\n" as *u8); sys_exit(1); return 1 131 } 132 if skn <= 0 { 133 g_w(" secret empty\nNX-TLS12-PORKBUN-E2E verdict=RED\n" as *u8); sys_exit(1); return 1 134 } 135 136 // ===================================================================== 137 // 0) REGRESSION: a TLS 1.3 host (Let's Encrypt directory) still works 138 // over the SAME transport. Read-only -- creates NO ACME state. 139 // ===================================================================== 140 g_w(" -- regression: GET Let's Encrypt directory over the transport (uses TLS 1.3) --\n" as *u8) 141 let le_url: *u8 = "https://acme-v02.api.letsencrypt.org/directory\x00" as *u8 142 let lebuf: *u8 = sys_mmap(65536) 143 let letot: *i64 = sys_mmap(8) as *i64 144 var le_ok: i64 = 0 145 var ltries: i64 = 0 146 while ltries < 3 { 147 let lrc: i64 = nx_acme_req(le_url, 46, 0, 0 as *u8, 0, 0 as *u8, 0, store, now, lebuf, 65536, letot) 148 if lrc == NX_ACME_HTTP_OK { 149 let rr: *i64 = nx_http_resp_alloc() 150 if nx_http_response_parse(lebuf, letot[0], rr) == 0 { 151 g_w(" LE directory HTTP status=" as *u8); g_n(rr[1]); g_w("\n" as *u8) 152 if rr[1] == 200 { le_ok = 1 } 153 } 154 ltries = 3 155 } else { 156 g_w(" LE req rc=" as *u8); g_n(lrc); g_w(" (retry)\n" as *u8) 157 ltries = ltries + 1 158 } 159 } 160 g_row("REGRESSION: Let's Encrypt (TLS 1.3) directory GET -> 200 through the SAME transport" as *u8, le_ok, pass); total=total+1 161 162 // ===================================================================== 163 // 1) SET: create a UNIQUE _acme-challenge.nishifamily.com TXT over 1.2. 164 // ===================================================================== 165 let tval: *u8 = sys_mmap(64) 166 var vo: i64 = 0 167 let pfx: *u8 = "nishi-tls12-e2e-" as *u8 168 var pi: i64 = 0 169 while pfx[pi] != (0 as u8) { tval[vo]=pfx[pi]; vo=vo+1; pi=pi+1 } 170 vo = g_dec(tval, vo, now) 171 let vn: i64 = vo 172 g_w(" test TXT value = " as *u8); sys_write(1, tval, vn); g_w("\n" as *u8) 173 174 let resp: *u8 = sys_mmap(16384) 175 let rn: *i64 = sys_mmap(8) as *i64 176 177 g_w(" -- SET TXT (Porkbun dns/create over TLS 1.2) --\n" as *u8) 178 let sv: i64 = nx_porkbun_set_txt(ak, akn, sk, skn, "nishifamily.com" as *u8, 15, 179 tval, vn, store, now, resp, 16384, rn) 180 g_w(" set rc=" as *u8); g_n(sv); g_w(" resp_bytes=" as *u8); g_n(rn[0]); g_w("\n" as *u8) 181 e2e_print_body(" SET API response: " as *u8, resp, rn[0]) 182 var set_ok: i64 = 0 183 if sv == NX_PORKBUN_OK { set_ok = 1 } 184 // IMPORTANT: do NOT early-exit -- the DELETE below MUST run for cleanup. 185 186 // ===================================================================== 187 // 2) DELETE: remove it. ALWAYS runs -> guaranteed cleanup, reversible. 188 // ===================================================================== 189 g_w(" -- DELETE TXT (Porkbun dns/deleteByNameType over TLS 1.2; guaranteed cleanup) --\n" as *u8) 190 let resp2: *u8 = sys_mmap(16384) 191 let rn2: *i64 = sys_mmap(8) as *i64 192 let dv: i64 = nx_porkbun_delete_txt(ak, akn, sk, skn, "nishifamily.com" as *u8, 15, 193 store, now, resp2, 16384, rn2) 194 g_w(" delete rc=" as *u8); g_n(dv); g_w(" resp_bytes=" as *u8); g_n(rn2[0]); g_w("\n" as *u8) 195 e2e_print_body(" DELETE API response: " as *u8, resp2, rn2[0]) 196 var del_ok: i64 = 0 197 if dv == NX_PORKBUN_OK { del_ok = 1 } 198 199 g_row("LIVE: Porkbun CREATE _acme-challenge.nishifamily.com TXT over TLS 1.2 -> SUCCESS" as *u8, set_ok, pass); total=total+1 200 g_row("LIVE: Porkbun DELETE _acme-challenge.nishifamily.com TXT over TLS 1.2 -> SUCCESS (cleanup)" as *u8, del_ok, pass); total=total+1 201 202 g_w("NX-TLS12-PORKBUN-E2E rows=" as *u8); g_n(total); g_w(" pass=" as *u8); g_n(pass[0]) 203 if pass[0]==total { g_w(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 204 g_w(" verdict=RED\n" as *u8); sys_exit(1); return 1 205}