nx_hgw_mtls_resolve.nx source
↩ module page · 156 lines · 9550 B
1// nx_hgw_mtls_resolve.nx -- R7 ENFORCEMENT wiring: the mTLS-first handle resolution the doc wall plugs in.
2//
3// nx_hub_gw_decide.nx:91-94 is the bug, in three lines: it reads the `X-Nishi-Session:` header, resolves the
4// token -> handle, and routes on that handle. A top-level browser NAVIGATION carries no custom header -> the
5// handle is empty -> mr_route DENY -> 302 /login = "logs in but isn't let in". The mTLS fix lives exactly here:
6// the verified client cert rides EVERY connection (nav/refresh/new-tab) at the TLS layer, so resolve the handle
7// from the CERT first, and fall back to the header token only when no cert was presented (never-lockout).
8//
9// This is that resolution, pure + gated (composes nx_mtls_serve_decide). The live hgw_decide swaps its three
10// lines for ONE call to nx_hgw_mtls_resolve_handle, passing the run loop's out_auth + client cert; everything
11// downstream (mr_route + the status map) is byte-identical. The uid->handle index models the proven live
12// olgd_idx_lookup; the sessions registry is the existing token->handle fallback.
13// license_tier: ORIGINAL expect_exit: 0
14import "nx_syscalls.nx"
15import "nx_mtls_serve_decision.nx" // nx_mtls_serve_decide + NX_SERVE_* (+ transitively nx_mtls_authz az_mint/az_hex)
16const NX_MAGIC_4096: i64 = 4096
17
18const NX_HMH_REJECT: i64 = 0 - 1 // a cert was presented but FAILED verify -> caller returns 403, never falls back
19
20// scan a "key<TAB>value\n" registry for `key`; copy its value to out (nul-terminated). returns value length (0 = miss).
21func hmh_lookup(reg: *u8, reglen: i64, key: *u8, keylen: i64, out: *u8, cap: i64) -> i64 {
22 out[0] = 0 as u8
23 if keylen <= 0 { return 0 }
24 var ls: i64 = 0
25 while ls < reglen {
26 var le: i64 = ls; var sc: i64 = 1
27 while sc == 1 { if le >= reglen { sc = 0 } else { if reg[le] == (10 as u8) { sc = 0 } else { le = le + 1 } } }
28 if le > ls { if reg[ls] != (35 as u8) {
29 var tb: i64 = ls; var fc: i64 = 1
30 while fc == 1 { if tb >= le { fc = 0 } else { if reg[tb] == (9 as u8) { fc = 0 } else { tb = tb + 1 } } }
31 if tb < le {
32 let f0len: i64 = tb - ls
33 if f0len == keylen {
34 var m: i64 = 1; var c: i64 = 0
35 while c < keylen { if reg[ls + c] != key[c] { m = 0; c = keylen } else { c = c + 1 } }
36 if m == 1 {
37 var o: i64 = 0; var k: i64 = tb + 1
38 while k < le { if o < cap - 1 { out[o] = reg[k] } o = o + 1; k = k + 1 }
39 out[o] = 0 as u8
40 return o
41 }
42 }
43 }
44 } }
45 ls = le + 1
46 }
47 out[0] = 0 as u8
48 return 0
49}
50
51// Resolve the doc-serving handle, PREFERRING the verified mTLS client-cert identity (rides every nav at the TLS
52// layer -> fixes the bounce), falling back to the X-Nishi-Session header token (never-lockout). Returns the
53// handle length (>=0; 0 = anonymous -> caller 302 /login as today), or NX_HMH_REJECT for a presented-but-bad cert.
54func nx_hgw_mtls_resolve_handle(
55 mtls_auth: i64, mtls_cert: *u8, mtls_cert_len: i64,
56 roster: *i64, rlens: *i64, levels: *i64, roster_n: i64,
57 uid_index: *u8, uidx_len: i64,
58 header_token: *u8, header_tlen: i64,
59 sessions: *u8, seslen: i64,
60 out_handle: *u8, cap: i64
61) -> i64 {
62 let uidhex: *u8 = sys_mmap(128); let lvl: *i64 = sys_mmap(8) as *i64
63 let d: i64 = nx_mtls_serve_decide(mtls_auth, mtls_cert, mtls_cert_len, roster, rlens, levels, roster_n, uidhex, 128, lvl)
64 if d == NX_SERVE_REJECT { return NX_HMH_REJECT } // bad cert -> 403, never silently fall back
65 if d == NX_SERVE_BY_IDENTITY {
66 let hl: i64 = hmh_lookup(uid_index, uidx_len, uidhex, 64, out_handle, cap)
67 if hl > 0 { return hl } // CERT identity -> handle (THIS is the nav fix)
68 }
69 // FALLBACK: no cert / unenrolled cert / uid not yet indexed -> the existing header-token session path
70 if header_tlen > 0 {
71 let hl2: i64 = hmh_lookup(sessions, seslen, header_token, header_tlen, out_handle, cap)
72 if hl2 > 0 { return hl2 }
73 }
74 out_handle[0] = 0 as u8
75 return 0 // anonymous -> mr_route DENY -> 302 (today's behavior)
76}
77
78// ===== in-process gate: the FIX (nav carries identity) + never-lockout + precedence + reject =====
79func hr_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
80func hr_row(name: *u8, ok: i64) -> i64 { if ok == 1 { hr_w(" PASS " as *u8) } else { hr_w(" FAIL " as *u8) } hr_w(name); hr_w("\n" as *u8); return ok }
81func hr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
82func hr_streq(a: *u8, an: i64, b: *u8) -> i64 { let bn: i64 = hr_slen(b); if an != bn { return 0 } var i: i64 = 0; while i < an { if a[i] != b[i] { return 0 } i = i + 1 } return 1 }
83func hr_put(buf: *u8, off: i64, key: *u8, keylen: i64, val: *u8, vallen: i64) -> i64 {
84 var o: i64 = off
85 var i: i64 = 0; while i < keylen { buf[o] = key[i]; o = o + 1; i = i + 1 }
86 buf[o] = 9 as u8; o = o + 1
87 var j: i64 = 0; while j < vallen { buf[o] = val[j]; o = o + 1; j = j + 1 }
88 buf[o] = 10 as u8; o = o + 1
89 return o
90}
91
92func main() -> i64 {
93 hr_w("nx_hgw mTLS handle-resolution gate (the nav-bounce FIX: cert carries identity; header fallback = never-lockout)\n" as *u8)
94
95 // identity X enrolled lvl 2; minted cert + hex
96 let uidX: *u8 = sys_mmap(32)
97 var i: i64 = 0
98 while i < 32 { uidX[i] = (0x10 + i) as u8; i = i + 1 }
99 let hexX: *u8 = sys_mmap(72); az_hex(uidX, 32, hexX)
100 let certX: *u8 = sys_mmap(NX_MAGIC_4096); let lenX: i64 = az_mint(uidX, certX, NX_MAGIC_4096)
101
102 // roster (uidhex->level) + uid_index (uidhex->handle, the olgd_idx_lookup stand-in)
103 let roster: *i64 = sys_mmap(8 * 4) as *i64; let rlens: *i64 = sys_mmap(8 * 4) as *i64; let levels: *i64 = sys_mmap(8 * 4) as *i64
104 roster[0] = hexX as i64; rlens[0] = 64; levels[0] = 2
105 let roster_n: i64 = 1
106 let uidx: *u8 = sys_mmap(256); let uidx_n: i64 = hr_put(uidx, 0, hexX, 64, "handleX" as *u8, 7)
107 // sessions (token->handle, the existing header fallback)
108 let sess: *u8 = sys_mmap(256); let sess_n: i64 = hr_put(sess, 0, "tokenT" as *u8, 6, "handleT" as *u8, 7)
109
110 let h: *u8 = sys_mmap(128)
111 var pass: i64 = 0
112
113 // T1 -- THE FIX: a top-level NAV (no X-Nishi-Session header) WITH a verified cert -> handle resolved from the cert
114 let r1: i64 = nx_hgw_mtls_resolve_handle(1, certX, lenX, roster, rlens, levels, roster_n, uidx, uidx_n, "" as *u8, 0, sess, sess_n, h, 128)
115 var t1: i64 = 0
116 if r1 > 0 { if hr_streq(h, r1, "handleX" as *u8) == 1 { t1 = 1 } }
117 pass = pass + hr_row("T1 THE FIX: a nav with NO header but a verified cert -> identity resolved (handleX)" as *u8, t1)
118
119 // T2 -- the BUG, for contrast: the SAME nav with NO cert and NO header -> anonymous (0) -> caller 302 (today)
120 let r2: i64 = nx_hgw_mtls_resolve_handle(0, certX, lenX, roster, rlens, levels, roster_n, uidx, uidx_n, "" as *u8, 0, sess, sess_n, h, 128)
121 var t2: i64 = 0
122 if r2 == 0 { t2 = 1 }
123 pass = pass + hr_row("T2 contrast: a nav with NEITHER cert NOR header -> anonymous (the 302 bounce, pre-mTLS)" as *u8, t2)
124
125 // T3 -- a presented-but-bad cert (verify-failed) -> REJECT (403), never silently downgrades to fallback
126 let r3: i64 = nx_hgw_mtls_resolve_handle(0 - 1, certX, lenX, roster, rlens, levels, roster_n, uidx, uidx_n, "tokenT" as *u8, 6, sess, sess_n, h, 128)
127 var t3: i64 = 0
128 if r3 == NX_HMH_REJECT { t3 = 1 }
129 pass = pass + hr_row("T3 presented-but-bad cert -> REJECT (403), no downgrade to the header even if one exists" as *u8, t3)
130
131 // T4 -- NEVER-LOCKOUT: no cert but a valid header token -> falls back to the header handle (existing clients work)
132 let r4: i64 = nx_hgw_mtls_resolve_handle(0, certX, lenX, roster, rlens, levels, roster_n, uidx, uidx_n, "tokenT" as *u8, 6, sess, sess_n, h, 128)
133 var t4: i64 = 0
134 if r4 > 0 { if hr_streq(h, r4, "handleT" as *u8) == 1 { t4 = 1 } }
135 pass = pass + hr_row("T4 never-lockout: no cert + valid header token -> header handle (handleT), today's clients keep working" as *u8, t4)
136
137 // T5 -- graceful: verified+enrolled cert but uid NOT yet in the index -> falls back to the header token
138 let r5: i64 = nx_hgw_mtls_resolve_handle(1, certX, lenX, roster, rlens, levels, roster_n, uidx, 0, "tokenT" as *u8, 6, sess, sess_n, h, 128)
139 var t5: i64 = 0
140 if r5 > 0 { if hr_streq(h, r5, "handleT" as *u8) == 1 { t5 = 1 } }
141 pass = pass + hr_row("T5 graceful: verified cert but uid not yet indexed -> falls back to header (handleT)" as *u8, t5)
142
143 // T6 -- PRECEDENCE: when BOTH a cert AND a header are present, the cert identity wins (handleX, not handleT)
144 let r6: i64 = nx_hgw_mtls_resolve_handle(1, certX, lenX, roster, rlens, levels, roster_n, uidx, uidx_n, "tokenT" as *u8, 6, sess, sess_n, h, 128)
145 var t6: i64 = 0
146 if r6 > 0 { if hr_streq(h, r6, "handleX" as *u8) == 1 { t6 = 1 } }
147 pass = pass + hr_row("T6 precedence: cert + header both present -> the CERT identity wins (handleX)" as *u8, t6)
148
149 if pass == 6 {
150 hr_w("NX-HGW-MTLS-RESOLVE GATE GREEN 6/6 (nav-bounce FIXED by the cert; header fallback preserved; one call drops into hgw_decide)\n" as *u8)
151 sys_exit(0)
152 }
153 hr_w("NX-HGW-MTLS-RESOLVE GATE RED\n" as *u8)
154 sys_exit(1)
155 return 1
156}