code wiki / _hdl_build / nx_cms_argon_gate.nx
nx_cms_argon_gate.nx source
↩ module page · 311 lines · 12725 B
1// nx_cms_argon_gate.nx -- CMS C10 GATE (re-runnable, evidence-driven): argon2id admin credentials
2// at the wire. Site fixture carries admin_pw.ar2 (self-describing params; gate uses m=8192 t=3 to
3// stay brisk -- production = the vault's measured m=32768, the FILE decides) PLUS a deliberately
4// VALID legacy admin_pw.sha256 for a DIFFERENT password: the gate proves the legacy file cannot
5// bypass when .ar2 is present (no downgrade), wrong pw refused, right pw verifies memory-hard,
6// lockout still enforced. Appends "CMSGATE row=nx_cms_argon ... verdict=PASS|FAIL". Exit 0 iff all
7// rows pass. license_tier: ORIGINAL
8import "nx_cms_pw.nx"
9import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
10import "nx_cms_store.nx"
11import "nx_sha256.nx"
12import "_hdl_build/nx_kill_portable.nx"
13
14const AG_PORT: i64 = 8082
15const AG_SITE: *u8 = "/tmp/_cms_ar2_site"
16const AG_M_KIB: i64 = 8192 // gate-speed param, recorded IN the fixture file (rule 11: data not code)
17const AG_T: i64 = 3
18
19func ag_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
20func ag_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
21func ag_cat(dst: *u8, off: i64, s: *u8) -> i64 {
22 var o: i64 = off
23 var k: i64 = 0
24 while s[k] != (0 as u8) { dst[o] = s[k]; o = o + 1; k = k + 1 }
25 return o
26}
27func ag_catn(dst: *u8, off: i64, s: *u8, n: i64) -> i64 {
28 var o: i64 = off
29 var k: i64 = 0
30 while k < n { dst[o] = s[k]; o = o + 1; k = k + 1 }
31 return o
32}
33func ag_index(hay: *u8, n: i64, needle: *u8) -> i64 {
34 let nl: i64 = ag_len(needle)
35 var i: i64 = 0
36 while i + nl <= n {
37 var q: i64 = 0
38 var ok: i64 = 1
39 while q < nl { if (hay[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nl } q = q + 1 }
40 if ok == 1 { return i }
41 i = i + 1
42 }
43 return 0 - 1
44}
45func ag_has(hay: *u8, n: i64, needle: *u8) -> i64 { if ag_index(hay, n, needle) >= 0 { return 1 } return 0 }
46
47func ag_http(req: *u8, rl: i64, resp: *u8, cap: i64) -> i64 {
48 let fd: i64 = sys_socket(2, 1, 0)
49 if fd < 0 { return 0 - 1 }
50 let addr: *u8 = sys_mmap(16)
51 addr[0] = 2 as u8; addr[1] = 0 as u8
52 addr[2] = ((AG_PORT >> 8) & 0xff) as u8
53 addr[3] = (AG_PORT & 0xff) as u8
54 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8
55 var zi: i64 = 8
56 while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 }
57 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 }
58 var off: i64 = 0
59 var go: i64 = 1
60 while go == 1 {
61 go = 0
62 if off < rl {
63 let w: i64 = sys_write(fd, (req + off) as *u8, rl - off)
64 if w > 0 { off = off + w; go = 1 }
65 }
66 }
67 sys_set_socket_timeout(fd, 30) // argon2id verify takes real time; don't cut the read short
68 var total: i64 = 0
69 go = 1
70 while go == 1 {
71 go = 0
72 if total < cap {
73 let r: i64 = sys_read(fd, (resp + total) as *u8, cap - total)
74 if r > 0 { total = total + r; go = 1 }
75 }
76 }
77 sys_close(fd)
78 return total
79}
80
81func ag_post(req: *u8, path: *u8, cookie: *u8, body: *u8) -> i64 {
82 var o: i64 = ag_cat(req, 0, "POST " as *u8)
83 o = ag_cat(req, o, path)
84 o = ag_cat(req, o, " HTTP/1.1\r\nHost: gate\r\nContent-Type: application/x-www-form-urlencoded\r\n" as *u8)
85 if (cookie[0] as i64) != 0 {
86 o = ag_cat(req, o, "Cookie: nsess=" as *u8)
87 o = ag_catn(req, o, cookie, 32)
88 o = ag_cat(req, o, "\r\n" as *u8)
89 }
90 o = ag_cat(req, o, "Content-Length: " as *u8)
91 let bl: i64 = ag_len(body)
92 let t: *u8 = sys_mmap(24)
93 var m: i64 = bl
94 var k: i64 = 0
95 if m == 0 { t[0] = 48 as u8; k = 1 }
96 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
97 var i: i64 = 0
98 while i < k { req[o] = t[k-1-i]; o = o + 1; i = i + 1 }
99 o = ag_cat(req, o, "\r\n\r\n" as *u8)
100 o = ag_cat(req, o, body)
101 return o
102}
103func ag_get(req: *u8, path: *u8, cookie: *u8) -> i64 {
104 var o: i64 = ag_cat(req, 0, "GET " as *u8)
105 o = ag_cat(req, o, path)
106 o = ag_cat(req, o, " HTTP/1.1\r\nHost: gate\r\n" as *u8)
107 if (cookie[0] as i64) != 0 {
108 o = ag_cat(req, o, "Cookie: nsess=" as *u8)
109 o = ag_catn(req, o, cookie, 32)
110 o = ag_cat(req, o, "\r\n" as *u8)
111 }
112 o = ag_cat(req, o, "\r\n" as *u8)
113 return o
114}
115
116func ag_row(id: i64, ok: i64, what: *u8) -> i64 {
117 ag_w("ARGONROW " as *u8)
118 let d: *u8 = sys_mmap(8); d[0] = (48 + id/10) as u8; d[1] = (48 + id%10) as u8; d[2] = 32 as u8; d[3] = 0 as u8
119 ag_w(d)
120 if ok == 1 { ag_w("PASS " as *u8) }
121 if ok == 0 { ag_w("FAIL " as *u8) }
122 ag_w(what); ag_w("\n" as *u8)
123 return ok
124}
125
126func ag_mkdir(path: *u8, mode: i64) -> i64 {
127 let nb: *i64 = sys_mmap(8) as *i64
128 nb[0] = 258
129 return __syscall(nb[0], 0 - 100, path, mode, 0, 0, 0)
130}
131
132func main() -> i64 {
133 // ---- fixture: .ar2 credential (pinned salt = deterministic gate) + a VALID legacy file
134 // for a DIFFERENT password (the downgrade bait) ----
135 ag_mkdir(AG_SITE, 0x1ed)
136 cst_copy("knowledge/cms/andelinwest/template.html" as *u8, "/tmp/_cms_ar2_site/template.html" as *u8)
137 cst_copy("knowledge/cms/andelinwest/content.txt" as *u8, "/tmp/_cms_ar2_site/content.txt" as *u8)
138 sys_renameat("/tmp/_cms_ar2_site/draft.txt" as *u8, "/tmp/_cms_ar2_site/draft.stale" as *u8)
139 let salt: *u8 = "00112233445566778899aabbccddeeff" as *u8
140 if cpw_write("/tmp/_cms_ar2_site/admin_pw.ar2" as *u8, "argon-gate-pw-1" as *u8, 15, salt, AG_M_KIB, AG_T) != 1 {
141 ag_w("CMSGATE row=nx_cms_argon FIXTURE-DERIVE-FAILED\n" as *u8)
142 sys_exit(1)
143 }
144 let dig: *u8 = sys_mmap(32)
145 sha256_digest("legacy-bypass-pw" as *u8, 16, dig)
146 let hexs: *u8 = sys_mmap(80)
147 let hxc: *u8 = "0123456789abcdef" as *u8
148 var i: i64 = 0
149 while i < 32 {
150 hexs[i*2] = hxc[((dig[i] as i64) >> 4) & 15]
151 hexs[i*2+1] = hxc[(dig[i] as i64) & 15]
152 i = i + 1
153 }
154 let pf: i64 = sys_openat_wr("/tmp/_cms_ar2_site/admin_pw.sha256" as *u8, 0x1a4)
155 sys_write(pf, hexs, 64)
156 sys_close(pf)
157
158 // ---- rebuild + boot the admin ----
159 let bpid: i64 = sys_fork()
160 if bpid == 0 {
161 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
162 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) }
163 let bargv: *i64 = sys_mmap(32) as *i64
164 bargv[0] = "_offc/nx_sov_build_run.elf" as *u8 as i64
165 bargv[1] = "nx_cms_admin" as *u8 as i64
166 bargv[2] = 0
167 let benvp: *i64 = sys_mmap(16) as *i64
168 benvp[0] = 0
169 sys_execve("_offc/nx_sov_build_run.elf" as *u8, bargv, benvp); sys_exit(127)
170 }
171 let bst: *i64 = sys_mmap(16) as *i64
172 sys_wait4(bpid, bst, 0)
173 // CALLER-GAP FIX 2026-08-01 (seq1267 class): the runner emits buildroot/_build/<t>.sov.elf under
174 // the no-/tmp doctrine while this probed /tmp. Proven by control: _build copy 208962B, /tmp absent.
175 var bchk: i64 = sys_openat_rd("buildroot/_build/nx_cms_admin.sov.elf" as *u8)
176 var badm: *u8 = "buildroot/_build/nx_cms_admin.sov.elf" as *u8
177 if bchk < 0 { bchk = sys_openat_rd("/tmp/nx_cms_admin.sov.elf" as *u8); badm = "/tmp/nx_cms_admin.sov.elf" as *u8 }
178 if bchk < 0 { ag_w("CMSGATE row=nx_cms_argon ADMIN-BUILD-MISSING (probed buildroot/_build AND /tmp)\n" as *u8); sys_exit(1) }
179 sys_close(bchk)
180 let pid: i64 = sys_fork()
181 if pid == 0 {
182 let lg: i64 = sys_openat_wr("/tmp/_cms_admin_argon_gate.log" as *u8, 0x1a4)
183 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0) }
184 let argvv: *i64 = sys_mmap(8*6) as *i64
185 argvv[0] = badm as i64
186 argvv[1] = "8082" as *u8 as i64
187 argvv[2] = AG_SITE as i64
188 argvv[3] = "99" as *u8 as i64
189 argvv[4] = 0
190 let envp: *i64 = sys_mmap(16) as *i64
191 envp[0] = 0
192 sys_execve(badm, argvv, envp)
193 sys_exit(127)
194 }
195
196 let req: *u8 = sys_mmap(262144)
197 let resp: *u8 = sys_mmap(524288)
198 let nocookie: *u8 = sys_mmap(8)
199 nocookie[0] = 0 as u8
200 let sess: *u8 = sys_mmap(64)
201 sess[0] = 0 as u8
202
203 // A0: boots in argon2id mode + serves
204 var rn: i64 = 0 - 1
205 var tries: i64 = 0
206 while tries < 50 {
207 let rl0: i64 = ag_get(req, "/" as *u8, nocookie)
208 rn = ag_http(req, rl0, resp, 524287)
209 if rn > 0 { tries = 50 }
210 if rn <= 0 { sys_sleep_ms(100); tries = tries + 1 }
211 }
212 var pass: i64 = 0
213 var rows: i64 = 0
214 var ok: i64 = 0
215 if rn > 0 { if ag_has(resp, rn, "200 OK" as *u8) == 1 { ok = 1 } }
216 rows = rows + 1; pass = pass + ag_row(0, ok, "daemon boots with .ar2 credential + serves" as *u8)
217
218 // A1: wrong password refused (memory-hard path)
219 var rl: i64 = ag_post(req, "/admin/login" as *u8, nocookie, "pw=totally-wrong" as *u8)
220 rn = ag_http(req, rl, resp, 524287)
221 ok = 0
222 if rn > 0 { if ag_has(resp, rn, "403" as *u8) == 1 { ok = 1 } }
223 rows = rows + 1; pass = pass + ag_row(1, ok, "wrong password refused 403" as *u8)
224
225 // A2: the legacy sha256 file's password CANNOT bypass (no downgrade when .ar2 present)
226 rl = ag_post(req, "/admin/login" as *u8, nocookie, "pw=legacy-bypass-pw" as *u8)
227 rn = ag_http(req, rl, resp, 524287)
228 ok = 0
229 if rn > 0 { if ag_has(resp, rn, "403" as *u8) == 1 { ok = 1 } }
230 rows = rows + 1; pass = pass + ag_row(2, ok, "legacy sha256 password refused (no downgrade)" as *u8)
231
232 // A3: correct password verifies via argon2id -> session
233 rl = ag_post(req, "/admin/login" as *u8, nocookie, "pw=argon-gate-pw-1" as *u8)
234 rn = ag_http(req, rl, resp, 524287)
235 ok = 0
236 if rn > 0 { if ag_has(resp, rn, "303" as *u8) == 1 {
237 let sp: i64 = ag_index(resp, rn, "nsess=" as *u8)
238 if sp >= 0 {
239 var m2: i64 = 0
240 while m2 < 32 { sess[m2] = resp[sp+6+m2]; m2 = m2 + 1 }
241 sess[32] = 0 as u8
242 ok = 1
243 }
244 } }
245 rows = rows + 1; pass = pass + ag_row(3, ok, "argon2id verify issues session" as *u8)
246
247 // A4: the session works (editor reachable)
248 rl = ag_get(req, "/admin" as *u8, sess)
249 rn = ag_http(req, rl, resp, 524287)
250 ok = 0
251 if rn > 0 { if ag_has(resp, rn, "name=\"hero_title\"" as *u8) == 1 { ok = 1 } }
252 rows = rows + 1; pass = pass + ag_row(4, ok, "session reaches the editor" as *u8)
253
254 // A5: lockout survives the argon path -- 5 fresh failures, then the RIGHT password is refused
255 var f: i64 = 0
256 while f < 5 {
257 rl = ag_post(req, "/admin/login" as *u8, nocookie, "pw=still-wrong" as *u8)
258 ag_http(req, rl, resp, 524287)
259 f = f + 1
260 }
261 rl = ag_post(req, "/admin/login" as *u8, nocookie, "pw=argon-gate-pw-1" as *u8)
262 rn = ag_http(req, rl, resp, 524287)
263 ok = 0
264 if rn > 0 { if ag_has(resp, rn, "403" as *u8) == 1 { ok = 1 } }
265 rows = rows + 1; pass = pass + ag_row(5, ok, "lockout enforced on the argon path" as *u8)
266
267 // ---- teardown ----
268 nxk_kill(pid, 9)
269 let stbuf: *i64 = sys_mmap(16) as *i64
270 sys_wait4(pid, stbuf, 0)
271
272 var verdict: *u8 = "FAIL" as *u8
273 if pass == rows { verdict = "PASS" as *u8 }
274 let line: *u8 = sys_mmap(512)
275 var lo: i64 = ag_cat(line, 0, "CMSGATE row=nx_cms_argon rows=" as *u8)
276 let t2: *u8 = sys_mmap(24)
277 var mm: i64 = rows
278 var kk: i64 = 0
279 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
280 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
281 var ii: i64 = 0
282 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
283 lo = ag_cat(line, lo, " pass=" as *u8)
284 mm = pass; kk = 0
285 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
286 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
287 ii = 0
288 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
289 lo = ag_cat(line, lo, " m_kib=" as *u8)
290 mm = AG_M_KIB; kk = 0
291 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
292 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
293 ii = 0
294 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
295 lo = ag_cat(line, lo, " verdict=" as *u8)
296 lo = ag_cat(line, lo, verdict)
297 lo = ag_cat(line, lo, " epoch=" as *u8)
298 mm = sys_now_realtime_sec(); kk = 0
299 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
300 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
301 ii = 0
302 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
303 line[lo] = 10 as u8
304 lo = lo + 1
305 sys_write(1, line, lo)
306 let gf: i64 = sys_openat_append("knowledge/status/cms_gate.log" as *u8, 0x1a4)
307 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) }
308 if pass == rows { sys_exit(0) }
309 sys_exit(1)
310 return 1
311}