code wiki / (root) / nx_hgw_mtls_resolve.nx

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}