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}