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}