⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/tls/
certificate.rs

1//! X.509 Certificate Parsing and Validation
2//!
3//! Provides simplified X.509 certificate parsing (DER-encoded) and
4//! a trust store for root CA chain validation.
5
6#[cfg(feature = "alloc")]
7use alloc::vec::Vec;
8
9// ============================================================================
10// ASN.1 DER Parsing
11// ============================================================================
12
13/// ASN.1 tag constants
14const ASN1_SEQUENCE: u8 = 0x30;
15const ASN1_SET: u8 = 0x31;
16const ASN1_OID: u8 = 0x06;
17const ASN1_UTF8STRING: u8 = 0x0C;
18const ASN1_PRINTABLESTRING: u8 = 0x13;
19const ASN1_BIT_STRING: u8 = 0x03;
20
21/// Parse ASN.1 DER tag and length. Returns (tag, content_start,
22/// content_length).
23pub(crate) fn asn1_parse_tlv(data: &[u8]) -> Option<(u8, usize, usize)> {
24    if data.is_empty() {
25        return None;
26    }
27    let tag = data[0];
28    if data.len() < 2 {
29        return None;
30    }
31
32    let (content_start, content_len) = if data[1] & 0x80 == 0 {
33        // Short form
34        (2, data[1] as usize)
35    } else {
36        let num_len_bytes = (data[1] & 0x7F) as usize;
37        if num_len_bytes == 0 || num_len_bytes > 4 || data.len() < 2 + num_len_bytes {
38            return None;
39        }
40        let mut len: usize = 0;
41        for i in 0..num_len_bytes {
42            len = (len << 8) | (data[2 + i] as usize);
43        }
44        (2 + num_len_bytes, len)
45    };
46
47    if content_start + content_len > data.len() {
48        return None;
49    }
50
51    Some((tag, content_start, content_len))
52}
53
54/// Extract a Common Name (OID 2.5.4.3) from an X.501 Name sequence
55fn extract_cn(name_data: &[u8]) -> Vec<u8> {
56    // OID for commonName: 2.5.4.3 = 55 04 03
57    let cn_oid: [u8; 3] = [0x55, 0x04, 0x03];
58
59    let mut pos = 0;
60    while pos < name_data.len() {
61        if let Some((_tag, start, len)) = asn1_parse_tlv(&name_data[pos..]) {
62            let inner = &name_data[pos + start..pos + start + len];
63            // Search for CN OID within this SET
64            if let Some(idx) = find_subsequence(inner, &cn_oid) {
65                // The value follows the OID TLV
66                let after_oid = idx + cn_oid.len();
67                if after_oid < inner.len() {
68                    if let Some((_vtag, vstart, vlen)) = asn1_parse_tlv(&inner[after_oid..]) {
69                        let value = &inner[after_oid + vstart..after_oid + vstart + vlen];
70                        return value.to_vec();
71                    }
72                }
73            }
74            pos += start + len;
75        } else {
76            break;
77        }
78    }
79
80    Vec::new()
81}
82
83/// Find a subsequence within a byte slice
84fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
85    if needle.is_empty() || needle.len() > haystack.len() {
86        return None;
87    }
88    for i in 0..=haystack.len() - needle.len() {
89        if haystack[i..i + needle.len()] == *needle {
90            return Some(i + needle.len());
91        }
92    }
93    None
94}
95
96// ============================================================================
97// X.509 Certificate
98// ============================================================================
99
100/// Simplified X.509 certificate representation
101#[derive(Debug, Clone)]
102pub struct X509Certificate {
103    /// Raw DER-encoded certificate bytes
104    pub raw: Vec<u8>,
105    /// Subject common name (extracted from DER)
106    pub subject_cn: Vec<u8>,
107    /// Issuer common name (extracted from DER)
108    pub issuer_cn: Vec<u8>,
109    /// Subject public key bytes (raw)
110    pub public_key: Vec<u8>,
111    /// Not-before timestamp (Unix epoch seconds, 0 if unparsed)
112    pub not_before: u64,
113    /// Not-after timestamp (Unix epoch seconds, 0 if unparsed)
114    pub not_after: u64,
115    /// Is this a CA certificate?
116    pub is_ca: bool,
117}
118
119impl X509Certificate {
120    /// Parse a simplified X.509 certificate from DER-encoded bytes.
121    ///
122    /// This is a best-effort parser that extracts subject/issuer CN and
123    /// the public key. Full ASN.1 validation is beyond scope.
124    pub fn from_der(data: &[u8]) -> Option<Self> {
125        // Outer SEQUENCE
126        let (tag, start, len) = asn1_parse_tlv(data)?;
127        if tag != ASN1_SEQUENCE {
128            return None;
129        }
130        let cert_content = &data[start..start + len];
131
132        // TBSCertificate (first SEQUENCE inside)
133        let (tbs_tag, tbs_start, tbs_len) = asn1_parse_tlv(cert_content)?;
134        if tbs_tag != ASN1_SEQUENCE {
135            return None;
136        }
137        let tbs = &cert_content[tbs_start..tbs_start + tbs_len];
138
139        // Skip version (context [0]) + serial number + signature algorithm
140        // Then find issuer and subject sequences
141        // This is simplified: we scan for CN OIDs in the TBS data
142
143        let issuer_cn = extract_cn(tbs);
144
145        // Subject is typically after issuer -- scan from a later offset
146        let subject_cn = if tbs.len() > 100 {
147            let second_half = &tbs[tbs.len() / 3..];
148            let cn = extract_cn(second_half);
149            if cn.is_empty() {
150                issuer_cn.clone()
151            } else {
152                cn
153            }
154        } else {
155            issuer_cn.clone()
156        };
157
158        // Extract public key (look for BIT STRING after SubjectPublicKeyInfo SEQUENCE)
159        let mut public_key = Vec::new();
160        let mut scan_pos = 0;
161        while scan_pos < tbs.len() {
162            if tbs[scan_pos] == ASN1_BIT_STRING {
163                if let Some((_, bs_start, bs_len)) = asn1_parse_tlv(&tbs[scan_pos..]) {
164                    if bs_len > 1 {
165                        // Skip the "unused bits" byte
166                        public_key =
167                            tbs[scan_pos + bs_start + 1..scan_pos + bs_start + bs_len].to_vec();
168                    }
169                    break;
170                }
171            }
172            scan_pos += 1;
173        }
174
175        Some(Self {
176            raw: data.to_vec(),
177            subject_cn,
178            issuer_cn,
179            public_key,
180            not_before: 0,
181            not_after: 0,
182            is_ca: false,
183        })
184    }
185}
186
187// ============================================================================
188// Trust Store
189// ============================================================================
190
191/// Trust anchor store for root CAs
192pub struct TrustStore {
193    /// Trusted root CA certificates (subject CN -> certificate)
194    anchors: Vec<X509Certificate>,
195}
196
197impl Default for TrustStore {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203impl TrustStore {
204    /// Create an empty trust store
205    pub fn new() -> Self {
206        Self {
207            anchors: Vec::new(),
208        }
209    }
210
211    /// Add a trusted root CA certificate
212    pub fn add_anchor(&mut self, cert: X509Certificate) {
213        self.anchors.push(cert);
214    }
215
216    /// Validate a certificate chain against the trust store.
217    ///
218    /// Returns true if the chain can be verified back to a trusted anchor
219    /// via basic issuer/subject matching.
220    pub fn validate_chain(&self, chain: &[X509Certificate]) -> bool {
221        if chain.is_empty() {
222            return false;
223        }
224
225        // Walk the chain: each cert's issuer should match the next cert's subject
226        for i in 0..chain.len().saturating_sub(1) {
227            if chain[i].issuer_cn != chain[i + 1].subject_cn {
228                return false;
229            }
230        }
231
232        // The last cert in the chain should be issued by a trusted anchor
233        let root_issuer = &chain[chain.len() - 1].issuer_cn;
234        self.anchors
235            .iter()
236            .any(|anchor| &anchor.subject_cn == root_issuer)
237    }
238
239    /// Number of trust anchors
240    pub fn anchor_count(&self) -> usize {
241        self.anchors.len()
242    }
243}