code wiki / (root) / nx_mgmt_client_gate.nx

nx_mgmt_client_gate.nx source

↩ module page · 142 lines · 8791 B

1// nx_mgmt_client_gate.nx -- in-process referee for the sovereign mgmt-API client codec. NO socket / NO creds: 2// asserts the PURE byte builders + parsers are exactly right (a live TLS handshake can't run in-process; the 3// operator/parent drives the real round-trip). Locks: 4// T1 login body is url-ENCODED byte-exactly (special chars '&'/'='/space MUST percent-encode) 5// T2 a 200 {"token":"abc123"} response -> token == "abc123", status 200 6// T3 NEG: a 401 {"error":"unauthorized"} -> NO token (fail signal), status 401 7// T4 an authed request carries "X-Nishi-Session: <token>" exactly (GET header; POST header+body+Content-Length; 8// and token_len==0 OMITS the header -- the negative control for header injection) 9// T5 NEG: malformed (unterminated) AND empty JSON -> token extraction returns empty (fail-closed, not garbage) 10// T6 upload integrity: sha256_digest+mcl_hex32 == NIST SHA-256("abc"); &sha256= rides the FINAL chunk ONLY (NEG: absent when final=0) 11// GREEN + exit 0 iff all 6 pass. license_tier: ORIGINAL expect_exit: 0 12import "nx_mgmt_client.nx" 13import "nx_syscalls.nx" 14import "nx_gate_verdict.nx" 15 16func mcg_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 17func mcg_putn(v: i64) -> i64 { 18 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 } 19 var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 20 let d: *u8 = sys_mmap(24); var k: i64 = 0 21 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 22 var j: i64 = k - 1 23 while j >= 0 { sys_write(1, ((d as i64)+j) as *u8, 1); j = j - 1 } 24 return 0 25} 26func mcg_row(name: *u8, ok: i64) -> i64 { 27 if ok == 1 { mcg_puts(" PASS " as *u8) } else { mcg_puts(" FAIL " as *u8) } 28 mcg_puts(name); mcg_puts("\n" as *u8) 29 return ok 30} 31// exact byte compare of a[0..an) vs b[0..bn) 32func mcg_memeq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 33 if an != bn { return 0 } 34 var i: i64 = 0 35 while i < an { if a[i] != b[i] { return 0 } i = i + 1 } 36 return 1 37} 38// substring search: is needle (NUL-term) present in buf[0..n)? 39func mcg_contains(buf: *u8, n: i64, needle: *u8) -> i64 { 40 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 } 41 if nl == 0 { return 0 } 42 var i: i64 = 0 43 while i + nl <= n { 44 var j: i64 = 0; var ok: i64 = 1 45 while j < nl { if buf[i + j] != needle[j] { ok = 0; j = nl } else { j = j + 1 } } 46 if ok == 1 { return 1 } 47 i = i + 1 48 } 49 return 0 50} 51func mcg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 52 53func main() -> i64 { 54 mcg_puts("mgmt-client codec gate (PURE builders/parsers; no socket -- live TLS driven by the operator)\n" as *u8) 55 let out: *u8 = sys_mmap(65536) 56 let total: i64 = 6 57 var pass: i64 = 0 58 59 // ---- T1: login body url-encoding is byte-EXACT (handle "a b", passphrase "x&y=z") ---- 60 // expected: "handle=a%20b&passphrase=x%26y%3Dz" (space->%20, '&'->%26, '='->%3D) 61 let n1: i64 = mcl_build_login_body("a b" as *u8, 3, "x&y=z" as *u8, 5, out) 62 let want1: *u8 = "handle=a%20b&passphrase=x%26y%3Dz" as *u8 63 var t1: i64 = mcg_memeq(out, n1, want1, mcg_slen(want1)) 64 pass = pass + mcg_row("T1 login body url-encoded byte-exact (space/&/= percent-encoded)\x00" as *u8, t1) 65 66 // ---- T2: parse 200 {"token":"abc123"} -> token=="abc123", status 200 ---- 67 let s2: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"token\":\"abc123\"}" as *u8 68 let stbox: *i64 = sys_mmap(8) as *i64 69 let tok: *u8 = sys_mmap(512) 70 let tn2: i64 = mcl_extract_token(s2, mcg_slen(s2), stbox, tok, 512) 71 var t2: i64 = 0 72 if tn2 == 6 { if mcg_memeq(tok, 6, "abc123" as *u8, 6) == 1 { if stbox[0] == 200 { t2 = 1 } } } 73 pass = pass + mcg_row("T2 parse 200 {\"token\":\"abc123\"} -> abc123 (status 200)\x00" as *u8, t2) 74 75 // ---- T3 NEG: 401 {"error":"unauthorized"} -> no token, status 401 ---- 76 let s3: *u8 = "HTTP/1.1 401 Unauthorized\r\nContent-Type: application/json\r\nContent-Length: 24\r\n\r\n{\"error\":\"unauthorized\"}" as *u8 77 let stbox3: *i64 = sys_mmap(8) as *i64 78 let tn3: i64 = mcl_extract_token(s3, mcg_slen(s3), stbox3, tok, 512) 79 var t3: i64 = 0 80 if tn3 == 0 { if stbox3[0] == 401 { t3 = 1 } } 81 pass = pass + mcg_row("T3 NEG 401 {\"error\":...} -> no token + status 401\x00" as *u8, t3) 82 83 // ---- T4: authed request builders carry X-Nishi-Session exactly; token_len==0 omits it ---- 84 // (a) authed GET /api/services 85 let n4a: i64 = mcl_build_request("GET" as *u8, 3, "/api/services" as *u8, 13, "nishifamily.com" as *u8, 15, "abc123" as *u8, 6, 0 as *u8, 0, 0 as *u8, 0, out) 86 var t4a: i64 = 0 87 if mcg_contains(out, n4a, "GET /api/services HTTP/1.1" as *u8) == 1 { 88 if mcg_contains(out, n4a, "Host: nishifamily.com" as *u8) == 1 { 89 if mcg_contains(out, n4a, "X-Nishi-Session: abc123" as *u8) == 1 { t4a = 1 } } } 90 // (b) authed POST /api/reconcile with confirm=yes body -> header + body + Content-Length + Content-Type 91 let n4b: i64 = mcl_build_request("POST" as *u8, 4, "/api/reconcile" as *u8, 14, "nishifamily.com" as *u8, 15, "TOK123" as *u8, 6, "application/x-www-form-urlencoded" as *u8, 33, "confirm=yes" as *u8, 11, out) 92 var t4b: i64 = 0 93 if mcg_contains(out, n4b, "POST /api/reconcile HTTP/1.1" as *u8) == 1 { 94 if mcg_contains(out, n4b, "X-Nishi-Session: TOK123" as *u8) == 1 { 95 if mcg_contains(out, n4b, "Content-Type: application/x-www-form-urlencoded" as *u8) == 1 { 96 if mcg_contains(out, n4b, "Content-Length: 11" as *u8) == 1 { 97 if mcg_contains(out, n4b, "confirm=yes" as *u8) == 1 { t4b = 1 } } } } } 98 // (c) NEG control: no token -> NO X-Nishi-Session header emitted 99 let n4c: i64 = mcl_build_request("GET" as *u8, 3, "/api/health" as *u8, 11, "nishifamily.com" as *u8, 15, 0 as *u8, 0, 0 as *u8, 0, 0 as *u8, 0, out) 100 var t4c: i64 = 0 101 if mcg_contains(out, n4c, "X-Nishi-Session" as *u8) == 0 { t4c = 1 } 102 var t4: i64 = 0 103 if t4a == 1 { if t4b == 1 { if t4c == 1 { t4 = 1 } } } 104 pass = pass + mcg_row("T4 authed req carries X-Nishi-Session (GET+POST); token=0 omits it (NEG control)\x00" as *u8, t4) 105 106 // ---- T5 NEG: malformed (unterminated) + empty JSON -> fail-closed empty ---- 107 let s5a: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{\"token\":\"oops" as *u8 // no closing quote 108 let stbox5: *i64 = sys_mmap(8) as *i64 109 let tn5a: i64 = mcl_extract_token(s5a, mcg_slen(s5a), stbox5, tok, 512) 110 let s5b: *u8 = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" as *u8 // empty body 111 let tn5b: i64 = mcl_extract_token(s5b, mcg_slen(s5b), stbox5, tok, 512) 112 var t5: i64 = 0 113 if tn5a == 0 { if tn5b == 0 { t5 = 1 } } 114 pass = pass + mcg_row("T5 NEG malformed/empty JSON -> empty token (fail-closed, no garbage)\x00" as *u8, t5) 115 116 // ---- T6: upload integrity path -- sha256_digest+mcl_hex32 match a known SHA-256 vector; &sha256= rides ONLY the final chunk ---- 117 let dig6: *u8 = sys_mmap(32) 118 sha256_digest("abc" as *u8, 3, dig6) 119 let hex6: *u8 = sys_mmap(72) 120 mcl_hex32(dig6, hex6) 121 var t6a: i64 = mcg_memeq(hex6, 64, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" as *u8, 64) // NIST SHA-256("abc") 122 let qp6: *u8 = sys_mmap(512) 123 let qn6: i64 = mcl_upload_qpath("/" as *u8, 1, "nx_mgmt_api.elf" as *u8, 3, 1, hex6, 64, qp6) 124 var t6b: i64 = mcg_contains(qp6, qn6, "&final=1&sha256=ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" as *u8) 125 let qn6c: i64 = mcl_upload_qpath("/" as *u8, 1, "nx_mgmt_api.elf" as *u8, 0, 0, hex6, 64, qp6) // NON-final -> integrity must NOT ride 126 var t6c: i64 = 0 127 if mcg_contains(qp6, qn6c, "&sha256=" as *u8) == 0 { t6c = 1 } 128 var t6: i64 = 0 129 if t6a == 1 { if t6b == 1 { if t6c == 1 { t6 = 1 } } } 130 pass = pass + mcg_row("T6 upload sha256: hex32==NIST(abc) + &sha256= on final ONLY (NEG: absent when final=0)\x00" as *u8, t6) 131 132 mcg_puts("nx_mgmt_client_gate pass=" as *u8); mcg_putn(pass); mcg_puts("/" as *u8); mcg_putn(total); mcg_puts("\n" as *u8) 133 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 134 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 135 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 136 let ctr__dry: *i64 = gv_ctr() 137 ctr__dry[0] = pass 138 ctr__dry[1] = total 139 let rc__dry: i64 = gv_verdict("MGMT-CLIENT-GATE" as *u8, ctr__dry, "mgmt-client codec proven (login url-enc + token parse + fail-closed + X-Nishi-Session + upload sha256 integrity)" as *u8) 140 sys_exit(rc__dry) 141 return rc__dry 142}