code wiki / (root) / nx_aw_mtls_inject.nx

nx_aw_mtls_inject.nx source

↩ module page · 99 lines · 4807 B

1// nx_aw_mtls_inject.nx -- inject the VERIFIED mTLS client identity into the decrypted HTTP request as an 2// X-Nishi-Cert-Identity header, so the loopback backend can serve-by-identity. This is the genuinely NEW 3// logic of the sovereign mTLS carrier (the TLS handshake + the reverse-proxy mechanics are already proven: 4// run_ecdsa_mtls loopback+interop 6/6, nx_aw_tlsproxy live for the CMS admin). 5// 6// THE PROXY IS THE TRUST BOUNDARY (rule 12, defensive at boundaries). Two invariants, both security-floor: 7// 1. ALWAYS strip any client-supplied X-Nishi-Cert-Identity header from the request -- else a NO-cert 8// client simply sends its own and the backend trusts a SPOOFED identity. Strip happens for every auth 9// value (0/1/-1) and only inside the HEADER section (a body that happens to contain the string is left 10// verbatim). 11// 2. Inject the REAL identity ONLY when auth==1 (a client cert was PRESENTED and its CertificateVerify 12// VERIFIED this handshake). auth==0 (no cert) or auth==-1 (bad cert) => no identity injected = request- 13// not-require / NEVER-LOCKOUT (the backend falls back to its X-Nishi-Session header path exactly as today). 14// 15// Identity = the cert Subject CN (hex(user_id_hash)) via the canonical nx_mtls_cert_identity -- the SAME 16// stable id the OPAQUE/HR stack keys on, so the backend maps it with the existing uidhex->handle index. 17// license_tier: ORIGINAL expect_exit: 0 18import "nx_syscalls.nx" 19import "nx_mtls_authz.nx" // nx_mtls_cert_identity (Subject CN extraction; DRY -- the enforcement layer's own id) 20 21const MI_HDR: *u8 = "X-Nishi-Cert-Identity: " // 23 bytes (name + ": ") 22 23// lowercase an ASCII byte (for case-insensitive header-name match -- HTTP field names are case-insensitive). 24func mi_lc(b: i64) -> i64 { if b >= 65 { if b <= 90 { return b + 32 } } return b } 25 26// does req[i..n] begin with the lowercase, nul-terminated literal `lit` (case-insensitive)? 27func mi_ci_starts(req: *u8, i: i64, n: i64, lit: *u8) -> i64 { 28 var k: i64 = 0 29 while lit[k] != (0 as u8) { 30 if i + k >= n { return 0 } 31 if mi_lc(req[i+k] as i64) != (lit[k] as i64) { return 0 } 32 k = k + 1 33 } 34 return 1 35} 36 37// index just AFTER the first \r\n at/after `start` within req[0..n]; -1 if none. 38func mi_line_end(req: *u8, start: i64, n: i64) -> i64 { 39 var i: i64 = start 40 while i + 1 < n { 41 if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { return i + 2 } } 42 i = i + 1 43 } 44 return 0 - 1 45} 46 47// copy req[a..b] into dst at offset o; return the new offset. 48func mi_copy(dst: *u8, o: i64, src: *u8, a: i64, b: i64) -> i64 { 49 var oo: i64 = o 50 var i: i64 = a 51 while i < b { dst[oo] = src[i]; oo = oo + 1; i = i + 1 } 52 return oo 53} 54 55// Rewrite `req` (req_n bytes) into `out`: strip any client X-Nishi-Cert-Identity, inject the real one iff 56// auth==1. Returns the rewritten length, or -1 on out_cap overflow (caller must size out >= req_n + ~300). 57func mtls_inject_identity(req: *u8, req_n: i64, cert: *u8, cc_len: i64, auth: i64, out: *u8, out_cap: i64) -> i64 { 58 let lit: *u8 = "x-nishi-cert-identity:" as *u8 59 let rle: i64 = mi_line_end(req, 0, req_n) 60 if rle < 0 { 61 // No request line (not an HTTP request head) -> cannot safely place a header; pass through verbatim. 62 if req_n > out_cap { return 0 - 1 } 63 return mi_copy(out, 0, req, 0, req_n) 64 } 65 var o: i64 = 0 66 // (1) request line verbatim 67 o = mi_copy(out, o, req, 0, rle) 68 // (2) inject the REAL identity, only on a verified cert 69 if auth == 1 { 70 let idbuf: *u8 = sys_mmap(256) 71 let idn: i64 = nx_mtls_cert_identity(cert, cc_len, idbuf, 256) 72 if idn > 0 { 73 if o + 23 + idn + 2 > out_cap { return 0 - 1 } 74 o = mi_copy(out, o, MI_HDR, 0, 23) 75 o = mi_copy(out, o, idbuf, 0, idn) 76 out[o] = 13 as u8; o = o + 1; out[o] = 10 as u8; o = o + 1 77 } 78 } 79 // (3) copy remaining header lines, STRIPPING the spoof header; at the blank line, copy the rest verbatim. 80 var i: i64 = rle 81 while i < req_n { 82 if i + 1 < req_n { if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { 83 // end-of-headers blank line -> blank line + body verbatim, done. 84 if o + (req_n - i) > out_cap { return 0 - 1 } 85 return mi_copy(out, o, req, i, req_n) 86 } } } 87 let le: i64 = mi_line_end(req, i, req_n) 88 var lend: i64 = req_n 89 if le >= 0 { lend = le } 90 if mi_ci_starts(req, i, req_n, lit) == 1 { 91 i = lend // strip the client-supplied identity line 92 } else { 93 if o + (lend - i) > out_cap { return 0 - 1 } 94 o = mi_copy(out, o, req, i, lend) 95 i = lend 96 } 97 } 98 return o 99}