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 }