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}