code wiki / _hdl_build / nx_cert_carrier.nx
nx_cert_carrier.nx source
↩ module page · 121 lines · 6428 B
1// nx_cert_carrier.nx -- the BACKEND trust side of the sovereign no-JS cert carrier (rung 3 of the hosting
2// platform / the login project). A request reaches the content backend (wiki_gw = nx_opaque_login_daemon) ONLY
3// via the trusted personal daemon, which strips any client-supplied copies (nx_aw_mtls_inject 10/10) and injects
4// the REAL X-Nishi-Cert-Identity (the uid) + X-Nishi-Proxy-Auth (a shared secret). cc_accept_uid trusts the
5// identity ONLY when X-Nishi-Proxy-Auth EXACTLY equals the secret -- so a LAN attacker hitting the backend port
6// directly with a forged X-Nishi-Cert-Identity (no secret) gets NOTHING, falls back to the X-Nishi-Session path.
7// Defensive at the boundary (rule 12). license_tier: ORIGINAL expect_exit: 0
8import "nx_syscalls.nx"
9
10func cc_lc(b: i64) -> i64 { if b >= 65 { if b <= 90 { return b + 32 } } return b }
11
12// does req[i..n] begin with the lowercase, nul-terminated header name `name_lc` (case-insensitive)?
13func cc_ci_at(req: *u8, i: i64, n: i64, name_lc: *u8) -> i64 {
14 var k: i64=0
15 while name_lc[k]!=(0 as u8) {
16 if i+k >= n { return 0 }
17 if cc_lc(req[i+k] as i64) != (name_lc[k] as i64) { return 0 }
18 k=k+1
19 }
20 return 1
21}
22// index just AFTER the first \r\n at/after `start` (or n if none).
23func cc_line_end(req: *u8, start: i64, n: i64) -> i64 {
24 var i: i64 = start; var go: i64 = 1
25 while go == 1 {
26 if i + 1 >= n { go = 0; i = n } else {
27 if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { go = 0; i = i + 2 } else { i = i + 1 } } else { i = i + 1 }
28 }
29 }
30 return i
31}
32func cc_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { if an != bn { return 0 } var i: i64=0; while i<an { if a[i]!=b[i] { return 0 } i=i+1 } return 1 }
33
34// extract the value of header `name_lc` (lowercase incl. the colon, e.g. "x-foo:") from the request headers into
35// out (NUL-terminated); return the value length, 0 if the header is absent. Header-section only.
36func cc_header_value(req: *u8, req_n: i64, name_lc: *u8, out: *u8, cap: i64) -> i64 {
37 var nk: i64=0; while name_lc[nk]!=(0 as u8){nk=nk+1}
38 var i: i64 = cc_line_end(req, 0, req_n) // start after the request line
39 var go: i64 = 1
40 while go == 1 {
41 if i >= req_n { go = 0 } else {
42 var blank: i64 = 0
43 if i + 1 < req_n { if req[i]==(13 as u8) { if req[i+1]==(10 as u8) { blank = 1 } } }
44 if blank == 1 { go = 0 } else {
45 if cc_ci_at(req, i, req_n, name_lc) == 1 {
46 var p: i64 = i + nk
47 var sp: i64 = 1
48 while sp == 1 { if p < req_n { if req[p]==(32 as u8) { p = p + 1 } else { sp = 0 } } else { sp = 0 } }
49 let vs: i64 = p
50 var ve: i64 = p; var fe: i64 = 1
51 while fe == 1 { if ve >= req_n { fe = 0 } else { if req[ve]==(13 as u8) { fe = 0 } else { if req[ve]==(10 as u8) { fe = 0 } else { ve = ve + 1 } } } }
52 let vl: i64 = ve - vs
53 var k: i64=0; while k<vl { if k<cap-1 { out[k]=req[vs+k] } k=k+1 }
54 if vl < cap { out[vl]=0 as u8 } else { out[cap-1]=0 as u8 }
55 return vl
56 }
57 i = cc_line_end(req, i, req_n)
58 }
59 }
60 }
61 return 0
62}
63
64// FRONTEND egress: the personal daemon calls cc_inject_secret AFTER mtls_inject_identity -- strip any client
65// X-Nishi-Proxy-Auth + inject the REAL secret. NishiLang gotcha: a module-level `const *u8` indexed byte-by-byte
66// here emitted GARBAGE; a literal PASSED AS AN ARG + copied in a helper reads fine (cf. nx_aw_mtls_inject mi_copy).
67func cc_cpy(dst: *u8, o: i64, src: *u8) -> i64 {
68 var oo: i64=o; var i: i64=0
69 while src[i]!=(0 as u8) { dst[oo]=src[i]; oo=oo+1; i=i+1 }
70 return oo
71}
72
73// strip any client X-Nishi-Proxy-Auth from `req` and inject the real `secret`; return the rewritten length (or -1
74// on overflow). Identity is handled separately by mtls_inject_identity (call it FIRST). Header-section only.
75func cc_inject_secret(req: *u8, req_n: i64, secret: *u8, secret_n: i64, out: *u8, out_cap: i64) -> i64 {
76 let lit: *u8 = "x-nishi-proxy-auth:" as *u8
77 let rle: i64 = cc_line_end(req, 0, req_n)
78 if rle >= req_n { // no request line -> pass through unchanged
79 if req_n > out_cap { return 0 - 1 }
80 var j: i64=0; while j<req_n { out[j]=req[j]; j=j+1 }
81 return req_n
82 }
83 var o: i64 = 0
84 var i: i64 = 0
85 while i < rle { out[o]=req[i]; o=o+1; i=i+1 } // request line + its CRLF
86 if secret_n > 0 { // inject the real secret right after the request line
87 if o + 20 + secret_n + 2 > out_cap { return 0 - 1 }
88 o = cc_cpy(out, o, "X-Nishi-Proxy-Auth: " as *u8)
89 var s: i64=0; while s<secret_n { out[o]=secret[s]; o=o+1; s=s+1 }
90 out[o]=13 as u8; o=o+1; out[o]=10 as u8; o=o+1
91 }
92 var go: i64 = 1
93 while go == 1 {
94 if i >= req_n { go = 0 } else {
95 var blank: i64 = 0
96 if i+1<req_n { if req[i]==(13 as u8) { if req[i+1]==(10 as u8) { blank=1 } } }
97 if blank == 1 {
98 if o + (req_n - i) > out_cap { return 0 - 1 }
99 while i < req_n { out[o]=req[i]; o=o+1; i=i+1 }
100 go = 0
101 } else {
102 let le: i64 = cc_line_end(req, i, req_n)
103 if cc_ci_at(req, i, req_n, lit) == 1 { i = le } else { // strip the client's copy
104 if o + (le - i) > out_cap { return 0 - 1 }
105 while i < le { out[o]=req[i]; o=o+1; i=i+1 }
106 }
107 }
108 }
109 }
110 return o
111}
112
113// THE TRUST GATE: return the trusted uid (X-Nishi-Cert-Identity) length into out_uid, ONLY IF X-Nishi-Proxy-Auth
114// exactly equals the (non-empty) shared secret. 0 = not trusted -> the backend uses its existing X-Nishi-Session path.
115func cc_accept_uid(req: *u8, req_n: i64, secret: *u8, secret_n: i64, out_uid: *u8, cap: i64) -> i64 {
116 if secret_n <= 0 { return 0 } // never trust if no secret is configured
117 let auth: *u8 = sys_mmap(512)
118 let an: i64 = cc_header_value(req, req_n, "x-nishi-proxy-auth:" as *u8, auth, 512)
119 if cc_eq(auth, an, secret, secret_n) == 0 { return 0 } // wrong/absent secret -> spoof rejected
120 return cc_header_value(req, req_n, "x-nishi-cert-identity:" as *u8, out_uid, cap)
121}