nx_gen_http_verify.nx source
↩ module page · 233 lines · 12513 B
1// Test-only fixed-origin /gen verifier. LOCAL CANDIDATE; root must review identity before any live run.
2// No browser, cookie extraction, raw response output, user switch, redirect or auth policy writes.
3import "nx_syscalls.nx"
4import "nx_session_mint_lib.nx"
5import "nx_https_fetch_lib.nx"
6import "nx_sha256.nx"
7import "nx_itoa_lib.nx"
8
9const GV_URL: *u8 = "https://nishifamily.com/gen" as *u8
10const GV_KEYS: *u8 = "/volume1/homes/elderwesto/nishihost/opaque_keys.bin" as *u8
11const GV_REALM: *u8 = "nishi_site_admin" as *u8
12const GV_HANDLE: *u8 = "elderwesto" as *u8
13const GV_EXPECTED: *u8 = "/volume1/ai/gen/elara_ui.html" as *u8
14const GV_CONNECT_IP: i64 = 0x7f000001
15const GV_CONNECT_PORT: i64 = 8443
16
17func gv_put(s: *u8) -> i64 { return sys_write(1, s, msm_slen(s)) }
18func gv_eq(a: *u8, b: *u8) -> i64 {
19 var i: i64 = 0
20 while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 }
21 return 0
22}
23func gv_lower(c: i64) -> i64 { if (c >= 65) & (c <= 90) { return c + 32 } return c }
24func gv_match(b: *u8, n: i64, key: *u8) -> i64 {
25 let k: i64 = msm_slen(key)
26 if k != n { return 0 }
27 var i: i64 = 0
28 while i < n { if gv_lower(b[i] as i64) != gv_lower(key[i] as i64) { return 0 } i = i + 1 }
29 return 1
30}
31func gv_find(b: *u8, n: i64, needle: *u8, nn: i64) -> i64 {
32 if (nn <= 0) | (n < nn) { return 0 - 1 }
33 var i: i64 = 0
34 while i <= n - nn {
35 var j: i64 = 0
36 while j < nn { if b[i + j] != needle[j] { break } j = j + 1 }
37 if j == nn { return i }
38 i = i + 1
39 }
40 return 0 - 1
41}
42func gv_zero(b: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { b[i] = 0 as u8; i = i + 1 } return 0 }
43func gv_hex(d: *u8, out: *u8) -> i64 {
44 let alphabet: *u8 = "0123456789abcdef" as *u8
45 var i: i64 = 0
46 while i < 32 { let v: i64 = d[i] as i64; out[i * 2] = alphabet[v >> 4]; out[i * 2 + 1] = alphabet[v & 15]; i = i + 1 }
47 out[64] = 0 as u8
48 return 0
49}
50
51// out[0]=HTTP status, out[1]=body offset, out[2]=body length.
52// Deliberately accepts identity + one exact Content-Length only. A different framing is an explicit
53// verification gap, never decoded heuristically into a false byte-identity result.
54func gv_body(resp: *u8, n: i64, out: *i64) -> i64 {
55 out[0] = 0; out[1] = 0; out[2] = 0
56 if (resp as i64) == 0 { return 0 - 1 }
57 if n < 14 { return 0 - 1 }
58 if gv_match(resp, 7, "HTTP/1." as *u8) != 1 { return 0 - 1 }
59 if (resp[7] != (48 as u8)) & (resp[7] != (49 as u8)) { return 0 - 1 }
60 if (resp[8] != (32 as u8)) | (resp[12] != (32 as u8)) { return 0 - 1 }
61 var st: i64 = 0
62 var k: i64 = 9
63 while k < 12 { let c: i64 = resp[k] as i64; if (c < 48) | (c > 57) { return 0 - 1 } st = st * 10 + c - 48; k = k + 1 }
64 if (st < 100) | (st > 599) { return 0 - 1 }
65 out[0] = st
66 if (st == 401) | (st == 403) { return 0 - 2 }
67 if (st >= 300) & (st < 400) { return 0 - 3 }
68 if st != 200 { return 0 - 4 }
69 let line: i64 = gv_find(resp, n, "\r\n" as *u8, 2)
70 if line < 13 { return 0 - 1 }
71 var i: i64 = line + 2
72 var length: i64 = 0 - 1
73 var html: i64 = 0
74 var ended: i64 = 0
75 while i + 1 < n {
76 if (resp[i] == (13 as u8)) & (resp[i + 1] == (10 as u8)) { i = i + 2; ended = 1; break }
77 let off: i64 = gv_find(resp + i, n - i, "\r\n" as *u8, 2)
78 if off <= 0 { return 0 - 1 }
79 let end: i64 = i + off
80 var colon: i64 = i
81 while colon < end { if resp[colon] == (58 as u8) { break } let c: i64 = resp[colon] as i64; if (c <= 32) | (c >= 127) { return 0 - 1 } colon = colon + 1 }
82 if (colon == i) | (colon == end) { return 0 - 1 }
83 var a: i64 = colon + 1
84 var z: i64 = end
85 while a < z { if (resp[a] != (32 as u8)) & (resp[a] != (9 as u8)) { break } a = a + 1 }
86 while z > a { if (resp[z - 1] != (32 as u8)) & (resp[z - 1] != (9 as u8)) { break } z = z - 1 }
87 if gv_match(resp + i, colon - i, "content-length" as *u8) == 1 {
88 if length >= 0 { return 0 - 5 }
89 if a == z { return 0 - 5 }
90 var v: i64 = 0
91 var p: i64 = a
92 while p < z { let c: i64 = (resp[p] as i64) - 48; if (c < 0) | (c > 9) { return 0 - 5 } if v > (n - c) / 10 { return 0 - 5 } v = v * 10 + c; p = p + 1 }
93 length = v
94 }
95 if gv_match(resp + i, colon - i, "transfer-encoding" as *u8) == 1 { return 0 - 6 }
96 if gv_match(resp + i, colon - i, "content-encoding" as *u8) == 1 { if gv_match(resp + a, z - a, "identity" as *u8) != 1 { return 0 - 6 } }
97 if gv_match(resp + i, colon - i, "content-type" as *u8) == 1 {
98 if html == 1 { return 0 - 7 }
99 if z - a < 9 { return 0 - 7 }
100 if gv_match(resp + a, 9, "text/html" as *u8) != 1 { return 0 - 7 }
101 if z - a > 9 { if resp[a + 9] != (59 as u8) { return 0 - 7 } }
102 html = 1
103 }
104 i = end + 2
105 }
106 if ended != 1 { return 0 - 1 }
107 if (length < 0) | (length != n - i) { return 0 - 5 }
108 if html != 1 { return 0 - 7 }
109 out[1] = i; out[2] = length
110 return 0
111}
112func gv_report_error(reason: *u8, status: i64) -> i64 {
113 gv_put("{\"ok\":false,\"reason\":\"" as *u8); gv_put(reason); gv_put("\",\"http_status\":" as *u8); nxi_out(status); gv_put("}\n" as *u8)
114 return 1
115}
116func gv_reason(code: i64) -> *u8 {
117 if code == (0 - 2) { return "access-refused" as *u8 }
118 if code == (0 - 3) { return "redirect-refused" as *u8 }
119 if code == (0 - 4) { return "http-status-refused" as *u8 }
120 if code == (0 - 5) { return "content-length-mismatch-or-ambiguous" as *u8 }
121 if code == (0 - 6) { return "unsupported-transfer-or-content-encoding" as *u8 }
122 if code == (0 - 7) { return "expected-html-content-type" as *u8 }
123 return "malformed-http" as *u8
124}
125func gv_live() -> i64 {
126 let fl: *i64 = sys_mmap(16) as *i64
127 fl[0] = 0
128 let expected: *u8 = sys_read_file(GV_EXPECTED, fl)
129 if (expected as i64) == 0 { return gv_report_error("served-file-unreadable" as *u8, 0) }
130 if fl[0] <= 0 { return gv_report_error("served-file-empty" as *u8, 0) }
131 // Response budget = measured expected file bytes + existing request/header arena capacity.
132 let cap: i64 = fl[0] + NX_HTTPS_GC_REQ_BUF_BYTES
133 if cap <= fl[0] { return gv_report_error("response-budget-overflow" as *u8, 0) }
134 let store: i64 = hf_store_load()
135 if store <= 0 { return gv_report_error("trust-store-unavailable" as *u8, 0) }
136 let box: *i64 = sys_mmap(64) as *i64
137 let opened: i64 = hf_open(store, GV_URL, GV_CONNECT_IP, GV_CONNECT_PORT, box)
138 if opened != 1 { return gv_report_error("tls-or-connect-refused" as *u8, 0) }
139 let fd: i64 = box[1]
140 let token: *u8 = sys_mmap(512)
141 let minted: i64 = msm_mint_b64(GV_KEYS, GV_REALM, msm_slen(GV_REALM), GV_HANDLE, msm_slen(GV_HANDLE), sys_now_realtime_sec(), NX_NCS_DEFAULT_TTL_S, token)
142 if minted <= 0 { sys_close(fd); gv_zero(token, 512); return gv_report_error("session-mint-refused" as *u8, 0) }
143 let prefix: *u8 = "Accept-Encoding: identity\r\nX-Nishi-Session: " as *u8
144 let pn: i64 = msm_slen(prefix)
145 let hdr: *u8 = sys_mmap(pn + minted + 2)
146 var j: i64 = 0
147 while j < pn { hdr[j] = prefix[j]; j = j + 1 }
148 var t: i64 = 0
149 while t < minted { hdr[pn + t] = token[t]; t = t + 1 }
150 hdr[pn + minted] = 13 as u8; hdr[pn + minted + 1] = 10 as u8
151 let response: *u8 = sys_mmap(cap)
152 let n: i64 = nx_https_get_complete_cookie_xhdr(box[0] as *Tls13ClientSession, fd, box[2] as *u8, box[3], box[4] as *u8, box[5], "" as *u8, 0, hdr, pn + minted + 2, response, cap)
153 sys_close(fd)
154 gv_zero(hdr, pn + minted + 2)
155 if n <= 0 { gv_zero(token, 512); return gv_report_error("authenticated-get-failed" as *u8, 0) }
156 let reflected: i64 = gv_find(response, n, token, minted)
157 gv_zero(token, 512)
158 if reflected >= 0 { return gv_report_error("credential-reflection-refused" as *u8, 0) }
159 let meta: *i64 = sys_mmap(32) as *i64
160 let parsed: i64 = gv_body(response, n, meta)
161 if parsed != 0 { return gv_report_error(gv_reason(parsed), meta[0]) }
162 let actual_d: *u8 = sys_mmap(32)
163 let expected_d: *u8 = sys_mmap(32)
164 sha256_digest(response + meta[1], meta[2], actual_d)
165 sha256_digest(expected, fl[0], expected_d)
166 let ah: *u8 = sys_mmap(65)
167 let eh: *u8 = sys_mmap(65)
168 gv_hex(actual_d, ah); gv_hex(expected_d, eh)
169 var same: i64 = 0
170 if meta[2] == fl[0] { same = gv_eq(ah, eh) }
171 gv_put("{\"ok\":" as *u8)
172 if same == 1 { gv_put("true" as *u8) } else { gv_put("false" as *u8) }
173 gv_put(",\"http_status\":200,\"body_bytes\":" as *u8); nxi_out(meta[2])
174 gv_put(",\"body_sha256\":\"" as *u8); gv_put(ah)
175 gv_put("\",\"served_file_bytes\":" as *u8); nxi_out(fl[0])
176 gv_put(",\"served_file_sha256\":\"" as *u8); gv_put(eh)
177 gv_put("\",\"matches_served_file\":" as *u8); nxi_out(same)
178 gv_put(",\"origin\":\"https://nishifamily.com/gen\",\"connect\":\"127.0.0.1:8443\",\"redirects_followed\":0}\n" as *u8)
179 if same != 1 { return 1 }
180 return 0
181}
182
183// SELFTEST is offline: no keys, mint, socket, live identity or response body is printed.
184func gv_case(raw: *u8, expected_rc: i64, counts: *i64) -> i64 {
185 let meta: *i64 = sys_mmap(32) as *i64
186 let result: i64 = gv_body(raw, msm_slen(raw), meta)
187 counts[1] = counts[1] + 1
188 if result == expected_rc { counts[0] = counts[0] + 1 } else { gv_put("selftest-failed-case=" as *u8); nxi_out(counts[1]); gv_put("\n" as *u8) }
189 return 0
190}
191func gv_selftest() -> i64 {
192 let counts: *i64 = sys_mmap(16) as *i64
193 counts[0] = 0; counts[1] = 0
194 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 3\r\n\r\nabc" as *u8, 0, counts)
195 gv_case("HTTP/1.1 302 Found\r\nLocation: https://other.invalid/\r\n\r\n" as *u8, 0 - 3, counts)
196 gv_case("HTTP/1.1 401 Unauthorized\r\n\r\n" as *u8, 0 - 2, counts)
197 gv_case("HTTP/1.1 403 Forbidden\r\n\r\n" as *u8, 0 - 2, counts)
198 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 4\r\n\r\nabc" as *u8, 0 - 5, counts)
199 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 2\r\n\r\nabc" as *u8, 0 - 5, counts)
200 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 3\r\nContent-Length: 3\r\n\r\nabc" as *u8, 0 - 5, counts)
201 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 999999999999999999999999\r\n\r\nabc" as *u8, 0 - 5, counts)
202 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n" as *u8, 0 - 6, counts)
203 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Encoding: gzip\r\nContent-Length: 3\r\n\r\nabc" as *u8, 0 - 6, counts)
204 gv_case("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 3\r\n\r\nabc" as *u8, 0 - 7, counts)
205 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/htmlbad\r\nContent-Length: 3\r\n\r\nabc" as *u8, 0 - 7, counts)
206 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\ncontent-length:\t3 \r\n\r\nabc" as *u8, 0, counts)
207 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: +3\r\n\r\nabc" as *u8, 0 - 5, counts)
208 gv_case("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nabc" as *u8, 0 - 5, counts)
209 let raw: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 3\r\n\r\nabc" as *u8
210 let meta: *i64 = sys_mmap(32) as *i64
211 let rc: i64 = gv_body(raw, msm_slen(raw), meta)
212 let digest: *u8 = sys_mmap(32)
213 let hex: *u8 = sys_mmap(65)
214 sha256_digest(raw + meta[1], meta[2], digest); gv_hex(digest, hex)
215 counts[1] = counts[1] + 1
216 if rc == 0 { if gv_eq(hex, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" as *u8) == 1 { counts[0] = counts[0] + 1 } }
217 counts[1] = counts[1] + 1
218 if gv_find("prefix-fixture-token-suffix" as *u8, 27, "fixture-token" as *u8, 13) >= 0 { counts[0] = counts[0] + 1 }
219 counts[1] = counts[1] + 1
220 if gv_find("safe" as *u8, 4, "fixture-token" as *u8, 13) < 0 { counts[0] = counts[0] + 1 }
221 gv_put("{\"selftest_pass\":" as *u8); nxi_out(counts[0]); gv_put(",\"selftest_total\":" as *u8); nxi_out(counts[1]); gv_put("}\n" as *u8)
222 if counts[0] != counts[1] { return 1 }
223 return 0
224}
225func main(argc: i64, argv: *i64) -> i64 {
226 if argc == 2 {
227 if gv_eq(argv[1] as *u8, "selftest" as *u8) == 1 { return gv_selftest() }
228 if gv_eq(argv[1] as *u8, "verify" as *u8) == 1 { return gv_live() }
229 }
230 gv_put("{\"ok\":false,\"reason\":\"usage: nx_gen_http_verify selftest|verify\"}\n" as *u8)
231 return 2
232}
233