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}