1use 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#[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#[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 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#[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 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 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 pub crl_distribution_points: Vec<String>,
123 pub ocsp_urls: Vec<String>,
125 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 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 #[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 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 pub fn verify_signed_by(&self, issuer_key: &PublicKey) -> bool {
299 self.signature_algorithm.verify(issuer_key, &self.tbs, &self.signature)
300 }
301
302 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 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")]
328fn 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 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")]
348fn 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
364pub 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#[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 Revoked { subject: String, reason: Option<u8> },
398 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
429impl 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#[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
450pub 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 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 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 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 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 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
579pub 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
596pub 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 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#[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 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 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 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 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 #![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"))] 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); 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"))] 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 assert!(matches!(
804 verify_chain(&chain[..1], Some("leaf.camel.test"), &roots, now, OID_EKU_SERVER_AUTH),
805 Err(CertError::UnknownIssuer { .. })
806 ));
807 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 assert!(matches!(verify_chain(&chain, None, &roots, now, OID_EKU_CLIENT_AUTH), Err(CertError::UsageNotAllowed { .. })));
815 assert!(verify_chain(&chain, None, &RootStore::empty(), now, OID_EKU_SERVER_AUTH).is_err());
817 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 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 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}