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}