code wiki / _hdl_build / nx_acme_dns01_propagation_gate.nx
nx_acme_dns01_propagation_gate.nx source
↩ module page · 167 lines · 7391 B
1// nx_acme_dns01_propagation_gate.nx -- LIVE proof for the DNS-01 TXT
2// propagation poll that replaces the fixed sys_sleep_ms(360000) in
3// nx_acme_dns01_issue.
4//
5// Rows:
6// 1. SET a UNIQUE _acme-challenge.nishifamily.com TXT at Porkbun (TLS 1.2).
7// 2. POLL nx_dns01_wait_txt_propagated -> assert it returns VISIBLE(1)
8// within a bounded window, and report how long it took (this is the
9// "trigger as soon as propagated, not after a fixed 6 min" win).
10// 3. DELETE the TXT (ALWAYS runs -> guaranteed cleanup, fully reversible).
11// 4. NEG-CONTROL: poll for a value that was NEVER set, with a SHORT
12// timeout -> assert it returns TIMED-OUT(0) and is BOUNDED (does not
13// hang). This proves the ceiling path.
14//
15// AUTHORIZED scope: ONE Porkbun TXT set + delete (cleaned up), read-only DNS
16// queries. NO Let's Encrypt challenge is triggered.
17//
18// Creds: ./_offc/nx_secret_cli.elf get porkbun (/tmp/nxsecret.out; BOM stripped).
19//
20// expect_exit: 0
21// license_tier: ORIGINAL
22
23import "nx_syscalls.nx"
24import "nx_x509_trust_store.nx"
25import "nx_trust_store_load_from_certdata.nx"
26import "nx_acme_porkbun.nx"
27import "nx_acme_dns01_propagation.nx"
28
29func p_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
30func p_n(v: i64) -> i64 {
31 var m: i64=v
32 if m<0 { p_w("-" as *u8); m=0-m }
33 let t: *u8=sys_mmap(24); var k: i64=0
34 if m==0 { t[0]=48 as u8; k=1 }
35 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
36 let o: *u8=sys_mmap(24); var i: i64=0
37 while i<k { o[i]=t[k-1-i]; i=i+1 }
38 sys_write(1,o,k); return 0
39}
40func p_row(id: *u8, ok: i64, pass: *i64) -> i64 {
41 p_w(" " as *u8); p_w(id); p_w(": " as *u8)
42 if ok==1 { p_w("OK\n" as *u8); pass[0]=pass[0]+1 } else { p_w("FAIL\n" as *u8) }
43 return 0
44}
45func p_dec(buf: *u8, off: i64, v: i64) -> i64 {
46 var m: i64 = v
47 if m == 0 { buf[off]=48 as u8; return off+1 }
48 let t: *u8 = sys_mmap(24); var k: i64 = 0
49 while m > 0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
50 var o: i64 = off; var i: i64 = k-1
51 while i >= 0 { buf[o]=t[i]; o=o+1; i=i-1 }
52 return o
53}
54func p_read_file(path: *u8, out: *u8, cap: i64) -> i64 {
55 let fd: i64 = sys_openat_rd(path)
56 if fd <= 0 { return 0 - 1 }
57 let n: i64 = sys_read(fd, out, cap)
58 sys_close(fd)
59 return n
60}
61
62func main() -> i64 {
63 let pass: *i64 = sys_mmap(8) as *i64; pass[0]=0
64 var total: i64 = 0
65 p_w("=== NX-ACME-DNS01-PROPAGATION GATE (live TXT set -> real propagation poll -> delete) ===\n" as *u8)
66
67 let lr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304)
68 if lr <= 0 { p_w(" trust load FAIL\nverdict=RED\n" as *u8); sys_exit(1); return 1 }
69 let store: *TrustStore = lr as *TrustStore
70 let now: i64 = sys_now_realtime_sec()
71
72 let raw: *u8 = sys_mmap(512)
73 let rawn: i64 = p_read_file("/tmp/nxsecret.out\x00" as *u8, raw, 512)
74 if rawn <= 0 { p_w(" creds read FAIL (/tmp/nxsecret.out)\nverdict=RED\n" as *u8); sys_exit(1); return 1 }
75 var base: i64 = 0
76 if rawn >= 3 { if raw[0]==(0xEF as u8) { if raw[1]==(0xBB as u8) { if raw[2]==(0xBF as u8) { base=3 } } } }
77 let creds: *u8 = (raw as i64 + base) as *u8
78 let clen: i64 = rawn - base
79 var nl: i64 = 0
80 while nl < clen { if creds[nl]==0x0a { break }; nl=nl+1 }
81 var ak_end: i64 = nl
82 if ak_end>0 { if creds[ak_end-1]==0x0d { ak_end=ak_end-1 } }
83 let ak: *u8 = creds
84 let akn: i64 = ak_end
85 let s2: i64 = nl + 1
86 var nl2: i64 = s2
87 while nl2 < clen { if creds[nl2]==0x0a { break }; nl2=nl2+1 }
88 var sk_end: i64 = nl2
89 if sk_end>s2 { if creds[sk_end-1]==0x0d { sk_end=sk_end-1 } }
90 let sk: *u8 = (creds as i64 + s2) as *u8
91 let skn: i64 = sk_end - s2
92 if akn<=0 { p_w(" apikey empty\nverdict=RED\n" as *u8); sys_exit(1); return 1 }
93 if skn<=0 { p_w(" secret empty\nverdict=RED\n" as *u8); sys_exit(1); return 1 }
94 p_w(" vault creds loaded (apikey len=" as *u8); p_n(akn); p_w(")\n" as *u8)
95
96 let dom: *u8 = "nishifamily.com" as *u8
97 let dom_n: i64 = 15
98
99 // Unique TXT value (>= a realistic 43-char ACME proof length not required;
100 // any unique string round-trips through Porkbun + the TXT parser).
101 let tval: *u8 = sys_mmap(64)
102 var vo: i64 = 0
103 let pfx: *u8 = "nishi-dns01-poll-" as *u8
104 var ppi: i64 = 0
105 while pfx[ppi] != (0 as u8) { tval[vo]=pfx[ppi]; vo=vo+1; ppi=ppi+1 }
106 vo = p_dec(tval, vo, now)
107 let vn: i64 = vo
108 p_w(" test TXT value = " as *u8); sys_write(1, tval, vn); p_w("\n" as *u8)
109
110 // ---- Row 1: SET ----
111 let resp: *u8 = sys_mmap(16384)
112 let rn: *i64 = sys_mmap(8) as *i64
113 let sv: i64 = nx_porkbun_set_txt(ak, akn, sk, skn, dom, dom_n, tval, vn, store, now, resp, 16384, rn)
114 var set_ok: i64 = 0
115 if sv == NX_PORKBUN_OK { set_ok = 1 }
116 total=total+1; p_row("SET _acme-challenge.nishifamily.com TXT -> SUCCESS (TLS 1.2)" as *u8, set_ok, pass)
117
118 // ---- Row 2: POLL until visible (bounded 150s) ----
119 var poll_ok: i64 = 0
120 var secs: i64 = 0 - 1
121 if set_ok == 1 {
122 let wants: *i64 = sys_mmap(16) as *i64
123 let wlens: *i64 = sys_mmap(16) as *i64
124 wants[0] = tval as i64
125 wlens[0] = vn
126 let t0: i64 = sys_now_realtime_sec()
127 let vis: i64 = nx_dns01_wait_txt_propagated(dom, dom_n, wants, wlens, 1, 150000, now)
128 let t1: i64 = sys_now_realtime_sec()
129 secs = t1 - t0
130 if vis == 1 { poll_ok = 1 }
131 p_w(" poll returned vis=" as *u8); p_n(vis); p_w(" after ~" as *u8); p_n(secs); p_w("s (fixed-sleep was 360s)\n" as *u8)
132 }
133 total=total+1; p_row("POLL -> VISIBLE(1) within bound (triggers as soon as propagated)" as *u8, poll_ok, pass)
134
135 // ---- Row 3: DELETE (always; cleanup) ----
136 let resp2: *u8 = sys_mmap(16384)
137 let rn2: *i64 = sys_mmap(8) as *i64
138 let dv: i64 = nx_porkbun_delete_txt(ak, akn, sk, skn, dom, dom_n, store, now, resp2, 16384, rn2)
139 var del_ok: i64 = 0
140 if dv == NX_PORKBUN_OK { del_ok = 1 }
141 total=total+1; p_row("DELETE TXT (guaranteed cleanup, reversible)" as *u8, del_ok, pass)
142
143 // ---- Row 4: NEG-CONTROL: poll an ABSENT value, SHORT timeout -> 0, bounded ----
144 let absent: *u8 = sys_mmap(64)
145 var ao: i64 = 0
146 let apfx: *u8 = "nishi-dns01-ABSENT-never-set-" as *u8
147 var api: i64 = 0
148 while apfx[api] != (0 as u8) { absent[ao]=apfx[api]; ao=ao+1; api=api+1 }
149 ao = p_dec(absent, ao, now)
150 let an: i64 = ao
151 let wants2: *i64 = sys_mmap(16) as *i64
152 let wlens2: *i64 = sys_mmap(16) as *i64
153 wants2[0] = absent as i64
154 wlens2[0] = an
155 let nt0: i64 = sys_now_realtime_sec()
156 let nvis: i64 = nx_dns01_wait_txt_propagated(dom, dom_n, wants2, wlens2, 1, 8000, now)
157 let nt1: i64 = sys_now_realtime_sec()
158 let nsecs: i64 = nt1 - nt0
159 var neg_ok: i64 = 0
160 if nvis == 0 { if nsecs <= 30 { neg_ok = 1 } } // returned not-visible AND bounded
161 p_w(" neg-control poll(absent, 8s timeout) returned vis=" as *u8); p_n(nvis); p_w(" after ~" as *u8); p_n(nsecs); p_w("s\n" as *u8)
162 total=total+1; p_row("NEG-CONTROL: absent value -> TIMED-OUT(0), bounded (no hang)" as *u8, neg_ok, pass)
163
164 p_w("NX-ACME-DNS01-PROPAGATION rows=" as *u8); p_n(total); p_w(" pass=" as *u8); p_n(pass[0])
165 if pass[0]==total { p_w(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
166 p_w(" verdict=RED\n" as *u8); sys_exit(1); return 1
167}