code wiki / (root) / nx_sni_cert_select.nx

nx_sni_cert_select.nx source

↩ module page · 104 lines · 5596 B

1// nx_sni_cert_select.nx -- SNI -> per-domain wildcard-cert SELECTION for the SNI-aware 2// sovereign TLS site daemon (Route B). The SNI host_name is CLEARTEXT in the first 3// ClientHello record (RFC 6066), so the daemon can peek it BEFORE terminating TLS and 4// decide WHICH loaded wildcard chain+key pair to present: 5// 6// index 0 = NISHI (*.nishifamily.com + nishifamily.com) <- the DEFAULT 7// index 1 = ANDELIN (*.andelinwest.com + andelinwest.com) 8// 9// Matching is LABEL-AWARE and case-insensitive (DNS labels are case-insensitive): 10// a host matches a domain iff it EQUALS the domain (registrable apex) OR ends with 11// ".<domain>" (a real sub-label) -- so "app.nishifamily.com" matches but the look-alike 12// "evilnishifamily.com" does NOT (it falls to the default instead of being mis-selected). 13// 14// FAIL-SAFE BY CONSTRUCTION (rule 12 + rule 14): a missing / empty / malformed / unknown 15// SNI returns the DEFAULT index, never an error and never a crash. This function only 16// CHOOSES which already-validated cert to present; it AUTHORIZES nothing -- a client that 17// sends a bogus SNI simply gets the default cert and fails validation on ITS side. 18// 19// Composes the proven, bounds-checked sni_extract_hostname parser (nx_sni_extract.nx, the 20// same organ nx_sni_router peeks with) -- it does NOT add a second ClientHello parser 21// (rule 15 DRY). Pure decision logic so both the daemon AND the gate share ONE codepath. 22// 23// license_tier: ORIGINAL 24 25import "nx_syscalls.nx" 26import "nx_sni_extract.nx" 27 28// Cert-table indices. NISHI is index 0 == the default (returned for any host that does 29// not positively match a known domain), so an absent/garbled SNI degrades to nishifamily. 30const NX_SCS_CERT_NISHI: i64 = 0 31const NX_SCS_CERT_ANDELIN: i64 = 1 32 33// lowercase one ASCII byte (A-Z -> a-z); leaves everything else untouched. 34func scs_lc(c: i64) -> i64 { 35 if c >= 65 { if c <= 90 { return c + 32 } } 36 return c 37} 38 39// Label-aware, case-insensitive suffix match. Returns 1 iff: 40// host_n == suf_n AND host == suf (exact registrable apex), OR 41// host ends with suf AND the byte just before suf is '.' (a real sub-label). 42// Bounds-checked: host shorter than suf, or suf_n <= 0, can never match. 43func scs_label_endswith(host: *u8, host_n: i64, suf: *u8, suf_n: i64) -> i64 { 44 if suf_n <= 0 { return 0 } 45 if host_n < suf_n { return 0 } 46 let start: i64 = host_n - suf_n 47 var i: i64 = 0 48 while i < suf_n { 49 if scs_lc(host[start + i] as i64) != scs_lc(suf[i] as i64) { return 0 } 50 i = i + 1 51 } 52 if start == 0 { return 1 } // exact apex 53 if (host[start - 1] as i64) == 46 { return 1 } // 46 == '.' -> real sub-label 54 return 0 55} 56 57// Decide the cert-table index from an already-extracted host of length host_n 58// (NUL-termination NOT required; host_n is authoritative). Empty / unknown -> default. 59func scs_pick_index(host: *u8, host_n: i64) -> i64 { 60 if host_n <= 0 { return NX_SCS_CERT_NISHI } 61 if scs_label_endswith(host, host_n, "andelinwest.com" as *u8, 15) == 1 { return NX_SCS_CERT_ANDELIN } 62 if scs_label_endswith(host, host_n, "nishifamily.com" as *u8, 15) == 1 { return NX_SCS_CERT_NISHI } 63 return NX_SCS_CERT_NISHI // unknown SNI -> default 64} 65 66// ALL-IN-ONE entry used by BOTH the daemon and the gate (single shared codepath): extract 67// the SNI host_name from a raw CLEARTEXT ClientHello (record header 0x16 ...) held in 68// hello[0..n), then pick the cert index. host_scratch is caller-owned (cap >= 256 advised). 69// Any parse failure (sni_extract_hostname returns 0) degrades to the default index. 70func scs_pick_from_clienthello(hello: *u8, n: i64, host_scratch: *u8, host_cap: i64) -> i64 { 71 let hn: i64 = sni_extract_hostname(hello, n, host_scratch, host_cap) 72 if hn <= 0 { return NX_SCS_CERT_NISHI } 73 return scs_pick_index(host_scratch, hn) 74} 75 76// ---- DATA-DRIVEN cert table (R1b 2026-07-15) ------------------------------------------------- 77// The hardcoded 2-domain path above (scs_pick_index) is kept for the gate + as a fail-safe. The 78// daemon now loads an N-entry cert table from a manifest and picks by these table-driven funcs, so 79// a new domain is drop-a-cert + a manifest line -- NEVER an edge recompile. Index 0 = the DEFAULT 80// (first manifest row); an absent/garbled/unknown SNI degrades to it, same fail-safe contract. 81 82// Pick the cert-table index for `host` against `count` domain suffixes held in parallel arrays 83// (suf_ptrs[i] = *u8 suffix, suf_lens[i] = its length). Returns the FIRST label-aware match, else 0. 84func scs_pick_index_table(host: *u8, host_n: i64, suf_ptrs: *i64, suf_lens: *i64, count: i64) -> i64 { 85 if host_n <= 0 { return 0 } 86 var i: i64 = 0 87 while i < count { 88 if scs_label_endswith(host, host_n, suf_ptrs[i] as *u8, suf_lens[i]) == 1 { return i } 89 i = i + 1 90 } 91 return 0 92} 93 94// All-in-one for the daemon: extract the SNI host from a raw ClientHello, then pick the table index. 95// (Lives here so the daemon needs no second ClientHello parser -- rule 15 DRY.) Any parse failure -> 0. 96func scs_pick_from_clienthello_table(hello: *u8, n: i64, host_scratch: *u8, host_cap: i64, 97 suf_ptrs: *i64, suf_lens: *i64, count: i64) -> i64 { 98 let hn: i64 = sni_extract_hostname(hello, n, host_scratch, host_cap) 99 if hn <= 0 { return 0 } 100 return scs_pick_index_table(host_scratch, hn, suf_ptrs, suf_lens, count) 101} 102 103// Compile-only smoke; real assertions live in nx_sites_sni_cert_gate.nx. 104func main() -> i64 { return 0 }