code wiki / _hdl_build / nx_wiki_login_live_gate.nx

nx_wiki_login_live_gate.nx source

↩ module page · 293 lines · 13340 B

1// nx_wiki_login_live_gate.nx -- ENGINEER gate: the wiki accessibility gate over REAL HTTP bytes. 2// 3// Drives nx_wiki_admin_handle (the exact function the multi-vhost daemon dispatches 4// to) with synthetic-but-real HTTP requests, against an ARMED auth context (persistent 5// key bundle + account store + argon2id KSF) -- the full production path minus the 6// TLS socket. 12 rows: 7// 1 ops provisioning: register stores the admin record + issues 24-word mnemonic 8// 2 POST /wiki/admin/login (urlencoded body, %20 + '+' both exercised) -> 200 {"token":...} 9// 3 GET /wiki/admin/x with X-Nishi-Session: <base64 token from row 2> -> 200 10// 4 POST login wrong passphrase -> 401 11// 5 GET without session header -> 401 12// 6 GET with garbage session header -> 401 13// 7 POST login on UNARMED ctx -> honest 503 14// 8 POST login unknown handle -> 401 (same body as row 4: enumeration defense) 15// 9 POST /wiki/admin/recover w/ mnemonic -> 200 {"mnemonic": NEW} + login w/ new pw -> 200 16// 10 POST recover with a WRONG (valid-checksum) mnemonic -> 401 17// 11 POST /wiki/admin/refresh w/ valid token -> 200 fresh token that passes GET 18// 12 POST refresh with garbage session -> 401 19// license_tier: ORIGINAL 20import "wiki/nx_wiki_admin_wiring.nx" 21import "nx_syscalls.nx" 22 23func wg_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 24func wg_z(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 25func wg_row(idx: i64, ok: i64, name: *u8) -> i64 { 26 if ok == 1 { wg_w("ROW " as *u8) } 27 if ok != 1 { wg_w("FAIL " as *u8) } 28 let d: *u8 = sys_mmap(4) 29 d[0] = (48 + (idx / 10)) as u8 30 d[1] = (48 + (idx % 10)) as u8 31 d[2] = 32 as u8 32 sys_write(1, d, 3) 33 wg_w(name) 34 wg_w("\n" as *u8) 35 if ok == 1 { return 1 } 36 return 0 37} 38 39// append NUL-terminated chunk; returns new offset 40func wg_app(buf: *u8, off: i64, s: *u8) -> i64 { 41 var n: i64 = 0 42 while s[n] != (0 as u8) { buf[off + n] = s[n]; n = n + 1 } 43 return off + n 44} 45 46// build a request: writes to req, returns headers_end (offset past \r\n\r\n). 47// body appended after headers_end. total length implicit (handler uses Content-Length). 48func wg_build_post_login(req: *u8, body: *u8) -> i64 { 49 let body_n: i64 = wg_z(body) 50 var off: i64 = 0 51 off = wg_app(req, off, "POST /wiki/admin/login HTTP/1.1\r\nHost: nishifamily.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8) 52 // decimal body_n (max 3 digits here) 53 if body_n >= 100 { req[off] = (48 + (body_n / 100)) as u8; off = off + 1 } 54 if body_n >= 10 { req[off] = (48 + ((body_n / 10) % 10)) as u8; off = off + 1 } 55 req[off] = (48 + (body_n % 10)) as u8 56 off = off + 1 57 off = wg_app(req, off, "\r\n\r\n" as *u8) 58 let headers_end: i64 = off 59 off = wg_app(req, off, body) 60 return headers_end 61} 62 63func wg_build_get(req: *u8, session_b64: *u8, session_n: i64) -> i64 { 64 var off: i64 = 0 65 off = wg_app(req, off, "GET /wiki/admin/panel HTTP/1.1\r\nHost: nishifamily.com\r\n" as *u8) 66 if session_n > 0 { 67 off = wg_app(req, off, "X-Nishi-Session: " as *u8) 68 var i: i64 = 0 69 while i < session_n { req[off + i] = session_b64[i]; i = i + 1 } 70 off = off + session_n 71 off = wg_app(req, off, "\r\n" as *u8) 72 } 73 off = wg_app(req, off, "\r\n" as *u8) 74 return off 75} 76 77// response starts with "HTTP/1.1 NNN" -- extract NNN 78func wg_status(resp: *u8) -> i64 { 79 return ((resp[9] as i64) - 48) * 100 + ((resp[10] as i64) - 48) * 10 + ((resp[11] as i64) - 48) 80} 81 82// extract base64 token from {"token":"..."} into out; returns length or 0 83func wg_extract_token(resp: *u8, resp_n: i64, out: *u8) -> i64 { 84 let needle: *u8 = "{\"token\":\"" as *u8 85 var pos: i64 = 0 86 while pos < resp_n - 10 { 87 var m: i64 = 1 88 var i: i64 = 0 89 while i < 10 { 90 if (resp[pos + i] as i64) != (needle[i] as i64) { m = 0; i = 10 } 91 if i < 10 { i = i + 1 } 92 } 93 if m == 1 { 94 var o: i64 = 0 95 var p: i64 = pos + 10 96 var scan: i64 = 1 97 while scan == 1 { 98 if p >= resp_n { scan = 0 } 99 if scan == 1 { if (resp[p] as i64) == 34 { scan = 0 } } 100 if scan == 1 { out[o] = resp[p]; o = o + 1; p = p + 1 } 101 } 102 return o 103 } 104 pos = pos + 1 105 } 106 return 0 107} 108 109func main() -> i64 { 110 let store_path: *u8 = "/tmp/nishi_wiki_gate_store.log" as *u8 111 let keys_path: *u8 = "/tmp/nishi_wiki_gate_keys.log" as *u8 112 let fd1: i64 = sys_openat_wr(store_path, 0x180) 113 if fd1 >= 0 { sys_close(fd1) } 114 let fd2: i64 = sys_openat_wr(keys_path, 0x180) 115 if fd2 >= 0 { sys_close(fd2) } 116 var pass: i64 = 0 117 118 // armed context 119 let oprf_seed: *u8 = sys_mmap(32) 120 let akp: *u8 = sys_mmap(32) 121 let akb: *u8 = sys_mmap(33) 122 let edp: *u8 = sys_mmap(32) 123 let edb: *u8 = sys_mmap(32) 124 if nx_uas_server_keys_load_or_init(keys_path, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK { 125 wg_w("KEYS FAILED\n" as *u8); sys_exit(1) 126 } 127 let realm: *u8 = "Nishi Wiki" as *u8 128 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 129 nx_auth_context_init(ctx, realm, 10, realm, 10, 130 store_path as i64, oprf_seed, edp, edb, 131 900, 8192, 1, 1, 5, 1) 132 133 // ---- row 1: ops provisioning (+ 24-word mnemonic issued) ---- 134 let handle: *u8 = "elderwesto" as *u8 135 let pw: *u8 = "correct horse+battery!" as *u8 // 22 bytes; has space, '+', '!' 136 let mn: *u8 = sys_mmap(512) 137 let mn_n: *i64 = sys_mmap(16) as *i64 138 var r1: i64 = 0 139 if nx_modern_auth_register(ctx, handle, 10, pw, 22, mn, 512, mn_n) == NX_MAUTH_OK { 140 if mn_n[0] > 0 { r1 = 1 } 141 } 142 pass = pass + wg_row(1, r1, "ops provisioning (register + 24-word mnemonic)" as *u8) 143 144 let req: *u8 = sys_mmap(8192) 145 let resp: *u8 = sys_mmap(8192) 146 let resp_n: *i64 = sys_mmap(16) as *i64 147 let now_s: i64 = sys_now_realtime_sec() 148 149 // ---- row 2: POST login correct (urlencoded: space=%20, '+'=%2B, '!'=%21) ---- 150 let body_ok: *u8 = "handle=elderwesto&passphrase=correct%20horse%2Bbattery%21" as *u8 151 var he: i64 = wg_build_post_login(req, body_ok) 152 var rc: i64 = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/login" as *u8, 17, 2, now_s, resp, 8192, resp_n) 153 var r2: i64 = 0 154 let tok_b64: *u8 = sys_mmap(256) 155 var tok_b64_n: i64 = 0 156 if rc == NX_WAW_OK { 157 if wg_status(resp) == 200 { 158 tok_b64_n = wg_extract_token(resp, resp_n[0], tok_b64) 159 if tok_b64_n > 200 { r2 = 1 } 160 } 161 } 162 pass = pass + wg_row(2, r2, "POST login correct pw -> 200 + token JSON" as *u8) 163 164 // ---- row 3: GET with session token -> 200 ---- 165 he = wg_build_get(req, tok_b64, tok_b64_n) 166 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/panel" as *u8, 17, 1, now_s, resp, 8192, resp_n) 167 var r3: i64 = 0 168 if rc == NX_WAW_OK { if wg_status(resp) == 200 { r3 = 1 } } 169 pass = pass + wg_row(3, r3, "GET /wiki/admin with token -> 200" as *u8) 170 171 // ---- row 4: POST login wrong pw -> 401 ---- 172 let body_bad: *u8 = "handle=elderwesto&passphrase=wrong%20horse" as *u8 173 he = wg_build_post_login(req, body_bad) 174 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/login" as *u8, 17, 2, now_s, resp, 8192, resp_n) 175 var r4: i64 = 0 176 if rc == NX_WAW_OK { if wg_status(resp) == 401 { r4 = 1 } } 177 pass = pass + wg_row(4, r4, "POST login wrong pw -> 401" as *u8) 178 179 // ---- row 5: GET no session -> 401 ---- 180 he = wg_build_get(req, 0 as *u8, 0) 181 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/panel" as *u8, 17, 1, now_s, resp, 8192, resp_n) 182 var r5: i64 = 0 183 if rc == NX_WAW_OK { if wg_status(resp) == 401 { r5 = 1 } } 184 pass = pass + wg_row(5, r5, "GET without session -> 401" as *u8) 185 186 // ---- row 6: GET garbage session -> 401 ---- 187 let garbage: *u8 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" as *u8 188 he = wg_build_get(req, garbage, 44) 189 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/panel" as *u8, 17, 1, now_s, resp, 8192, resp_n) 190 var r6: i64 = 0 191 if rc == NX_WAW_OK { if wg_status(resp) == 401 { r6 = 1 } } 192 pass = pass + wg_row(6, r6, "GET garbage session -> 401" as *u8) 193 194 // ---- row 7: unarmed ctx -> honest 503 ---- 195 let ctx2: *NxAuthContext = sys_mmap(256) as *NxAuthContext 196 nx_auth_context_init(ctx2, realm, 10, realm, 10, 197 0, (0 as i64) as *u8, edp, edb, 198 900, 8192, 1, 1, 5, 0) 199 he = wg_build_post_login(req, body_ok) 200 rc = nx_wiki_admin_handle(ctx2, req, he, "/wiki/admin/login" as *u8, 17, 2, now_s, resp, 8192, resp_n) 201 var r7: i64 = 0 202 if rc == NX_WAW_OK { if wg_status(resp) == 503 { r7 = 1 } } 203 pass = pass + wg_row(7, r7, "POST login on unarmed ctx -> honest 503" as *u8) 204 205 // ---- row 8: unknown handle -> 401 ---- 206 let body_ghost: *u8 = "handle=ghost&passphrase=whatever" as *u8 207 he = wg_build_post_login(req, body_ghost) 208 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/login" as *u8, 17, 2, now_s, resp, 8192, resp_n) 209 var r8: i64 = 0 210 if rc == NX_WAW_OK { if wg_status(resp) == 401 { r8 = 1 } } 211 pass = pass + wg_row(8, r8, "POST login unknown handle -> 401" as *u8) 212 213 // ---- row 9: POST recover with the issued mnemonic -> 200 + new mnemonic; new pw lives ---- 214 // body = handle=elderwesto&mnemonic=<words +-joined>&passphrase=fresh%20start 215 let rbody: *u8 = sys_mmap(1024) 216 var ro: i64 = 0 217 ro = wg_app(rbody, ro, "handle=elderwesto&mnemonic=" as *u8) 218 var mi: i64 = 0 219 while mi < mn_n[0] { 220 var c: i64 = mn[mi] as i64 221 if c == 32 { c = 43 } // space -> '+' 222 rbody[ro] = c as u8 223 ro = ro + 1 224 mi = mi + 1 225 } 226 ro = wg_app(rbody, ro, "&passphrase=fresh%20start" as *u8) 227 rbody[ro] = 0 as u8 228 he = wg_build_post_login(req, rbody) 229 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/recover" as *u8, 19, 2, now_s, resp, 8192, resp_n) 230 var r9: i64 = 0 231 if rc == NX_WAW_OK { 232 if wg_status(resp) == 200 { 233 // login with the NEW passphrase must now succeed 234 let body_new: *u8 = "handle=elderwesto&passphrase=fresh%20start" as *u8 235 he = wg_build_post_login(req, body_new) 236 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/login" as *u8, 17, 2, now_s, resp, 8192, resp_n) 237 if rc == NX_WAW_OK { if wg_status(resp) == 200 { r9 = 1 } } 238 } 239 } 240 pass = pass + wg_row(9, r9, "POST recover -> 200 + new pw logs in" as *u8) 241 242 // ---- row 10: recover with a WRONG valid-checksum mnemonic -> 401 ---- 243 let wrong_mn: *u8 = "zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+zoo+vote" as *u8 244 let rbody2: *u8 = sys_mmap(1024) 245 var ro2: i64 = 0 246 ro2 = wg_app(rbody2, ro2, "handle=elderwesto&mnemonic=" as *u8) 247 ro2 = wg_app(rbody2, ro2, wrong_mn) 248 ro2 = wg_app(rbody2, ro2, "&passphrase=hijack" as *u8) 249 rbody2[ro2] = 0 as u8 250 he = wg_build_post_login(req, rbody2) 251 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/recover" as *u8, 19, 2, now_s, resp, 8192, resp_n) 252 var r10: i64 = 0 253 if rc == NX_WAW_OK { if wg_status(resp) == 401 { r10 = 1 } } 254 pass = pass + wg_row(10, r10, "POST recover wrong mnemonic -> 401" as *u8) 255 256 // ---- row 11: refresh with a valid token -> fresh token that passes GET ---- 257 let body_new2: *u8 = "handle=elderwesto&passphrase=fresh%20start" as *u8 258 he = wg_build_post_login(req, body_new2) 259 nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/login" as *u8, 17, 2, now_s, resp, 8192, resp_n) 260 tok_b64_n = wg_extract_token(resp, resp_n[0], tok_b64) 261 he = wg_build_get(req, tok_b64, tok_b64_n) 262 // (refresh is a POST with the session header; reuse the GET builder's header layout 263 // by rebuilding: method line is irrelevant to the handler, which gets method_kind) 264 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/refresh" as *u8, 19, 2, now_s, resp, 8192, resp_n) 265 var r11: i64 = 0 266 if rc == NX_WAW_OK { 267 if wg_status(resp) == 200 { 268 let tok2_b64: *u8 = sys_mmap(256) 269 let tok2_n: i64 = wg_extract_token(resp, resp_n[0], tok2_b64) 270 if tok2_n > 200 { 271 he = wg_build_get(req, tok2_b64, tok2_n) 272 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/panel" as *u8, 17, 1, now_s, resp, 8192, resp_n) 273 if rc == NX_WAW_OK { if wg_status(resp) == 200 { r11 = 1 } } 274 } 275 } 276 } 277 pass = pass + wg_row(11, r11, "POST refresh -> fresh token passes GET" as *u8) 278 279 // ---- row 12: refresh with garbage session -> 401 ---- 280 he = wg_build_get(req, garbage, 44) 281 rc = nx_wiki_admin_handle(ctx, req, he, "/wiki/admin/refresh" as *u8, 19, 2, now_s, resp, 8192, resp_n) 282 var r12: i64 = 0 283 if rc == NX_WAW_OK { if wg_status(resp) == 401 { r12 = 1 } } 284 pass = pass + wg_row(12, r12, "POST refresh garbage session -> 401" as *u8) 285 286 if pass == 12 { 287 wg_w("WIKI LOGIN LIVE GATE GREEN 12/12 (login + recover + refresh + session gate over real HTTP bytes)\n" as *u8) 288 sys_exit(0) 289 } 290 wg_w("WIKI LOGIN LIVE GATE RED\n" as *u8) 291 sys_exit(1) 292 return 1 293}