1use crypto::encoding as crypto_encoding;
2
3use crate::{CryptoProvider, Error, MAX_CERTS, SignatureScheme};
4
5#[cfg(not(target_os = "macos"))]
6const DEFAULT_ROOT_DIRS: &[&str] = &[
7 "/etc/ssl/cert",
8 "/etc/ssl/certs",
9 "/etc/pki/tls/certs",
10 "/usr/local/share/certs",
11 "/usr/share/ca-certificates/mozilla",
12];
13
14#[cfg(target_os = "macos")]
15const DEFAULT_ROOT_DIRS: &[&str] = &["/etc/ssl", "/usr/local/etc/openssl/certs"];
16
17#[async_trait::async_trait]
18pub trait CertificateVerifier {
19 async fn verify_certificate(&self, cert: &ReceivedCertificate, server_name: Option<&str>) -> Result<(), Error>;
20}
21
22pub enum ReceivedCertificate<'a> {
24 X509 {
26 chain: heapless::Vec<ParsedCertificate<'a>, MAX_CERTS>,
27 },
28 RawPublicKey {
30 public_key: &'a [u8],
31 scheme: SignatureScheme,
32 },
33}
34
35pub struct ParsedCertificate<'a> {
38 pub der: &'a [u8],
40 pub spki: &'a [u8],
42 pub public_key: &'a [u8],
44 pub issuer_dn: &'a [u8],
46 pub subject_dn: &'a [u8],
48 pub tbs: &'a [u8],
50 pub signature_value: &'a [u8],
52 pub sig_alg_oid: &'a [u8],
54 pub spki_alg_oid: &'a [u8],
56 pub is_ca: Option<bool>,
58 pub has_server_auth_eku: Option<bool>,
60 pub not_before: u64,
62 pub not_after: u64,
64}
65
66impl<'a> ParsedCertificate<'a> {
67 pub fn from_der(der: &'a [u8]) -> Result<Self, Error> {
69 let spki = x509::extract_spki_from_cert(der).map_err(|_| Error::CertificateParseFailed)?;
70 let public_key = x509::extract_key_from_spki(spki).map_err(|_| Error::CertificateParseFailed)?;
71 let issuer_dn = x509::extract_issuer_dn(der).map_err(|_| Error::CertificateParseFailed)?;
72 let subject_dn = x509::extract_subject_dn(der).map_err(|_| Error::CertificateParseFailed)?;
73 let tbs = x509::extract_tbs_cert(der).map_err(|_| Error::CertificateParseFailed)?;
74 let signature_value = x509::extract_signature_value(der).map_err(|_| Error::CertificateParseFailed)?;
75 let sig_alg_oid = x509::extract_signature_algorithm_oid(der).map_err(|_| Error::CertificateParseFailed)?;
76 let spki_alg_oid = x509::extract_spki_algorithm_oid(spki).map_err(|_| Error::CertificateParseFailed)?;
77 let is_ca = x509::is_ca(der);
78 let has_server_auth_eku = x509::has_eku_server_auth(der);
79 let (nb, na) = x509::parse_validity(der).map_err(|_| Error::CertificateParseFailed)?;
80
81 Ok(Self {
82 der,
83 spki,
84 public_key,
85 issuer_dn,
86 subject_dn,
87 tbs,
88 signature_value,
89 sig_alg_oid,
90 spki_alg_oid,
91 is_ca,
92 has_server_auth_eku,
93 not_before: nb.to_unix_seconds(),
94 not_after: na.to_unix_seconds(),
95 })
96 }
97}
98
99#[derive(Clone)]
104pub struct RootCa {
105 pub subject_dn: heapless::Vec<u8, 256>,
106 pub spki: heapless::Vec<u8, 512>,
107 pub spki_alg_oid: heapless::Vec<u8, 16>,
108 pub not_before: u64,
109 pub not_after: u64,
110}
111
112impl RootCa {
113 pub fn from_der(der: &[u8]) -> Result<Self, Error> {
115 let parsed = ParsedCertificate::from_der(der)?;
116 let mut subject_dn = heapless::Vec::new();
117 subject_dn
118 .extend_from_slice(parsed.subject_dn)
119 .map_err(|_| Error::CertificateParseFailed)?;
120 let mut spki = heapless::Vec::new();
121 spki.extend_from_slice(parsed.spki)
122 .map_err(|_| Error::CertificateParseFailed)?;
123 let mut spki_alg_oid = heapless::Vec::new();
124 spki_alg_oid
125 .extend_from_slice(parsed.spki_alg_oid)
126 .map_err(|_| Error::CertificateParseFailed)?;
127 Ok(Self {
128 subject_dn,
129 spki,
130 spki_alg_oid,
131 not_before: parsed.not_before,
132 not_after: parsed.not_after,
133 })
134 }
135}
136
137#[derive(Clone)]
143pub struct RawPublicKey {
144 pub public_key: alloc::borrow::Cow<'static, [u8]>,
145 pub scheme: SignatureScheme,
146}
147
148impl RawPublicKey {
149 pub fn new(scheme: SignatureScheme, public_key: impl Into<alloc::borrow::Cow<'static, [u8]>>) -> Self {
150 Self {
151 public_key: public_key.into(),
152 scheme,
153 }
154 }
155}
156
157pub trait Clock: Send + Sync {
165 fn now(&self) -> u64;
166}
167
168#[cfg(feature = "std")]
172pub struct SystemClock;
173
174#[cfg(feature = "std")]
175impl Clock for SystemClock {
176 fn now(&self) -> u64 {
177 std::time::SystemTime::now()
178 .duration_since(std::time::UNIX_EPOCH)
179 .unwrap_or_default()
180 .as_secs()
181 }
182}
183
184#[cfg(feature = "default-certificate-verifier")]
215#[derive(Clone)]
216pub struct DefaultCertificateVerifier<C: CryptoProvider> {
217 crypto: C,
218 roots: Option<alloc::vec::Vec<RootCa>>,
219 raw_keys: Option<alloc::vec::Vec<RawPublicKey>>,
220 accept_any: bool,
221 clock: Option<alloc::sync::Arc<dyn Clock>>,
222}
223
224#[cfg(feature = "default-certificate-verifier")]
225impl<C: CryptoProvider> DefaultCertificateVerifier<C> {
226 pub fn new(crypto: C) -> Self {
238 Self {
239 crypto,
240 roots: None,
241 raw_keys: None,
242 accept_any: false,
243 clock: None,
244 }
245 }
246
247 pub fn with_system_roots(mut self) -> Self {
250 self.roots = Some(load_roots(DEFAULT_ROOT_DIRS));
251 self
252 }
253
254 pub fn with_roots(mut self, custom_roots: impl IntoIterator<Item = RootCa>) -> Self {
256 let iter = custom_roots.into_iter();
257 let (iter_size_hint, _) = iter.size_hint();
258 let mut roots = self
259 .roots
260 .take()
261 .unwrap_or(alloc::vec::Vec::with_capacity(iter_size_hint));
262
263 roots.reserve(iter_size_hint); roots.extend(iter);
265 self.roots = Some(roots);
266 self
267 }
268
269 pub fn with_raw_keys(mut self, keys: impl IntoIterator<Item = RawPublicKey>) -> Self {
275 let iter = keys.into_iter();
276 let (iter_size_hint, _) = iter.size_hint();
277 let mut raw_keys = self
278 .raw_keys
279 .take()
280 .unwrap_or(alloc::vec::Vec::with_capacity(iter_size_hint));
281
282 raw_keys.reserve(iter_size_hint); raw_keys.extend(iter);
284 self.raw_keys = Some(raw_keys);
285 self
286 }
287
288 pub fn danger_with_no_verification(mut self) -> Self {
294 self.accept_any = true;
295 self
296 }
297
298 pub fn with_clock(mut self, clock: alloc::sync::Arc<dyn Clock>) -> Self {
305 self.clock = Some(clock);
306 self
307 }
308}
309
310#[cfg(feature = "default-certificate-verifier")]
311#[async_trait::async_trait]
312impl<C: CryptoProvider> CertificateVerifier for DefaultCertificateVerifier<C> {
313 async fn verify_certificate(&self, cert: &ReceivedCertificate, server_name: Option<&str>) -> Result<(), Error> {
314 if self.accept_any {
315 return Ok(());
316 }
317
318 match cert {
319 ReceivedCertificate::RawPublicKey {
320 public_key,
321 scheme,
322 } => {
323 let keys = self.raw_keys.as_ref().ok_or(Error::CertificateNoTrustedRootFound {
324 searched_roots: 0,
325 })?;
326 for key in keys {
327 if key.scheme == *scheme && key.public_key.as_ref() == *public_key {
328 return Ok(());
329 }
330 }
331 Err(Error::CertificateNoTrustedRootFound {
332 searched_roots: keys.len(),
333 })
334 }
335 ReceivedCertificate::X509 {
336 chain,
337 } => {
338 if self.roots.is_none() || self.roots.as_ref().unwrap().is_empty() {
339 return Err(Error::CertificateNoTrustedRootFound {
340 searched_roots: 0,
341 });
342 }
343 self.validate_chain(chain, server_name)
344 }
345 }
346 }
347}
348
349#[cfg(feature = "default-certificate-verifier")]
352impl<C: CryptoProvider> DefaultCertificateVerifier<C> {
353 fn validate_chain(&self, chain: &[ParsedCertificate], server_name: Option<&str>) -> Result<(), Error> {
354 if chain.is_empty() {
355 return Err(Error::CertificateEmptyChain);
356 }
357
358 let server_name = server_name.ok_or(Error::CertificateServerNameRequired)?;
359
360 self.validate_ee_extensions(&chain[0], server_name)?;
361
362 let now = match &self.clock {
363 Some(clock) => clock.now(),
364 None => {
365 #[cfg(feature = "std")]
366 {
367 std::time::SystemTime::now()
368 .duration_since(std::time::UNIX_EPOCH)
369 .unwrap_or_default()
370 .as_secs()
371 }
372
373 #[cfg(not(feature = "std"))]
374 return Err(Error::CertificateClockMissing);
375 }
376 };
377
378 for i in 0..chain.len() {
379 let cert = &chain[i];
380 let is_ee = i == 0;
381 let is_last = i == chain.len() - 1;
382
383 let (issuer_spki, issuer_subject_dn, issuer_spki_alg_oid): (&[u8], &[u8], &[u8]) = {
384 if i + 1 < chain.len() {
385 let issuer = &chain[i + 1];
386 (issuer.spki, issuer.subject_dn, issuer.spki_alg_oid)
387 } else {
388 match self.find_root(cert.issuer_dn, now) {
389 Ok(root) => (&root.spki[..], &root.subject_dn[..], &root.spki_alg_oid[..]),
390 Err(_) => {
391 let root = self.find_root_by_spki(cert.spki, now)?;
392 (&root.spki[..], &root.subject_dn[..], &root.spki_alg_oid[..])
393 }
394 }
395 }
396 };
397
398 if !x509::dn_equal(cert.issuer_dn, issuer_subject_dn) {
399 let is_self_key = !is_ee && self.is_own_root_key(cert.spki, now);
400 if !is_self_key {
401 return Err(Error::CertificateIssuerSubjectDnMismatch);
402 }
403 }
404
405 let is_cross_signed = !is_ee
406 && is_last
407 && self.is_own_root_key(cert.spki, now)
408 && !x509::dn_equal(cert.issuer_dn, issuer_subject_dn);
409 if !is_cross_signed {
410 self.verify_cert_signature(cert, issuer_spki, issuer_spki_alg_oid)
411 .map_err(|_| Error::CertificateSignatureVerificationFailed)?;
412 }
413
414 if now < cert.not_before {
415 return Err(Error::CertificateNotYetValid);
416 }
417 if now > cert.not_after {
418 return Err(Error::CertificateExpired);
419 }
420
421 if !is_ee && cert.is_ca != Some(true) {
422 return Err(Error::CertificateIntermediateNotCa);
423 }
424 }
425
426 Ok(())
427 }
428
429 fn validate_ee_extensions(&self, ee: &ParsedCertificate, server_name: &str) -> Result<(), Error> {
430 let matched = x509::check_san_dns_name(ee.der, server_name).map_err(|_| Error::CertificateParseFailed)?;
431 if !matched {
432 return Err(Error::CertificateSubjectNameMismatch);
433 }
434
435 if ee.is_ca == Some(true) {
436 return Err(Error::CertificateEndEntityMustNotBeCa);
437 }
438
439 if let Some(false) = ee.has_server_auth_eku {
440 return Err(Error::CertificateEkuDoesNotIncludeServerAuth);
441 }
442
443 Ok(())
444 }
445
446 fn verify_cert_signature(
447 &self,
448 cert: &ParsedCertificate,
449 issuer_spki: &[u8],
450 issuer_spki_alg_oid: &[u8],
451 ) -> Result<(), Error> {
452 let scheme = determine_signature_scheme(cert.sig_alg_oid, issuer_spki_alg_oid)?;
453
454 let public_key = x509::extract_key_from_spki(issuer_spki).map_err(|_| Error::CertificateParseFailed)?;
455
456 self.crypto.verify(scheme, public_key, cert.tbs, cert.signature_value)
457 }
458
459 fn find_root(&self, issuer_dn: &[u8], now: u64) -> Result<&RootCa, Error> {
461 if self.roots.is_none() {
462 return Err(Error::CertificateNoTrustedRootFound {
463 searched_roots: 0,
464 });
465 }
466
467 let roots = self.roots.as_ref().unwrap();
468
469 for root in roots {
470 if now < root.not_before || now > root.not_after {
471 continue;
472 }
473 if x509::dn_equal(&root.subject_dn[..], issuer_dn) {
474 return Ok(root);
475 }
476 }
477 Err(Error::CertificateNoTrustedRootFound {
478 searched_roots: roots.len(),
479 })
480 }
481
482 fn find_root_by_spki(&self, spki: &[u8], now: u64) -> Result<&RootCa, Error> {
484 if self.roots.is_none() {
485 return Err(Error::CertificateNoRootFoundBySpkiMatching);
486 }
487
488 let roots = self.roots.as_ref().unwrap();
489 for root in roots {
490 if now < root.not_before || now > root.not_after {
491 continue;
492 }
493
494 if &root.spki[..] == spki {
495 return Ok(root);
496 }
497 }
498 Err(Error::CertificateNoRootFoundBySpkiMatching)
499 }
500
501 fn is_own_root_key(&self, spki: &[u8], now: u64) -> bool {
502 self.find_root_by_spki(spki, now).is_ok()
503 }
504}
505
506fn determine_signature_scheme(sig_alg_oid: &[u8], spki_alg_oid: &[u8]) -> Result<SignatureScheme, Error> {
507 if sig_alg_oid == x509::OID_ED25519 {
508 return Ok(SignatureScheme::Ed25519);
509 }
510 if sig_alg_oid == x509::OID_ECDSA_SHA256 && spki_alg_oid == x509::OID_EC_PUBLIC_KEY_ALG {
511 return Ok(SignatureScheme::EcdsaP256Sha256);
512 }
513 if sig_alg_oid == x509::OID_ECDSA_SHA384 && spki_alg_oid == x509::OID_EC_PUBLIC_KEY_ALG {
514 return Ok(SignatureScheme::EcdsaP384Sha384);
515 }
516 if sig_alg_oid == x509::OID_RSA_SHA256 {
517 return Ok(SignatureScheme::RsaPkcs1Sha256);
518 }
519 if sig_alg_oid == x509::OID_RSA_SHA384 {
520 return Ok(SignatureScheme::RsaPkcs1Sha384);
521 }
522 if sig_alg_oid == x509::OID_RSA_SHA512 {
523 return Ok(SignatureScheme::RsaPkcs1Sha512);
524 }
525 if sig_alg_oid == x509::OID_RSA_PSS {
526 return Ok(SignatureScheme::RsaPssRsaSha256);
527 }
528 Err(Error::CertificateUnsupportedSignatureAlgorithm)
529}
530
531#[cfg(feature = "std")]
534pub fn load_roots(paths: &[&str]) -> alloc::vec::Vec<RootCa> {
535 let mut roots = alloc::vec::Vec::with_capacity(120);
536 for dir in paths {
537 load_certs_from_dir(&mut roots, dir);
538 }
539 roots
540}
541
542#[cfg(feature = "std")]
543fn load_certs_from_dir(roots: &mut alloc::vec::Vec<RootCa>, dir: &str) {
544 let Ok(entries) = std::fs::read_dir(dir) else {
545 return;
546 };
547 for entry in entries.flatten() {
548 let path = entry.path();
549 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
550 if ext != "crt" && ext != "pem" && ext != "cer" && (!ext.is_empty() || !path.is_file()) {
551 continue;
552 }
553 load_certs_from_file(roots, &path);
554 }
555}
556
557fn load_certs_from_file(roots: &mut alloc::vec::Vec<RootCa>, path: &std::path::Path) {
561 let raw = match std::fs::read(path) {
562 Ok(d) => d,
563 Err(_) => return,
564 };
565
566 if raw.trim_ascii().starts_with(b"-----") {
567 for block in crypto_encoding::pem::decode(&raw) {
568 let Ok(block) = block else { continue };
569 let root = match RootCa::from_der(&block.contents) {
570 Ok(r) => r,
571 Err(_) => continue,
572 };
573 if roots
574 .iter()
575 .any(|r| x509::dn_equal(&r.subject_dn[..], &root.subject_dn[..]))
576 {
577 continue;
578 }
579 let _ = roots.push(root);
580 }
581 } else {
582 if let Ok(root) = RootCa::from_der(&raw) {
583 if !roots
584 .iter()
585 .any(|r| x509::dn_equal(&r.subject_dn[..], &root.subject_dn[..]))
586 {
587 roots.push(root);
588 }
589 }
590 }
591}
592
593#[cfg(all(test, feature = "crypto-default-provider"))]
596mod tests {
597 use super::*;
598 use crate::{SignatureScheme, crypto_default_provider::DefaultCryptoProvider};
599
600 fn tokio_runtime() -> tokio::runtime::Runtime {
601 tokio::runtime::Runtime::new().unwrap()
602 }
603
604 fn rpk(scheme: SignatureScheme, key: &[u8]) -> ReceivedCertificate<'_> {
605 ReceivedCertificate::RawPublicKey {
606 public_key: key,
607 scheme,
608 }
609 }
610
611 #[test]
612 fn raw_key_match_accepts() {
613 let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04");
614 let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_raw_keys([pinned]);
615
616 let rt = tokio_runtime();
617 let result =
618 rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04"), None));
619 assert!(result.is_ok(), "matching raw key should be accepted");
620 }
621
622 #[test]
623 fn raw_key_mismatch_key_rejects() {
624 let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04");
625 let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_raw_keys([pinned]);
626
627 let rt = tokio_runtime();
628 let result =
629 rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\xff\xff\xff\xff\xff"), None));
630 assert!(result.is_err(), "mismatched key bytes should be rejected");
631 }
632
633 #[test]
634 fn raw_key_mismatch_scheme_rejects() {
635 let pinned = RawPublicKey::new(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04");
636 let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).with_raw_keys([pinned]);
637
638 let rt = tokio_runtime();
639 let result = rt.block_on(
640 verifier.verify_certificate(&rpk(SignatureScheme::EcdsaP256Sha256, b"\x00\x01\x02\x03\x04"), None),
641 );
642 assert!(result.is_err(), "mismatched scheme should be rejected");
643 }
644
645 #[test]
646 fn raw_key_no_keys_rejects() {
647 let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider);
648
649 let rt = tokio_runtime();
650 let result =
651 rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04"), None));
652 assert!(result.is_err(), "no pinned keys should reject raw public key");
653 }
654
655 #[test]
656 fn no_verification_accepts_raw_key() {
657 let verifier = DefaultCertificateVerifier::new(DefaultCryptoProvider).danger_with_no_verification();
658
659 let rt = tokio_runtime();
660 let result =
661 rt.block_on(verifier.verify_certificate(&rpk(SignatureScheme::Ed25519, b"\x00\x01\x02\x03\x04"), None));
662 assert!(result.is_ok(), "with_no_verification should accept raw public key");
663 }
664}