code wiki / (root) / nx_gen_http_verify.nx

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