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}