Skip to main content

simoom_tls/
x509.rs

1//! X.509 v3 certificate parsing (RFC 5280 subset), trust store and chain
2//! validation with hostname matching (RFC 6125).
3
4use crate::asn1::{self, Der, DerError, DerResult, TAG_BOOLEAN, TAG_INTEGER, TAG_SEQUENCE};
5use crate::keys::{PublicKey, SignatureAlgorithm};
6use crate::pem;
7use std::fmt;
8use std::net::IpAddr;
9use std::sync::{Arc, OnceLock};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12pub const KU_DIGITAL_SIGNATURE: u16 = 1 << 0;
13pub const KU_KEY_CERT_SIGN: u16 = 1 << 5;
14
15const OID_SAN: &str = "2.5.29.17";
16const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19";
17const OID_KEY_USAGE: &str = "2.5.29.15";
18const OID_EXT_KEY_USAGE: &str = "2.5.29.37";
19#[cfg(feature = "revocation")]
20const OID_CRL_DISTRIBUTION_POINTS: &str = "2.5.29.31";
21#[cfg(feature = "revocation")]
22const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1";
23#[cfg(feature = "revocation")]
24const OID_AD_OCSP: &str = "1.3.6.1.5.5.7.48.1";
25#[cfg(feature = "revocation")]
26const OID_AD_CA_ISSUERS: &str = "1.3.6.1.5.5.7.48.2";
27/// At most this many URIs are kept per extension (CRL distribution points, OCSP, caIssuers).
28#[cfg(feature = "revocation")]
29const MAX_URIS_PER_EXTENSION: usize = 16;
30pub const OID_EKU_SERVER_AUTH: &str = "1.3.6.1.5.5.7.3.1";
31pub const OID_EKU_CLIENT_AUTH: &str = "1.3.6.1.5.5.7.3.2";
32const OID_EKU_ANY: &str = "2.5.29.37.0";
33const OID_CN: &str = "2.5.4.3";
34
35/// A distinguished name as an ordered list of (attribute OID, value).
36#[derive(Clone, Debug, Default, PartialEq, Eq)]
37pub struct Name {
38    pub attributes: Vec<(String, String)>,
39}
40
41impl Name {
42    pub fn common_name(&self) -> Option<&str> {
43        self.attributes.iter().find(|(o, _)| o == OID_CN).map(|(_, v)| v.as_str())
44    }
45
46    /// The name with the most specific attribute first (`CN=x, O=y, C=z`), as Java's
47    /// `X500Principal.toString()` renders it.
48    pub fn to_dn_string(&self) -> String {
49        let reversed = Name { attributes: self.attributes.iter().rev().cloned().collect() };
50        reversed.to_string()
51    }
52}
53
54impl fmt::Display for Name {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        let parts: Vec<String> = self
57            .attributes
58            .iter()
59            .map(|(o, v)| {
60                let k = match o.as_str() {
61                    "2.5.4.3" => "CN",
62                    "2.5.4.6" => "C",
63                    "2.5.4.7" => "L",
64                    "2.5.4.8" => "ST",
65                    "2.5.4.10" => "O",
66                    "2.5.4.11" => "OU",
67                    other => other,
68                };
69                format!("{k}={v}")
70            })
71            .collect();
72        write!(f, "{}", parts.join(", "))
73    }
74}
75
76fn parse_name(tlv: &asn1::Tlv) -> DerResult<Name> {
77    let mut name = Name::default();
78    let mut r = tlv.reader();
79    while !r.is_empty() {
80        let set = r.expect(asn1::TAG_SET)?;
81        let mut sr = set.reader();
82        while !sr.is_empty() {
83            let mut atv = sr.sequence()?;
84            let oid = atv.oid()?;
85            let v = atv.read()?;
86            let s = asn1::decode_string(&v).unwrap_or_else(|| crate::util::hex(v.value));
87            name.attributes.push((oid, s));
88        }
89    }
90    Ok(name)
91}
92
93/// A parsed X.509 certificate.
94#[derive(Clone, Debug)]
95pub struct Certificate {
96    pub der: Vec<u8>,
97    pub tbs: Vec<u8>,
98    pub version: u8,
99    pub serial: Vec<u8>,
100    pub issuer: Name,
101    pub subject: Name,
102    pub issuer_raw: Vec<u8>,
103    pub subject_raw: Vec<u8>,
104    pub not_before: i64,
105    pub not_after: i64,
106    pub spki: Vec<u8>,
107    /// None when the key algorithm is unsupported (the certificate can still be
108    /// parsed, e.g. as an unused root).
109    pub public_key: Option<PublicKey>,
110    pub signature_algorithm: SignatureAlgorithm,
111    pub signature: Vec<u8>,
112    pub dns_names: Vec<String>,
113    pub ip_addresses: Vec<IpAddr>,
114    pub has_san: bool,
115    /// Some(is_ca) when basicConstraints is present.
116    pub basic_constraints_ca: Option<bool>,
117    pub path_len: Option<u64>,
118    pub key_usage: Option<u16>,
119    pub ext_key_usage: Option<Vec<String>>,
120    pub unknown_critical_extensions: Vec<String>,
121    /// URIs from the CRL distribution points extension (fullName entries only).
122    pub crl_distribution_points: Vec<String>,
123    /// OCSP responder URIs from the authority information access extension.
124    pub ocsp_urls: Vec<String>,
125    /// caIssuers URIs from the authority information access extension.
126    pub ca_issuers_urls: Vec<String>,
127}
128
129impl Certificate {
130    pub fn from_der(der: &[u8]) -> DerResult<Certificate> {
131        let mut outer = Der::new(der);
132        let cert = outer.expect(TAG_SEQUENCE)?;
133        if !outer.is_empty() {
134            return Err(DerError("trailing data after certificate".into()));
135        }
136        let mut c = cert.reader();
137        let tbs_tlv = c.expect(TAG_SEQUENCE)?;
138        let mut sig_alg_r = c.sequence()?;
139        let signature_algorithm = SignatureAlgorithm::parse(&mut sig_alg_r)?;
140        let signature = c.bit_string()?.to_vec();
141
142        let mut t = tbs_tlv.reader();
143        let mut version = 1u8;
144        if let Some(v) = t.optional(0xa0)? {
145            version = v.reader().small_uint()? as u8 + 1;
146        }
147        let serial = t.expect(TAG_INTEGER)?.value.to_vec();
148        let mut inner_alg = t.sequence()?;
149        let inner_sig = SignatureAlgorithm::parse(&mut inner_alg)?;
150        if inner_sig != signature_algorithm {
151            return Err(DerError("signature algorithm mismatch".into()));
152        }
153        let issuer_tlv = t.expect(TAG_SEQUENCE)?;
154        let mut validity = t.sequence()?;
155        let nb = validity.read()?;
156        let na = validity.read()?;
157        let not_before = asn1::parse_time(&nb)?;
158        let not_after = asn1::parse_time(&na)?;
159        let subject_tlv = t.expect(TAG_SEQUENCE)?;
160        let spki_tlv = t.expect(TAG_SEQUENCE)?;
161        let public_key = PublicKey::from_spki(spki_tlv.raw).ok();
162
163        let mut cert = Certificate {
164            der: der.to_vec(),
165            tbs: tbs_tlv.raw.to_vec(),
166            version,
167            serial,
168            issuer: parse_name(&issuer_tlv)?,
169            subject: parse_name(&subject_tlv)?,
170            issuer_raw: issuer_tlv.raw.to_vec(),
171            subject_raw: subject_tlv.raw.to_vec(),
172            not_before,
173            not_after,
174            spki: spki_tlv.raw.to_vec(),
175            public_key,
176            signature_algorithm,
177            signature,
178            dns_names: Vec::new(),
179            ip_addresses: Vec::new(),
180            has_san: false,
181            basic_constraints_ca: None,
182            path_len: None,
183            key_usage: None,
184            ext_key_usage: None,
185            unknown_critical_extensions: Vec::new(),
186            crl_distribution_points: Vec::new(),
187            ocsp_urls: Vec::new(),
188            ca_issuers_urls: Vec::new(),
189        };
190        // optional issuerUniqueID [1], subjectUniqueID [2]
191        let _ = t.optional(0x81)?;
192        let _ = t.optional(0x82)?;
193        if let Some(exts) = t.optional(0xa3)? {
194            let mut list = exts.reader().sequence()?;
195            while !list.is_empty() {
196                let mut e = list.sequence()?;
197                let oid = e.oid()?;
198                let critical = match e.optional(TAG_BOOLEAN)? {
199                    Some(b) => b.value.first().copied().unwrap_or(0) != 0,
200                    None => false,
201                };
202                let value = e.octet_string()?;
203                cert.parse_extension(&oid, critical, value)?;
204            }
205        }
206        Ok(cert)
207    }
208
209    fn parse_extension(&mut self, oid: &str, critical: bool, value: &[u8]) -> DerResult<()> {
210        let mut r = Der::new(value);
211        match oid {
212            OID_SAN => {
213                self.has_san = true;
214                let mut names = r.sequence()?;
215                while !names.is_empty() {
216                    let gn = names.read()?;
217                    match gn.tag {
218                        0x82 => self.dns_names.push(String::from_utf8_lossy(gn.value).to_ascii_lowercase()),
219                        0x87 => match gn.value.len() {
220                            4 => self.ip_addresses.push(IpAddr::from(<[u8; 4]>::try_from(gn.value).unwrap())),
221                            16 => self.ip_addresses.push(IpAddr::from(<[u8; 16]>::try_from(gn.value).unwrap())),
222                            _ => {}
223                        },
224                        _ => {}
225                    }
226                }
227            }
228            OID_BASIC_CONSTRAINTS => {
229                let mut bc = r.sequence()?;
230                let mut ca = false;
231                if let Some(b) = bc.optional(TAG_BOOLEAN)? {
232                    ca = b.value.first().copied().unwrap_or(0) != 0;
233                }
234                if bc.peek_tag() == Some(TAG_INTEGER) {
235                    self.path_len = Some(bc.small_uint()?);
236                }
237                self.basic_constraints_ca = Some(ca);
238            }
239            OID_KEY_USAGE => {
240                let bs = r.expect(asn1::TAG_BIT_STRING)?.value;
241                let mut ku = 0u16;
242                for (i, byte) in bs.iter().skip(1).take(2).enumerate() {
243                    for bit in 0..8 {
244                        if byte & (0x80 >> bit) != 0 {
245                            ku |= 1 << (8 * i + bit);
246                        }
247                    }
248                }
249                self.key_usage = Some(ku);
250            }
251            OID_EXT_KEY_USAGE => {
252                let mut s = r.sequence()?;
253                let mut v = Vec::new();
254                while !s.is_empty() {
255                    v.push(s.oid()?);
256                }
257                self.ext_key_usage = Some(v);
258            }
259            // Parsed leniently: a malformed non-critical locator never rejects a certificate.
260            // Only revocation checking reads them, so other builds leave them unparsed.
261            #[cfg(feature = "revocation")]
262            OID_CRL_DISTRIBUTION_POINTS => {
263                if parse_crl_distribution_points(value, &mut self.crl_distribution_points).is_err() && critical {
264                    return Err(DerError("malformed CRL distribution points".into()));
265                }
266            }
267            #[cfg(feature = "revocation")]
268            OID_AUTHORITY_INFO_ACCESS => {
269                if parse_authority_info_access(value, &mut self.ocsp_urls, &mut self.ca_issuers_urls).is_err() && critical {
270                    return Err(DerError("malformed authority information access".into()));
271                }
272            }
273            _ => {
274                if critical {
275                    self.unknown_critical_extensions.push(oid.to_string());
276                }
277            }
278        }
279        Ok(())
280    }
281
282    /// Parses every CERTIFICATE block in PEM text.
283    pub fn from_pem_all(data: &[u8]) -> Result<Vec<Certificate>, String> {
284        let mut out = Vec::new();
285        for b in pem::parse_pem(data)? {
286            if b.label == "CERTIFICATE" || b.label == "TRUSTED CERTIFICATE" {
287                out.push(Certificate::from_der(&b.der).map_err(|e| e.to_string())?);
288            }
289        }
290        Ok(out)
291    }
292
293    pub fn is_self_issued(&self) -> bool {
294        self.issuer_raw == self.subject_raw
295    }
296
297    /// Verifies this certificate's signature with `issuer_key`.
298    pub fn verify_signed_by(&self, issuer_key: &PublicKey) -> bool {
299        self.signature_algorithm.verify(issuer_key, &self.tbs, &self.signature)
300    }
301
302    /// RFC 6125 style hostname / IP check against SAN (CN only when no SAN extension exists).
303    pub fn matches_hostname(&self, host: &str) -> bool {
304        let host = host.trim_end_matches('.').to_ascii_lowercase();
305        let host_ip = host.trim_start_matches('[').trim_end_matches(']').parse::<IpAddr>().ok();
306        if let Some(ip) = host_ip {
307            return self.ip_addresses.contains(&ip);
308        }
309        if self.has_san {
310            return self.dns_names.iter().any(|p| dns_name_matches(p, &host));
311        }
312        match self.subject.common_name() {
313            Some(cn) => dns_name_matches(&cn.to_ascii_lowercase(), &host),
314            None => false,
315        }
316    }
317}
318
319#[cfg(feature = "revocation")]
320fn push_uri(out: &mut Vec<String>, gn: &asn1::Tlv) {
321    // uniformResourceIdentifier [6] IA5String
322    if gn.tag == 0x86 && out.len() < MAX_URIS_PER_EXTENSION && gn.value.is_ascii() {
323        out.push(String::from_utf8_lossy(gn.value).into_owned());
324    }
325}
326
327#[cfg(feature = "revocation")]
328/// CRLDistributionPoints ::= SEQUENCE OF DistributionPoint (RFC 5280 section 4.2.1.13).
329fn parse_crl_distribution_points(value: &[u8], out: &mut Vec<String>) -> DerResult<()> {
330    let mut dps = Der::new(value).sequence()?;
331    while !dps.is_empty() {
332        let mut dp = dps.sequence()?;
333        if let Some(name) = dp.optional(0xa0)? {
334            let mut n = name.reader();
335            // fullName [0] GeneralNames; nameRelativeToCRLIssuer [1] is skipped
336            if let Some(full) = n.optional(0xa0)? {
337                let mut gns = full.reader();
338                while !gns.is_empty() {
339                    push_uri(out, &gns.read()?);
340                }
341            }
342        }
343    }
344    Ok(())
345}
346
347#[cfg(feature = "revocation")]
348/// AuthorityInfoAccessSyntax ::= SEQUENCE OF AccessDescription (RFC 5280 section 4.2.2.1).
349fn parse_authority_info_access(value: &[u8], ocsp: &mut Vec<String>, ca_issuers: &mut Vec<String>) -> DerResult<()> {
350    let mut list = Der::new(value).sequence()?;
351    while !list.is_empty() {
352        let mut ad = list.sequence()?;
353        let method = ad.oid()?;
354        let location = ad.read()?;
355        match method.as_str() {
356            OID_AD_OCSP => push_uri(ocsp, &location),
357            OID_AD_CA_ISSUERS => push_uri(ca_issuers, &location),
358            _ => {}
359        }
360    }
361    Ok(())
362}
363
364/// Matches a (lowercase) certificate DNS pattern against a (lowercase) host name.
365/// A wildcard is only allowed as the complete left-most label and matches exactly
366/// one non-empty label; at least two labels must follow it.
367pub fn dns_name_matches(pattern: &str, host: &str) -> bool {
368    let pattern = pattern.trim_end_matches('.');
369    if let Some(suffix) = pattern.strip_prefix("*.") {
370        if suffix.split('.').filter(|l| !l.is_empty()).count() < 2 || suffix.contains('*') {
371            return false;
372        }
373        match host.split_once('.') {
374            Some((first, rest)) => !first.is_empty() && rest == suffix,
375            None => false,
376        }
377    } else {
378        !pattern.contains('*') && pattern == host
379    }
380}
381
382/// Certificate validation failures.
383#[derive(Clone, PartialEq, Eq)]
384pub enum CertError {
385    NoCertificates,
386    Parse(String),
387    Expired { subject: String },
388    NotYetValid { subject: String },
389    HostnameMismatch { host: String, names: Vec<String> },
390    UnknownIssuer { subject: String, issuer: String },
391    BadSignature { subject: String },
392    NotCa { subject: String },
393    UsageNotAllowed { subject: String },
394    PathTooLong,
395    UnsupportedCriticalExtension { subject: String, oid: String },
396    /// The certificate is revoked (CRL or OCSP); `reason` is the CRLReason code when given.
397    Revoked { subject: String, reason: Option<u8> },
398    /// Revocation checking was required but no valid status could be obtained.
399    RevocationUnknown { subject: String, why: String },
400}
401
402impl fmt::Display for CertError {
403    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404        match self {
405            CertError::NoCertificates => write!(f, "peer sent no certificates"),
406            CertError::Parse(e) => write!(f, "certificate parse error: {e}"),
407            CertError::Expired { subject } => write!(f, "certificate expired: {subject}"),
408            CertError::NotYetValid { subject } => write!(f, "certificate not yet valid: {subject}"),
409            CertError::HostnameMismatch { host, names } => {
410                write!(f, "certificate is not valid for '{host}' (valid for: {})", names.join(", "))
411            }
412            CertError::UnknownIssuer { subject, issuer } => {
413                write!(f, "unable to find trusted issuer '{issuer}' for '{subject}'")
414            }
415            CertError::BadSignature { subject } => write!(f, "invalid certificate signature on '{subject}'"),
416            CertError::NotCa { subject } => write!(f, "issuer certificate '{subject}' is not a CA"),
417            CertError::UsageNotAllowed { subject } => write!(f, "key usage not permitted for '{subject}'"),
418            CertError::PathTooLong => write!(f, "certificate path too long"),
419            CertError::UnsupportedCriticalExtension { subject, oid } => {
420                write!(f, "unsupported critical extension {oid} in '{subject}'")
421            }
422            CertError::Revoked { subject, reason: Some(r) } => write!(f, "certificate revoked (reason {r}): {subject}"),
423            CertError::Revoked { subject, reason: None } => write!(f, "certificate revoked: {subject}"),
424            CertError::RevocationUnknown { subject, why } => write!(f, "revocation status unknown for '{subject}': {why}"),
425        }
426    }
427}
428
429/// Debug output is the Display text (smaller than a derived implementation).
430impl fmt::Debug for CertError {
431    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432        fmt::Display::fmt(self, f)
433    }
434}
435
436impl std::error::Error for CertError {}
437
438/// A set of trust anchors.
439#[derive(Clone, Default)]
440pub struct RootStore {
441    roots: Vec<Arc<Certificate>>,
442}
443
444impl fmt::Debug for RootStore {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        write!(f, "RootStore({} roots)", self.roots.len())
447    }
448}
449
450/// Candidate locations of the system CA bundle.
451pub const SYSTEM_BUNDLE_PATHS: &[&str] = &[
452    "/etc/ssl/certs/ca-certificates.crt",
453    "/etc/pki/tls/certs/ca-bundle.crt",
454    "/etc/ssl/ca-bundle.pem",
455    "/etc/ssl/cert.pem",
456    "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
457];
458
459impl RootStore {
460    pub fn empty() -> RootStore {
461        RootStore::default()
462    }
463
464    /// The system trust store (`SSL_CERT_FILE`, else the first existing bundle in
465    /// [`SYSTEM_BUNDLE_PATHS`], else the Windows `ROOT` certificate store; macOS is covered by
466    /// `/etc/ssl/cert.pem`); loaded once per process. Certificates that fail to parse are
467    /// skipped.
468    pub fn system() -> RootStore {
469        static SYSTEM: OnceLock<RootStore> = OnceLock::new();
470        SYSTEM
471            .get_or_init(|| {
472                let mut store = RootStore::empty();
473                let mut paths: Vec<String> = Vec::new();
474                if let Ok(p) = std::env::var("SSL_CERT_FILE") {
475                    paths.push(p);
476                }
477                paths.extend(SYSTEM_BUNDLE_PATHS.iter().map(|s| s.to_string()));
478                for p in paths {
479                    if let Ok(data) = std::fs::read(&p) {
480                        store.add_pem_lenient(&data);
481                        if !store.is_empty() {
482                            break;
483                        }
484                    }
485                }
486                // Windows keeps its roots in the system certificate store, not in a file.
487                if store.is_empty() {
488                    for der in simoom_sys::certstore::system_roots() {
489                        if let Ok(c) = Certificate::from_der(&der) {
490                            store.roots.push(Arc::new(c));
491                        }
492                    }
493                }
494                store
495            })
496            .clone()
497    }
498
499    fn add_pem_lenient(&mut self, data: &[u8]) -> usize {
500        let mut n = 0;
501        if let Ok(blocks) = pem::parse_pem(data) {
502            for b in blocks.into_iter().filter(|b| b.label == "CERTIFICATE") {
503                if let Ok(c) = Certificate::from_der(&b.der) {
504                    self.roots.push(Arc::new(c));
505                    n += 1;
506                }
507            }
508        }
509        n
510    }
511
512    /// Adds every certificate in PEM text; fails if any block is malformed or none found.
513    pub fn add_pem(&mut self, data: &[u8]) -> Result<usize, String> {
514        let certs = Certificate::from_pem_all(data)?;
515        if certs.is_empty() {
516            return Err("no CERTIFICATE blocks found".into());
517        }
518        let n = certs.len();
519        self.roots.extend(certs.into_iter().map(Arc::new));
520        Ok(n)
521    }
522
523    pub fn add_pem_file(&mut self, path: impl AsRef<std::path::Path>) -> Result<usize, String> {
524        let data = std::fs::read(path.as_ref()).map_err(|e| format!("{}: {e}", path.as_ref().display()))?;
525        self.add_pem(&data)
526    }
527
528    pub fn add_der(&mut self, der: &[u8]) -> Result<(), String> {
529        self.roots.push(Arc::new(Certificate::from_der(der).map_err(|e| e.to_string())?));
530        Ok(())
531    }
532
533    pub fn len(&self) -> usize {
534        self.roots.len()
535    }
536
537    pub fn is_empty(&self) -> bool {
538        self.roots.is_empty()
539    }
540
541    pub fn extend(&mut self, other: &RootStore) {
542        self.roots.extend(other.roots.iter().cloned());
543    }
544
545    /// The trust anchors.
546    pub fn certificates(&self) -> impl Iterator<Item = &Certificate> {
547        self.roots.iter().map(|c| c.as_ref())
548    }
549}
550
551pub fn now_unix() -> i64 {
552    SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs() as i64).unwrap_or(0)
553}
554
555const MAX_PATH_DEPTH: usize = 8;
556
557fn check_time(c: &Certificate, now: i64) -> Result<(), CertError> {
558    if now < c.not_before {
559        return Err(CertError::NotYetValid { subject: c.subject.to_string() });
560    }
561    if now > c.not_after {
562        return Err(CertError::Expired { subject: c.subject.to_string() });
563    }
564    Ok(())
565}
566
567fn check_critical(c: &Certificate) -> Result<(), CertError> {
568    // name constraints / policy extensions are tolerated but not enforced (documented limitation);
569    // anything else unknown and critical is rejected.
570    const TOLERATED: &[&str] = &["2.5.29.30", "2.5.29.32", "2.5.29.36", "2.5.29.54"];
571    for oid in &c.unknown_critical_extensions {
572        if !TOLERATED.contains(&oid.as_str()) {
573            return Err(CertError::UnsupportedCriticalExtension { subject: c.subject.to_string(), oid: oid.clone() });
574        }
575    }
576    Ok(())
577}
578
579/// Validates a peer certificate chain (leaf first, DER) against `roots` at time `now` (Unix
580/// seconds).
581///
582/// Checks: signatures along the path to a trust anchor, validity periods of the leaf and
583/// intermediates, CA flag / keyCertSign / pathLenConstraint on issuers, leaf keyUsage
584/// (digitalSignature) and extendedKeyUsage (`required_eku`), and the hostname (when given).
585pub fn verify_chain(
586    chain: &[Vec<u8>],
587    hostname: Option<&str>,
588    roots: &RootStore,
589    now: i64,
590    required_eku: &str,
591) -> Result<Certificate, CertError> {
592    let (mut certs, _, _) = verify_core(chain, hostname, roots, now, required_eku)?;
593    Ok(certs.swap_remove(0))
594}
595
596/// Like [`verify_chain`], but returns the validated path: the leaf first, then each issuer,
597/// ending with the trust anchor. A pinned leaf that is itself a trust anchor gives a path of
598/// one certificate.
599pub fn verify_chain_path(
600    chain: &[Vec<u8>],
601    hostname: Option<&str>,
602    roots: &RootStore,
603    now: i64,
604    required_eku: &str,
605) -> Result<Vec<Certificate>, CertError> {
606    let (mut certs, path, anchor) = verify_core(chain, hostname, roots, now, required_eku)?;
607    let Some(anchor) = anchor else {
608        // a pinned leaf
609        return Ok(vec![certs.swap_remove(0)]);
610    };
611    let mut slots: Vec<Option<Certificate>> = certs.into_iter().map(Some).collect();
612    let mut out: Vec<Certificate> = path.iter().filter_map(|&i| slots[i].take()).collect();
613    out.push(Certificate::clone(&anchor));
614    Ok(out)
615}
616
617/// Validates the chain: (the parsed certificates, the indices of the path through them, the
618/// trust anchor; None when the leaf is itself a trust anchor).
619#[allow(clippy::type_complexity)]
620fn verify_core(
621    chain: &[Vec<u8>],
622    hostname: Option<&str>,
623    roots: &RootStore,
624    now: i64,
625    required_eku: &str,
626) -> Result<(Vec<Certificate>, Vec<usize>, Option<Arc<Certificate>>), CertError> {
627    if chain.is_empty() {
628        return Err(CertError::NoCertificates);
629    }
630    if chain.len() > MAX_PATH_DEPTH + 2 {
631        return Err(CertError::PathTooLong);
632    }
633    let certs: Vec<Certificate> = chain
634        .iter()
635        .map(|d| Certificate::from_der(d).map_err(|e| CertError::Parse(e.to_string())))
636        .collect::<Result<_, _>>()?;
637    let leaf = &certs[0];
638    check_time(leaf, now)?;
639    check_critical(leaf)?;
640    if let Some(ku) = leaf.key_usage {
641        if ku & KU_DIGITAL_SIGNATURE == 0 {
642            return Err(CertError::UsageNotAllowed { subject: leaf.subject.to_string() });
643        }
644    }
645    if let Some(eku) = &leaf.ext_key_usage {
646        if !eku.iter().any(|o| o == required_eku || o == OID_EKU_ANY) {
647            return Err(CertError::UsageNotAllowed { subject: leaf.subject.to_string() });
648        }
649    }
650    if let Some(host) = hostname {
651        if !leaf.matches_hostname(host) {
652            let mut names = leaf.dns_names.clone();
653            names.extend(leaf.ip_addresses.iter().map(|i| i.to_string()));
654            if names.is_empty() {
655                names.extend(leaf.subject.common_name().map(str::to_string));
656            }
657            return Err(CertError::HostnameMismatch { host: host.to_string(), names });
658        }
659    }
660    // a leaf that is itself a trust anchor (pinned self-signed certificate)
661    if roots.roots.iter().any(|r| r.der == leaf.der) {
662        return Ok((certs, vec![0], None));
663    }
664    let mut used = vec![false; certs.len()];
665    used[0] = true;
666    let mut path = vec![0usize];
667    let anchor = build_path(&certs, 0, roots, now, &mut used, 0, &mut path)?;
668    Ok((certs, path, Some(anchor)))
669}
670
671fn build_path(
672    certs: &[Certificate],
673    idx: usize,
674    roots: &RootStore,
675    now: i64,
676    used: &mut Vec<bool>,
677    depth: usize,
678    path: &mut Vec<usize>,
679) -> Result<Arc<Certificate>, CertError> {
680    if depth > MAX_PATH_DEPTH {
681        return Err(CertError::PathTooLong);
682    }
683    let cert = &certs[idx];
684    let mut last_err = CertError::UnknownIssuer { subject: cert.subject.to_string(), issuer: cert.issuer.to_string() };
685    // trust anchors first
686    for root in roots.roots.iter().filter(|r| r.subject_raw == cert.issuer_raw) {
687        if let Some(pk) = &root.public_key {
688            if cert.verify_signed_by(pk) {
689                return Ok(root.clone());
690            }
691            last_err = CertError::BadSignature { subject: cert.subject.to_string() };
692        }
693    }
694    // then intermediates supplied by the peer
695    for j in 0..certs.len() {
696        if used[j] || certs[j].subject_raw != cert.issuer_raw {
697            continue;
698        }
699        let issuer = &certs[j];
700        if issuer.basic_constraints_ca != Some(true) {
701            last_err = CertError::NotCa { subject: issuer.subject.to_string() };
702            continue;
703        }
704        if let Some(ku) = issuer.key_usage {
705            if ku & KU_KEY_CERT_SIGN == 0 {
706                last_err = CertError::UsageNotAllowed { subject: issuer.subject.to_string() };
707                continue;
708            }
709        }
710        if let Some(pl) = issuer.path_len {
711            // number of intermediate CA certificates below this issuer (depth counts them)
712            if (depth as u64) > pl {
713                last_err = CertError::PathTooLong;
714                continue;
715            }
716        }
717        if let Err(e) = check_time(issuer, now).and_then(|_| check_critical(issuer)) {
718            last_err = e;
719            continue;
720        }
721        let pk = match &issuer.public_key {
722            Some(pk) => pk,
723            None => continue,
724        };
725        if !cert.verify_signed_by(pk) {
726            last_err = CertError::BadSignature { subject: cert.subject.to_string() };
727            continue;
728        }
729        used[j] = true;
730        path.push(j);
731        match build_path(certs, j, roots, now, used, depth + 1, path) {
732            Ok(anchor) => return Ok(anchor),
733            Err(e) => last_err = e,
734        }
735        path.pop();
736        used[j] = false;
737    }
738    Err(last_err)
739}
740
741#[cfg(test)]
742mod tests {
743    // some fixtures are used only by the tests that need every key type
744    #![cfg_attr(not(all(feature = "rsa", feature = "p384", feature = "ed25519")), allow(dead_code))]
745    use super::*;
746
747    fn der_of(pem_text: &str) -> Vec<u8> {
748        pem::parse_pem(pem_text.as_bytes()).unwrap().remove(0).der
749    }
750
751    const CA: &str = include_str!("../testdata/ca.pem");
752    const INT: &str = include_str!("../testdata/int.pem");
753    const LEAF: &str = include_str!("../testdata/leaf.pem");
754    const RSA_SELF: &str = include_str!("../testdata/rsa_selfsigned.pem");
755
756    #[test]
757    #[cfg(all(feature = "rsa", feature = "p384", feature = "ed25519"))] // the test chain uses RSA and P-384
758    fn parse_openssl_chain() {
759        let leaf = Certificate::from_der(&der_of(LEAF)).unwrap();
760        assert_eq!(leaf.version, 3);
761        assert_eq!(leaf.subject.common_name(), Some("leaf.camel.test"));
762        assert_eq!(leaf.issuer.common_name(), Some("Camel Test Intermediate"));
763        assert_eq!(leaf.dns_names, vec!["leaf.camel.test", "*.apps.camel.test"]);
764        assert_eq!(leaf.ip_addresses, vec!["127.0.0.1".parse::<IpAddr>().unwrap()]);
765        assert_eq!(leaf.basic_constraints_ca, Some(false));
766        assert_eq!(leaf.key_usage, Some(KU_DIGITAL_SIGNATURE));
767        assert_eq!(leaf.ext_key_usage, Some(vec![OID_EKU_SERVER_AUTH.to_string()]));
768        assert!(matches!(leaf.signature_algorithm, SignatureAlgorithm::RsaPss { salt_len: 32, .. }));
769        assert!(leaf.not_after > 4_102_444_800); // GeneralizedTime beyond 2100
770        let int = Certificate::from_der(&der_of(INT)).unwrap();
771        assert_eq!(int.basic_constraints_ca, Some(true));
772        assert_eq!(int.path_len, Some(0));
773        assert!(matches!(int.signature_algorithm, SignatureAlgorithm::Ecdsa(_)));
774        let ca = Certificate::from_der(&der_of(CA)).unwrap();
775        assert!(ca.is_self_issued());
776        assert!(ca.verify_signed_by(ca.public_key.as_ref().unwrap()));
777        assert!(int.verify_signed_by(ca.public_key.as_ref().unwrap()));
778        assert!(leaf.verify_signed_by(int.public_key.as_ref().unwrap()));
779        assert!(!leaf.verify_signed_by(ca.public_key.as_ref().unwrap()));
780        let rsa = Certificate::from_der(&der_of(RSA_SELF)).unwrap();
781        assert!(rsa.verify_signed_by(rsa.public_key.as_ref().unwrap()));
782    }
783
784    #[test]
785    #[cfg(all(feature = "rsa", feature = "p384", feature = "ed25519"))] // the test chain uses RSA and P-384
786    fn chain_validation() {
787        let mut roots = RootStore::empty();
788        roots.add_pem(CA.as_bytes()).unwrap();
789        let chain = vec![der_of(LEAF), der_of(INT)];
790        let now = now_unix();
791        assert!(verify_chain(&chain, Some("leaf.camel.test"), &roots, now, OID_EKU_SERVER_AUTH).is_ok());
792        assert!(verify_chain(&chain, Some("x.apps.camel.test"), &roots, now, OID_EKU_SERVER_AUTH).is_ok());
793        assert!(verify_chain(&chain, Some("127.0.0.1"), &roots, now, OID_EKU_SERVER_AUTH).is_ok());
794        assert!(matches!(
795            verify_chain(&chain, Some("a.b.apps.camel.test"), &roots, now, OID_EKU_SERVER_AUTH),
796            Err(CertError::HostnameMismatch { .. })
797        ));
798        assert!(matches!(
799            verify_chain(&chain, Some("other.test"), &roots, now, OID_EKU_SERVER_AUTH),
800            Err(CertError::HostnameMismatch { .. })
801        ));
802        // missing intermediate
803        assert!(matches!(
804            verify_chain(&chain[..1], Some("leaf.camel.test"), &roots, now, OID_EKU_SERVER_AUTH),
805            Err(CertError::UnknownIssuer { .. })
806        ));
807        // expired / not yet valid
808        assert!(matches!(
809            verify_chain(&chain, None, &roots, 48_000_000_000, OID_EKU_SERVER_AUTH),
810            Err(CertError::Expired { .. })
811        ));
812        assert!(matches!(verify_chain(&chain, None, &roots, 1_000_000_000, OID_EKU_SERVER_AUTH), Err(CertError::NotYetValid { .. })));
813        // wrong EKU
814        assert!(matches!(verify_chain(&chain, None, &roots, now, OID_EKU_CLIENT_AUTH), Err(CertError::UsageNotAllowed { .. })));
815        // untrusted
816        assert!(verify_chain(&chain, None, &RootStore::empty(), now, OID_EKU_SERVER_AUTH).is_err());
817        // pinned self-signed leaf
818        let mut pinned = RootStore::empty();
819        pinned.add_pem(RSA_SELF.as_bytes()).unwrap();
820        assert!(verify_chain(&[der_of(RSA_SELF)], Some("rsa.test"), &pinned, now, OID_EKU_SERVER_AUTH).is_ok());
821        assert!(verify_chain(&[der_of(RSA_SELF)], Some("foo.wild.test"), &pinned, now, OID_EKU_SERVER_AUTH).is_ok());
822        // tampered signature
823        let mut bad = der_of(LEAF);
824        let n = bad.len();
825        bad[n - 5] ^= 1;
826        assert!(matches!(
827            verify_chain(&[bad, der_of(INT)], None, &roots, now, OID_EKU_SERVER_AUTH),
828            Err(CertError::BadSignature { .. })
829        ));
830    }
831
832    #[test]
833    fn wildcard_rules() {
834        assert!(dns_name_matches("*.example.com", "www.example.com"));
835        assert!(!dns_name_matches("*.example.com", "example.com"));
836        assert!(!dns_name_matches("*.example.com", "a.b.example.com"));
837        assert!(!dns_name_matches("*.com", "example.com"));
838        assert!(!dns_name_matches("w*.example.com", "www.example.com"));
839        assert!(dns_name_matches("example.com", "example.com"));
840    }
841
842    #[test]
843    fn system_bundle_parses() {
844        let store = RootStore::system();
845        if std::path::Path::new("/etc/ssl/certs/ca-certificates.crt").exists() {
846            assert!(store.len() > 10, "only {} roots parsed", store.len());
847            // every root with a supported key verifies its own self-signature when self-issued
848            // (spot-check a handful; some legacy roots use SHA-1)
849            let mut checked = 0;
850            for r in store.roots.iter().filter(|r| r.is_self_issued()).take(20) {
851                if let Some(pk) = &r.public_key {
852                    if !matches!(r.signature_algorithm, SignatureAlgorithm::Unknown(_)) {
853                        assert!(r.verify_signed_by(pk), "root {} self-signature", r.subject);
854                        checked += 1;
855                    }
856                }
857            }
858            assert!(checked > 5);
859        }
860    }
861}