code wiki / (root) / nx_x509_trust_store.nx

nx_x509_trust_store.nx source

↩ module page · 158 lines · 5892 B

1// nx_x509_trust_store.nx -- trust anchor store + subject-DN lookup. 2// 3// Phase 0b §I.4 piece 9 of the chain-walker arc. Provides the 4// data structure + lookup helper for cert chain validation's 5// trust-anchor input. 6// 7// Model: A trust store holds an array of TrustAnchor entries. 8// Each entry pairs a subject DN (full DER-encoded Name SEQUENCE 9// bytes, byte-equality matchable) with a parsed X509Cert struct 10// + its backing DER buffer that the chain walker uses as the 11// "root" cert. 12// 13// Lookup: given the LAST cert in a peer's presented chain (the 14// one signed by a CA), find its issuer DN, then search the store 15// for a TrustAnchor whose subject DN byte-equals that issuer DN. 16// If found -> caller has its trust anchor. If not -> chain 17// terminates outside the trust store (UNTRUSTED). 18// 19// Why subject-DN-keyed lookup: 20// - RFC 5280 §6.1.1 says the trust anchor is identified by 21// its issuer DN matching the next cert's subject DN. 22// - Indexing by full DER bytes (vs. parsing into a canonical 23// form first) keeps the primitive sovereign + simple. Real 24// CAs emit byte-equal DN encodings in matched pairs. 25// - For multi-megabyte CA bundles a hash-table or binary tree 26// would be needed; current linear scan is adequate for the 27// Mozilla NSS bundle (~150 roots) at qemu speed. 28// 29// Public API: 30// struct TrustAnchor { 31// buf: *u8, DER cert buffer 32// cert: *X509Cert, already-parsed cert 33// } 34// trust_store_alloc(max_n) -> *TrustStore 35// trust_store_add(store, buf, cert) -> verdict 36// trust_store_lookup_by_subject(store, dn_buf, dn_off, dn_len) 37// -> *TrustAnchor (or NULL if not found) 38// trust_store_count(store) -> i64 39// 40// Sealed verdict enum for the add path: 41// NX_TRUST_STORE_OK anchor added 42// NX_TRUST_STORE_FULL store at capacity 43// NX_TRUST_STORE_BAD cert has invalid subject DN 44// 45// What this primitive does NOT do (caller responsibility): 46// - Verify trust anchors are self-signed (they should be, but 47// this primitive trusts the caller's selection) 48// - Constrain by name constraints / policy mappings 49// - Multi-platform CA store loading (Mozilla NSS vs. Apple 50// keychain vs. Windows root store) -- separate primitive 51// that calls trust_store_add for each entry 52// 53// Per Cardinals 9 (single-responsibility -- just store + lookup), 54// 12 (defensive at boundaries -- capacity cap + DN validity check), 55// 22 (composition -- caller's CA-bundle parser populates this 56// store, chain walker reads from it). 57// 58// license_tier: INDEPENDENT_REDERIVE 59// genealogy_id: international-research-sources/ietf/rfc_5280 + mozilla/nss 60// lineage_id: nishi_x509_trust_store_q10 61 62// nx_safety_envelope: 63// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 64// sil_target: SIL1 65// evidence: [bulk_applied_2026-05-19, x509-trust-store-foundation] 66// verdict: NOT_YET_EVALUATED 67 68import "nx_syscalls.nx" 69import "nx_x509.nx" 70import "nx_x509_dn_match.nx" 71 72const NX_TRUST_STORE_OK: i64 = 1 73const NX_TRUST_STORE_FULL: i64 = 2 74const NX_TRUST_STORE_BAD: i64 = 3 75const NX_TRUST_STORE_VERDICT_N: i64 = 4 76 77// One anchor entry. The cert MUST have been parsed against the 78// matching buf -- the store doesn't re-parse, it relies on the 79// caller's prior x509_parse + the fact that x509_parse populates 80// subject_off/_len. 81struct TrustAnchor { 82 buf: *u8, 83 cert: *X509Cert, 84} 85 86// The store: a fixed-cap pool + count. Capacity is set at 87// alloc time; nx_https_pipeline tests use 4 entries, a real 88// Mozilla bundle would use ~150. 89struct TrustStore { 90 anchors: *TrustAnchor, // pointer to anchors[0..cap) 91 cap: i64, 92 n: i64, 93} 94 95func nx_trust_store_verdict_is_valid(v: i64) -> i64 { 96 if v < NX_TRUST_STORE_OK { return 0 } 97 if v >= NX_TRUST_STORE_VERDICT_N { return 0 } 98 return 1 99} 100 101// Allocate a new store with capacity for `max_n` anchors. 102func trust_store_alloc(max_n: i64) -> *TrustStore { 103 let st_raw: *u8 = sys_mmap(32) 104 let store: *TrustStore = st_raw as *TrustStore 105 // TrustAnchor is 2 pointers = 16 bytes per entry. 106 let anchors_raw: *u8 = sys_mmap(max_n * 16) 107 store.anchors = anchors_raw as *TrustAnchor 108 store.cap = max_n 109 store.n = 0 110 return store 111} 112 113// Number of anchors currently in the store. 114func trust_store_count(store: *TrustStore) -> i64 { 115 return store.n 116} 117 118// Append an anchor (buf, cert). Caller has already x509_parse'd 119// `cert` against `buf`; the store does not re-parse. 120func trust_store_add(store: *TrustStore, buf: *u8, cert: *X509Cert) -> i64 { 121 if store.n >= store.cap { return NX_TRUST_STORE_FULL } 122 if cert.subject_len <= 0 { return NX_TRUST_STORE_BAD } 123 let slot: *TrustAnchor = (store.anchors as *u8 + store.n * 16) as *TrustAnchor 124 slot.buf = buf 125 slot.cert = cert 126 store.n = store.n + 1 127 return NX_TRUST_STORE_OK 128} 129 130// Look up an anchor whose subject DN byte-equals the caller's 131// (dn_buf + dn_off, dn_len). Returns the matching *TrustAnchor, 132// or NULL if not found. 133// 134// Linear scan -- adequate for ~150-entry Mozilla bundle at qemu 135// speed. An indexed lookup (hash of leading bytes of subject DN) 136// is a separate optimization queued for when a real bundle is 137// embedded. 138func trust_store_lookup_by_subject( 139 store: *TrustStore, 140 dn_buf: *u8, dn_off: i64, dn_len: i64 141) -> *TrustAnchor { 142 var i: i64 = 0 143 while i < store.n { 144 let slot: *TrustAnchor = (store.anchors as *u8 + i * 16) as *TrustAnchor 145 let c: *X509Cert = slot.cert 146 if nx_x509_dn_match(slot.buf, c.subject_off, c.subject_len, 147 dn_buf, dn_off, dn_len) == 1 { 148 return slot 149 } 150 i = i + 1 151 } 152 return 0 as *TrustAnchor 153} 154 155// Compile-only smoke. Real KAT in nx_x509_trust_store_test.nx. 156func main() -> i64 { 157 return 0 158}