nx_https_cert_pipeline.nx source
↩ module page · 297 lines · 13582 B
1// nx_https_cert_pipeline.nx -- top-level HTTPS cert verification
2// pipeline.
3//
4// Phase 0b §I.4 piece 8 of the chain-walker arc (the user-facing
5// API). Bundles the shipped substrate primitives into ONE call
6// that takes raw TLS 1.3 Certificate message bytes + hostname +
7// trust anchor + now-epoch and returns one composite verdict.
8//
9// Composes 4 substrate primitives:
10// nx_tls13_auth.tls13_parse_certificate_chain_all (9f35188f)
11// -- extract cert offset array from Certificate message
12// nx_x509.x509_parse (extended for issuer/subject DN)
13// -- parse each cert's DER into an X509Cert struct
14// nx_x509_leaf_check.x509_leaf_check (50d861a8)
15// -- leaf-cert validity period + SAN hostname match
16// nx_x509_chain_verify.nx_x509_chain_verify (afe895e6)
17// -- chain walker (DN linkage + per-cert sig verify)
18//
19// Flow:
20// 1. parse_certificate_chain_all -> cert offset array
21// 2. for each offset, x509_parse into a parsed-cert slab
22// 3. leaf_check(cert[0]) -- validity period + SAN hostname match
23// (returns LEAF_* on first failure)
24// 4. Append the trust anchor cert at the end of the parsed array
25// and call chain_verify on the (leaf..root) ordering
26// 5. Return composite verdict
27//
28// Public API:
29// nx_https_cert_pipeline_verify(
30// cert_msg_buf, cert_msg_len,
31// hostname, hostname_len,
32// now_epoch_secs,
33// trust_anchor_buf, trust_anchor_cert
34// ) -> verdict
35//
36// nx_https_cert_pipeline_verdict_is_valid(v) -> 0|1
37//
38// Sealed verdict enum:
39// NX_HTTPS_PIPELINE_OK full validation passed
40// NX_HTTPS_PIPELINE_CERT_MSG_BAD Certificate-message parse fail
41// NX_HTTPS_PIPELINE_CERT_PARSE_FAIL x509_parse fail on some cert
42// NX_HTTPS_PIPELINE_LEAF_NOT_YET_VALID leaf cert not yet valid (clock)
43// NX_HTTPS_PIPELINE_LEAF_EXPIRED leaf cert expired
44// NX_HTTPS_PIPELINE_LEAF_NO_SAN leaf has no SAN extension
45// NX_HTTPS_PIPELINE_HOSTNAME_MISMATCH hostname doesn't match SAN
46// NX_HTTPS_PIPELINE_LEAF_OTHER other leaf-check failure
47// NX_HTTPS_PIPELINE_CHAIN_DN_MISMATCH DN linkage broken
48// NX_HTTPS_PIPELINE_CHAIN_SIG_FAIL cert sig under issuer failed
49// NX_HTTPS_PIPELINE_CHAIN_OTHER other chain-walk failure
50//
51// What this primitive does NOT do (caller responsibility):
52// - Pick the trust_anchor_cert from a root CA store. Caller
53// does that lookup (typically: get leaf's issuer DN -> look up
54// in Mozilla bundle subject-DN index -> pass matching root in).
55// - Revocation (OCSP / CRL).
56// - Extension policy (basicConstraints, keyUsage,
57// extendedKeyUsage, name constraints).
58//
59// Per Cardinals 9 (single-responsibility -- this is the
60// composition point, no new logic), 12 (defensive at boundaries --
61// cap on cert array size), 22 (composition -- 4 substrate
62// primitives into 1 user-facing call), 23 (preamble names every
63// caller responsibility).
64//
65// license_tier: INDEPENDENT_REDERIVE
66// genealogy_id: international-research-sources/ietf/rfc_5280 + rfc_8446
67// lineage_id: nishi_https_cert_pipeline_q10
68
69// nx_safety_envelope:
70// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
71// sil_target: SIL1
72// evidence: [bulk_applied_2026-05-19, https-cert-pipeline-orchestrator]
73// verdict: NOT_YET_EVALUATED
74
75import "nx_syscalls.nx"
76import "nx_x509.nx"
77import "nx_x509_leaf_check.nx"
78import "nx_x509_chain_verify.nx"
79import "nx_x509_trust_store.nx"
80import "nx_tls13_auth.nx"
81
82const NX_HTTPS_PIPELINE_OK: i64 = 1
83const NX_HTTPS_PIPELINE_CERT_MSG_BAD: i64 = 2
84const NX_HTTPS_PIPELINE_CERT_PARSE_FAIL: i64 = 3
85const NX_HTTPS_PIPELINE_LEAF_NOT_YET_VALID: i64 = 4
86const NX_HTTPS_PIPELINE_LEAF_EXPIRED: i64 = 5
87const NX_HTTPS_PIPELINE_LEAF_NO_SAN: i64 = 6
88const NX_HTTPS_PIPELINE_HOSTNAME_MISMATCH: i64 = 7
89const NX_HTTPS_PIPELINE_LEAF_OTHER: i64 = 8
90const NX_HTTPS_PIPELINE_CHAIN_DN_MISMATCH: i64 = 9
91const NX_HTTPS_PIPELINE_CHAIN_SIG_FAIL: i64 = 10
92const NX_HTTPS_PIPELINE_CHAIN_OTHER: i64 = 11
93const NX_HTTPS_PIPELINE_NO_TRUST_ANCHOR: i64 = 12
94const NX_HTTPS_PIPELINE_VERDICT_N: i64 = 13
95
96const NX_HTTPS_PIPELINE_MAX_CERTS: i64 = 8
97
98func nx_https_cert_pipeline_verdict_is_valid(v: i64) -> i64 {
99 if v < NX_HTTPS_PIPELINE_OK { return 0 }
100 if v >= NX_HTTPS_PIPELINE_VERDICT_N { return 0 }
101 return 1
102}
103
104// Parametrized core: verify the first `n_use_in` PRESENTED certs (leaf-first) with
105// `trust_anchor_*` appended as the terminal trust anchor. n_use_in <= 0 means "use ALL
106// presented certs" (the classic server-omitted-root case). n_use_in = n-1 lets the
107// resolver DROP a server-included (or cross-signed) root and re-terminate on OUR trusted
108// store copy of that root -- so the chain ALWAYS ends on a cert whose key we independently
109// trust, never on an attacker-presentable terminal. Single-responsibility core shared by
110// the public explicit-anchor API and the trust-store resolver.
111func _pipeline_verify_n(
112 cert_msg_buf: *u8, cert_msg_len: i64,
113 n_use_in: i64,
114 hostname: *u8, hostname_len: i64,
115 now_epoch_secs: i64,
116 trust_anchor_buf: *u8, trust_anchor_cert: *X509Cert
117) -> i64 {
118 // ---- Step 1: parse the Certificate message into offset arrays ----
119 let cert_offs_raw: *u8 = sys_mmap(NX_HTTPS_PIPELINE_MAX_CERTS * 8)
120 let cert_lens_raw: *u8 = sys_mmap(NX_HTTPS_PIPELINE_MAX_CERTS * 8)
121 let cert_offs: *i64 = cert_offs_raw as *i64
122 let cert_lens: *i64 = cert_lens_raw as *i64
123 let n_certs_p: *i64 = sys_mmap(16) as *i64
124
125 let parse_msg: i64 = tls13_parse_certificate_chain_all(
126 cert_msg_buf, cert_msg_len,
127 cert_offs, cert_lens, n_certs_p,
128 NX_HTTPS_PIPELINE_MAX_CERTS
129 )
130 if parse_msg != NX_TLS13_AUTH_VERDICT_OK { return NX_HTTPS_PIPELINE_CERT_MSG_BAD }
131 let n_present: i64 = *n_certs_p
132 if n_present < 1 { return NX_HTTPS_PIPELINE_CERT_MSG_BAD }
133
134 // How many presented certs to include below the appended anchor.
135 var n_use: i64 = n_use_in
136 if n_use < 1 { n_use = n_present } // sentinel: use all presented
137 if n_use > n_present { n_use = n_present } // clamp defensively
138
139 // ---- Step 2: x509_parse each used cert; append the trust anchor at index n_use ----
140 let total_certs: i64 = n_use + 1
141 let bufs_raw: *u8 = sys_mmap(total_certs * 8)
142 let certs_raw: *u8 = sys_mmap(total_certs * 8)
143 let bufs: **u8 = bufs_raw as **u8
144 let certs: **X509Cert = certs_raw as **X509Cert
145
146 var i: i64 = 0
147 while i < n_use {
148 let c_raw: *u8 = sys_mmap(256)
149 let c: *X509Cert = c_raw as *X509Cert
150 let cert_der_ptr: *u8 = cert_msg_buf + cert_offs[i]
151 let rc: i64 = x509_parse(cert_der_ptr, cert_lens[i], c)
152 if rc < 0 { return NX_HTTPS_PIPELINE_CERT_PARSE_FAIL }
153 bufs[i] = cert_der_ptr
154 certs[i] = c
155 i = i + 1
156 }
157 // Append trust anchor (our trusted store copy = the terminal we trust)
158 bufs[n_use] = trust_anchor_buf
159 certs[n_use] = trust_anchor_cert
160
161 // ---- Step 3: leaf check (validity period + SAN hostname) -- always the real leaf ----
162 let leaf_v: i64 = x509_leaf_check(
163 bufs[0], cert_lens[0],
164 hostname, hostname_len, now_epoch_secs
165 )
166 if leaf_v == NX_X509_LEAF_OK {
167 // Continue to chain check.
168 } else {
169 if leaf_v == NX_X509_LEAF_NOT_YET_VALID { return NX_HTTPS_PIPELINE_LEAF_NOT_YET_VALID }
170 if leaf_v == NX_X509_LEAF_EXPIRED { return NX_HTTPS_PIPELINE_LEAF_EXPIRED }
171 if leaf_v == NX_X509_LEAF_NO_SAN { return NX_HTTPS_PIPELINE_LEAF_NO_SAN }
172 if leaf_v == NX_X509_LEAF_HOSTNAME_MISMATCH { return NX_HTTPS_PIPELINE_HOSTNAME_MISMATCH }
173 return NX_HTTPS_PIPELINE_LEAF_OTHER
174 }
175
176 // ---- Step 4: chain verify (every adjacent (child, parent) link) ----
177 let chain_v: i64 = nx_x509_chain_verify(bufs, certs, total_certs)
178 if chain_v == NX_X509_CHAIN_OK { return NX_HTTPS_PIPELINE_OK }
179 if chain_v == NX_X509_CHAIN_DN_MISMATCH { return NX_HTTPS_PIPELINE_CHAIN_DN_MISMATCH }
180 if chain_v == NX_X509_CHAIN_SIG_FAIL { return NX_HTTPS_PIPELINE_CHAIN_SIG_FAIL }
181 return NX_HTTPS_PIPELINE_CHAIN_OTHER
182}
183
184// Public explicit-anchor API (UNCHANGED contract): verify ALL presented certs against the
185// caller-supplied anchor appended as terminal. Thin wrapper over the parametrized core.
186func nx_https_cert_pipeline_verify(
187 cert_msg_buf: *u8, cert_msg_len: i64,
188 hostname: *u8, hostname_len: i64,
189 now_epoch_secs: i64,
190 trust_anchor_buf: *u8, trust_anchor_cert: *X509Cert
191) -> i64 {
192 return _pipeline_verify_n(
193 cert_msg_buf, cert_msg_len,
194 0, // 0 => use all presented certs
195 hostname, hostname_len,
196 now_epoch_secs,
197 trust_anchor_buf, trust_anchor_cert
198 )
199}
200
201// Trust-store-aware variant. Like nx_https_cert_pipeline_verify
202// but resolves the trust anchor automatically by looking up the
203// LAST presented cert's issuer DN in the supplied store.
204//
205// Use case: caller has a Mozilla root CA bundle loaded into a
206// TrustStore (one anchor per root), and wants the substrate to
207// figure out which root the peer's chain terminates at. No need
208// to pre-pick the trust anchor based on hostname.
209//
210// Returns NX_HTTPS_PIPELINE_NO_TRUST_ANCHOR if the last cert's
211// issuer DN matches no entry in the store. Other verdicts pass
212// through from the inner pipeline.
213//
214// Algorithm:
215// 1. parse_certificate_chain_all -> offsets
216// 2. x509_parse the LAST cert (needed for issuer DN); rest are
217// parsed again inside the inner verify call (cheap)
218// 3. trust_store_lookup_by_subject(last.issuer)
219// 4. If found -> delegate to nx_https_cert_pipeline_verify with
220// that anchor.
221func nx_https_cert_pipeline_verify_with_store(
222 cert_msg_buf: *u8, cert_msg_len: i64,
223 hostname: *u8, hostname_len: i64,
224 now_epoch_secs: i64,
225 store: *TrustStore
226) -> i64 {
227 // ---- Step 1: parse cert offsets ----
228 let offs_raw: *u8 = sys_mmap(NX_HTTPS_PIPELINE_MAX_CERTS * 8)
229 let lens_raw: *u8 = sys_mmap(NX_HTTPS_PIPELINE_MAX_CERTS * 8)
230 let offs: *i64 = offs_raw as *i64
231 let lens: *i64 = lens_raw as *i64
232 let n_p: *i64 = sys_mmap(16) as *i64
233 let parse_v: i64 = tls13_parse_certificate_chain_all(
234 cert_msg_buf, cert_msg_len, offs, lens, n_p,
235 NX_HTTPS_PIPELINE_MAX_CERTS
236 )
237 if parse_v != NX_TLS13_AUTH_VERDICT_OK { return NX_HTTPS_PIPELINE_CERT_MSG_BAD }
238 let n: i64 = *n_p
239 if n < 1 { return NX_HTTPS_PIPELINE_CERT_MSG_BAD }
240
241 // ---- Step 2: parse the LAST cert to read its issuer DN ----
242 let last_buf: *u8 = cert_msg_buf + offs[n - 1]
243 let last_len: i64 = lens[n - 1]
244 let last_cert_raw: *u8 = sys_mmap(256)
245 let last_cert: *X509Cert = last_cert_raw as *X509Cert
246 let rc: i64 = x509_parse(last_buf, last_len, last_cert)
247 if rc < 0 { return NX_HTTPS_PIPELINE_CERT_PARSE_FAIL }
248
249 // ---- Step 3: trust-anchor resolution -- handle BOTH chain shapes ----
250 // Case A (root-INCLUDED / cross-signed root): the last presented cert IS a trusted root
251 // (its SUBJECT matches a store anchor). Modern servers send these -- e.g. Google's
252 // chain ends in GTS Root R4 cross-signed by GTS Root R1 (R1 is the *issuer*, not in
253 // our store; R4 is the trusted root, IS in our store). Re-terminate on OUR trusted
254 // store copy of that root and verify the presented chain MINUS its (redundant, and
255 // not-independently-trusted) terminal. This is the gap that blocked every Cloudflare/
256 // Google-fronted site while servers that OMIT the root (Case B) worked.
257 // Case B (root-OMITTED): the last presented cert's ISSUER is a trusted root. Append it.
258 // SECURITY: in BOTH cases the chain terminates on a cert whose public key we independently
259 // trust (the store copy), and every link is signature-verified up to it -- a forged
260 // "GTS Root R4" with a different key cannot validate, because we verify the intermediate
261 // under OUR copy's key, never the presented terminal's.
262 let anchor_by_issuer: *TrustAnchor = trust_store_lookup_by_subject(
263 store, last_buf, last_cert.issuer_off, last_cert.issuer_len
264 )
265 let anchor_by_subject: *TrustAnchor = trust_store_lookup_by_subject(
266 store, last_buf, last_cert.subject_off, last_cert.subject_len
267 )
268
269 // Case A takes precedence: a presented terminal that IS a trusted root is authoritative.
270 if (anchor_by_subject as i64) != 0 {
271 if n >= 2 {
272 return _pipeline_verify_n(
273 cert_msg_buf, cert_msg_len,
274 n - 1, // drop the presented (untrusted) root copy
275 hostname, hostname_len,
276 now_epoch_secs,
277 anchor_by_subject.buf, anchor_by_subject.cert
278 )
279 }
280 }
281 // Case B: server omitted the root; append the one whose subject == last cert's issuer.
282 if (anchor_by_issuer as i64) != 0 {
283 return _pipeline_verify_n(
284 cert_msg_buf, cert_msg_len,
285 n, // use all presented, append the root
286 hostname, hostname_len,
287 now_epoch_secs,
288 anchor_by_issuer.buf, anchor_by_issuer.cert
289 )
290 }
291 return NX_HTTPS_PIPELINE_NO_TRUST_ANCHOR
292}
293
294// Compile-only smoke. Real KAT in nx_https_cert_pipeline_test.nx.
295func main() -> i64 {
296 return 0
297}