1use crate::crypto;
2use rustls::pki_types::pem::PemObject;
3use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6use std::{fs, io};
7
8#[cfg(all(
9 any(feature = "quinn", feature = "noq", feature = "quiche"),
10 any(feature = "aws-lc-rs", feature = "ring")
11))]
12use rustls::pki_types::PrivatePkcs8KeyDer;
13#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
14use std::sync::RwLock;
15
16#[derive(Debug, thiserror::Error)]
21#[non_exhaustive]
22pub enum Error {
23 #[error("failed to open certificate file")]
24 Open(#[source] std::io::Error),
25
26 #[error("failed to read file")]
27 ReadFile(#[source] std::io::Error),
28
29 #[error("failed to read certificates")]
30 Read(#[source] rustls::pki_types::pem::Error),
31
32 #[error("failed to parse private key")]
33 Key(#[source] rustls::pki_types::pem::Error),
34
35 #[error("no certificates found")]
36 Empty,
37
38 #[error("no roots found in {}", .0.display())]
39 EmptyRoots(PathBuf),
40
41 #[error(
42 "no trusted roots: provide --client-tls-root, enable --client-tls-system-roots, or use --client-tls-fingerprint / --client-tls-disable-verify"
43 )]
44 NoRoots,
45
46 #[error("invalid TLS fingerprint (expected hex-encoded SHA-256)")]
47 Fingerprint(#[source] hex::FromHexError),
48
49 #[error("invalid TLS fingerprint length: expected 32 bytes (SHA-256), got {0}")]
50 FingerprintLength(usize),
51
52 #[error(
53 "--client-tls-fingerprint cannot be combined with --client-tls-root or --client-tls-system-roots: fingerprint pinning bypasses CA verification"
54 )]
55 FingerprintWithRoots,
56
57 #[error("failed to add root certificate")]
58 AddRoot(#[source] rustls::Error),
59
60 #[cfg(target_os = "android")]
61 #[error("failed to initialize the Android platform verifier")]
62 AndroidInit(#[source] jni::errors::Error),
63
64 #[error("failed to configure client certificate")]
65 ClientAuth(#[source] rustls::Error),
66
67 #[error("both --client-tls-cert and --client-tls-key must be provided")]
68 IncompleteClientAuth,
69
70 #[error("must provide both cert and key")]
71 CertKeyCountMismatch,
72
73 #[error("must provide at least one cert/key pair or generate entry")]
74 NoCertSource,
75
76 #[error("private key {} doesn't match certificate {}", key.display(), cert.display())]
77 KeyMismatch {
78 key: PathBuf,
79 cert: PathBuf,
80 #[source]
81 source: rustls::Error,
82 },
83
84 #[error(transparent)]
85 Rustls(#[from] rustls::Error),
86
87 #[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
88 #[error("failed to build client certificate verifier")]
89 ClientVerifier(#[source] rustls::server::VerifierBuilderError),
90
91 #[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
92 #[error(transparent)]
93 Rcgen(#[from] rcgen::Error),
94
95 #[error("no crypto provider available; enable aws-lc-rs or ring feature")]
96 NoCryptoProvider,
97}
98
99pub type Result<T> = std::result::Result<T, Error>;
101
102pub(crate) fn read_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
104 let file = fs::File::open(path).map_err(Error::Open)?;
105 let mut reader = io::BufReader::new(file);
106 CertificateDer::pem_reader_iter(&mut reader)
107 .collect::<std::result::Result<_, _>>()
108 .map_err(Error::Read)
109}
110
111#[serde_with::serde_as]
115#[derive(Clone, Default, Debug, clap::Args, serde::Serialize, serde::Deserialize)]
116#[serde(default, deny_unknown_fields)]
117#[group(id = "tls-client")]
118#[non_exhaustive]
119pub struct Client {
120 #[serde(skip_serializing_if = "Vec::is_empty")]
130 #[arg(id = "client-tls-root", long = "client-tls-root", env = "MOQ_CLIENT_TLS_ROOT")]
131 #[serde_as(as = "serde_with::OneOrMany<_>")]
132 pub root: Vec<PathBuf>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
141 #[arg(
142 id = "client-tls-system-roots",
143 long = "client-tls-system-roots",
144 env = "MOQ_CLIENT_TLS_SYSTEM_ROOTS",
145 default_missing_value = "true",
146 num_args = 0..=1,
147 require_equals = true,
148 value_parser = clap::value_parser!(bool),
149 )]
150 pub system_roots: Option<bool>,
151
152 #[serde(skip_serializing_if = "Vec::is_empty")]
163 #[arg(
164 id = "client-tls-fingerprint",
165 long = "client-tls-fingerprint",
166 env = "MOQ_CLIENT_TLS_FINGERPRINT"
167 )]
168 #[serde_as(as = "serde_with::OneOrMany<_>")]
169 pub fingerprint: Vec<String>,
170
171 #[serde(skip_serializing_if = "Option::is_none")]
176 #[arg(id = "client-tls-cert", long = "client-tls-cert", env = "MOQ_CLIENT_TLS_CERT")]
177 pub cert: Option<PathBuf>,
178
179 #[serde(skip_serializing_if = "Option::is_none")]
184 #[arg(id = "client-tls-key", long = "client-tls-key", env = "MOQ_CLIENT_TLS_KEY")]
185 pub key: Option<PathBuf>,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
191 #[arg(
192 id = "client-tls-disable-verify",
193 long = "client-tls-disable-verify",
194 env = "MOQ_CLIENT_TLS_DISABLE_VERIFY",
195 default_missing_value = "true",
196 num_args = 0..=1,
197 require_equals = true,
198 value_parser = clap::value_parser!(bool),
199 )]
200 pub disable_verify: Option<bool>,
201
202 #[command(flatten)]
206 #[serde(skip)]
207 deprecated: Deprecated,
208}
209
210#[derive(Clone, Default, Debug, clap::Args)]
215struct Deprecated {
216 #[arg(long = "tls-root", hide = true)]
217 root: Vec<PathBuf>,
218
219 #[arg(
220 long = "tls-system-roots",
221 hide = true,
222 default_missing_value = "true",
223 num_args = 0..=1,
224 require_equals = true,
225 value_parser = clap::value_parser!(bool),
226 )]
227 system_roots: Option<bool>,
228
229 #[arg(long = "tls-fingerprint", hide = true)]
230 fingerprint: Vec<String>,
231
232 #[arg(
233 long = "tls-disable-verify",
234 hide = true,
235 default_missing_value = "true",
236 num_args = 0..=1,
237 require_equals = true,
238 value_parser = clap::value_parser!(bool),
239 )]
240 disable_verify: Option<bool>,
241}
242
243#[derive(Clone)]
250pub(crate) enum Verification {
251 Disabled,
253
254 Fingerprints(Vec<[u8; 32]>),
257
258 Roots {
263 custom: Vec<CertificateDer<'static>>,
264 system: bool,
265 },
266}
267
268impl Client {
269 pub(crate) fn warn_deprecated(&self) {
272 if !self.deprecated.root.is_empty() {
273 tracing::warn!("--tls-root is deprecated; use --client-tls-root");
274 }
275 if self.deprecated.system_roots.is_some() {
276 tracing::warn!("--tls-system-roots is deprecated; use --client-tls-system-roots");
277 }
278 if !self.deprecated.fingerprint.is_empty() {
279 tracing::warn!("--tls-fingerprint is deprecated; use --client-tls-fingerprint");
280 }
281 if self.deprecated.disable_verify.is_some() {
282 tracing::warn!("--tls-disable-verify is deprecated; use --client-tls-disable-verify");
283 }
284 }
285
286 pub(crate) fn effective_root(&self) -> Vec<PathBuf> {
288 let mut root = self.root.clone();
289 root.extend(self.deprecated.root.iter().cloned());
290 root
291 }
292
293 pub(crate) fn effective_fingerprint(&self) -> Vec<String> {
295 let mut fp = self.fingerprint.clone();
296 fp.extend(self.deprecated.fingerprint.iter().cloned());
297 fp
298 }
299
300 pub(crate) fn effective_system_roots(&self) -> Option<bool> {
302 self.system_roots.or(self.deprecated.system_roots)
303 }
304
305 pub(crate) fn effective_disable_verify(&self) -> Option<bool> {
307 self.disable_verify.or(self.deprecated.disable_verify)
308 }
309
310 pub(crate) fn verification(&self) -> Result<Verification> {
321 self.warn_deprecated();
322
323 if self.effective_disable_verify().unwrap_or_default() {
324 return Ok(Verification::Disabled);
325 }
326
327 let fingerprints = self.fingerprints()?;
328 if !fingerprints.is_empty() {
329 if !self.effective_root().is_empty() || self.effective_system_roots() == Some(true) {
330 return Err(Error::FingerprintWithRoots);
331 }
332 return Ok(Verification::Fingerprints(fingerprints));
333 }
334
335 let root = self.effective_root();
336 let system = self.effective_system_roots().unwrap_or(root.is_empty());
339
340 let mut custom = Vec::new();
341 for root in &root {
342 let certs = read_certs(root)?;
343 if certs.is_empty() {
344 return Err(Error::EmptyRoots(root.clone()));
345 }
346 custom.extend(certs);
347 }
348
349 if !system && custom.is_empty() {
354 return Err(Error::NoRoots);
355 }
356
357 Ok(Verification::Roots { custom, system })
358 }
359
360 pub(crate) fn allows_http_bootstrap(&self) -> bool {
369 self.effective_fingerprint().is_empty() && !self.effective_disable_verify().unwrap_or_default()
370 }
371
372 fn fingerprints(&self) -> Result<Vec<[u8; 32]>> {
374 self.effective_fingerprint()
375 .iter()
376 .map(|fp| {
377 let bytes = hex::decode(fp.trim()).map_err(Error::Fingerprint)?;
378 bytes.try_into().map_err(|v: Vec<u8>| Error::FingerprintLength(v.len()))
379 })
380 .collect()
381 }
382
383 pub fn build(&self) -> Result<rustls::ClientConfig> {
388 let provider = crypto::provider();
389 let verification = self.verification()?;
390
391 let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
394 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])?;
395
396 let builder = match &verification {
399 Verification::Roots { custom, system: true } => Self::system_verifier(builder, custom, &provider)?,
400 Verification::Roots { custom, system: false } => builder.with_root_certificates(root_store(custom)?),
401 Verification::Disabled | Verification::Fingerprints(_) => {
402 builder.with_root_certificates(rustls::RootCertStore::empty())
403 }
404 };
405
406 let mut tls = self.with_client_auth(builder)?;
407
408 match verification {
409 Verification::Disabled => {
410 tracing::warn!(
411 "TLS server certificate verification is disabled; A man-in-the-middle attack is possible."
412 );
413 tls.dangerous()
414 .set_certificate_verifier(Arc::new(NoCertificateVerification(provider)));
415 }
416 Verification::Fingerprints(fingerprints) => {
417 let fingerprints = fingerprints.into_iter().map(|fp| fp.to_vec()).collect();
418 let verifier = FingerprintVerifier::new(provider, fingerprints);
419 tls.dangerous().set_certificate_verifier(Arc::new(verifier));
420 }
421 Verification::Roots { .. } => {}
423 }
424
425 Ok(tls)
426 }
427
428 fn system_verifier(
436 builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>,
437 custom: &[CertificateDer<'static>],
438 provider: &crypto::Provider,
439 ) -> Result<rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>> {
440 #[cfg(target_os = "android")]
445 {
446 if ANDROID_INITIALIZED.load(std::sync::atomic::Ordering::Acquire) && custom.is_empty() {
447 let verifier = rustls_platform_verifier::Verifier::new(provider.clone())?;
448 return Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)));
449 }
450
451 let mut roots = rustls::RootCertStore::empty();
452 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
453 for cert in custom {
454 roots.add(cert.clone()).map_err(Error::AddRoot)?;
455 }
456 Ok(builder.with_root_certificates(roots))
457 }
458
459 #[cfg(not(target_os = "android"))]
460 {
461 let verifier = if custom.is_empty() {
462 rustls_platform_verifier::Verifier::new(provider.clone())?
463 } else {
464 rustls_platform_verifier::Verifier::new_with_extra_roots(custom.iter().cloned(), provider.clone())?
465 };
466 Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)))
467 }
468 }
469
470 fn with_client_auth(
472 &self,
473 builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>,
474 ) -> Result<rustls::ClientConfig> {
475 Ok(match (&self.cert, &self.key) {
476 (Some(cert_path), Some(key_path)) => {
477 let cert_pem = fs::read(cert_path).map_err(Error::ReadFile)?;
478 let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_pem)
479 .collect::<std::result::Result<_, _>>()
480 .map_err(Error::Read)?;
481 if chain.is_empty() {
482 return Err(Error::Empty);
483 }
484 let key_pem = fs::read(key_path).map_err(Error::ReadFile)?;
485 let key = PrivateKeyDer::from_pem_slice(&key_pem).map_err(Error::Key)?;
486 builder.with_client_auth_cert(chain, key).map_err(Error::ClientAuth)?
487 }
488 (None, None) => builder.with_no_client_auth(),
489 _ => return Err(Error::IncompleteClientAuth),
490 })
491 }
492}
493
494fn root_store(custom: &[CertificateDer<'static>]) -> Result<rustls::RootCertStore> {
496 let mut roots = rustls::RootCertStore::empty();
497 for cert in custom {
498 roots.add(cert.clone()).map_err(Error::AddRoot)?;
499 }
500 Ok(roots)
501}
502
503#[cfg(target_os = "android")]
505static ANDROID_INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
506
507#[cfg(target_os = "android")]
518pub fn init_android(env: &mut jni::JNIEnv, context: jni::objects::JObject) -> Result<()> {
519 rustls_platform_verifier::android::init_with_env(env, context).map_err(Error::AndroidInit)?;
520 ANDROID_INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
521 Ok(())
522}
523
524#[serde_with::serde_as]
533#[derive(clap::Args, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
534#[serde(deny_unknown_fields)]
535#[group(id = "tls-server")]
536#[non_exhaustive]
537pub struct Server {
538 #[arg(long = "tls-cert", id = "tls-cert", env = "MOQ_SERVER_TLS_CERT")]
540 #[serde(default, skip_serializing_if = "Vec::is_empty")]
541 #[serde_as(as = "serde_with::OneOrMany<_>")]
542 pub cert: Vec<PathBuf>,
543
544 #[arg(long = "tls-key", id = "tls-key", env = "MOQ_SERVER_TLS_KEY")]
546 #[serde(default, skip_serializing_if = "Vec::is_empty")]
547 #[serde_as(as = "serde_with::OneOrMany<_>")]
548 pub key: Vec<PathBuf>,
549
550 #[arg(
553 long = "tls-generate",
554 id = "tls-generate",
555 value_delimiter = ',',
556 env = "MOQ_SERVER_TLS_GENERATE"
557 )]
558 #[serde(default, skip_serializing_if = "Vec::is_empty")]
559 #[serde_as(as = "serde_with::OneOrMany<_>")]
560 pub generate: Vec<String>,
561
562 #[arg(
574 long = "server-tls-root",
575 id = "server-tls-root",
576 value_delimiter = ',',
577 env = "MOQ_SERVER_TLS_ROOT"
578 )]
579 #[serde(default, skip_serializing_if = "Vec::is_empty")]
580 #[serde_as(as = "serde_with::OneOrMany<_>")]
581 pub root: Vec<PathBuf>,
582}
583
584impl Server {
585 pub fn load_roots(&self) -> Result<rustls::RootCertStore> {
587 let mut roots = rustls::RootCertStore::empty();
588 for path in &self.root {
589 let certs = read_certs(path)?;
590 if certs.is_empty() {
591 return Err(Error::Empty);
592 }
593 for cert in certs {
594 roots.add(cert).map_err(Error::AddRoot)?;
595 }
596 }
597 Ok(roots)
598 }
599
600 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
609 pub fn server_config(&self, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
610 server_config(self, alpn)
611 }
612}
613
614#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
616fn server_config(config: &Server, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
617 let provider = crypto::provider();
618
619 let certs = ServeCerts::new(provider.clone());
620 certs.load_certs(config)?;
621 let certs = Arc::new(certs);
622
623 let builder =
625 rustls::ServerConfig::builder_with_provider(provider.clone()).with_safe_default_protocol_versions()?;
626
627 let mut tls = if config.root.is_empty() {
628 builder.with_no_client_auth().with_cert_resolver(certs)
629 } else {
630 let roots = config.load_roots()?;
631 let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider)
632 .allow_unauthenticated()
633 .build()
634 .map_err(Error::ClientVerifier)?;
635 builder.with_client_cert_verifier(verifier).with_cert_resolver(certs)
636 };
637
638 tls.alpn_protocols = alpn;
639 Ok(Arc::new(tls))
640}
641
642pub struct PeerIdentity {
649 chain: Vec<CertificateDer<'static>>,
650}
651
652impl PeerIdentity {
653 #[cfg(any(feature = "quinn", feature = "noq"))]
657 pub(crate) fn from_any(identity: Option<Box<dyn std::any::Any>>) -> Option<Self> {
658 let chain = identity?.downcast::<Vec<CertificateDer<'static>>>().ok()?;
659 Some(Self { chain: *chain })
660 }
661
662 pub fn chain(&self) -> &[CertificateDer<'static>] {
668 &self.chain
669 }
670
671 pub fn expiry(&self) -> Option<std::time::SystemTime> {
674 use std::time::{Duration, UNIX_EPOCH};
675
676 let leaf = self.chain.first()?;
677 let (_, cert) = x509_parser::parse_x509_certificate(leaf).ok()?;
678 let secs = u64::try_from(cert.validity().not_after.timestamp()).ok()?;
679 Some(UNIX_EPOCH + Duration::from_secs(secs))
680 }
681}
682
683#[derive(Debug)]
685pub struct Info {
686 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
687 pub(crate) certs: Vec<Arc<rustls::sign::CertifiedKey>>,
688 pub fingerprints: Vec<String>,
689}
690
691impl Info {
692 pub(crate) fn empty() -> Self {
694 Self {
695 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
696 certs: Vec::new(),
697 fingerprints: Vec::new(),
698 }
699 }
700}
701
702#[derive(Debug)]
705struct NoCertificateVerification(crypto::Provider);
706
707impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
708 fn verify_server_cert(
709 &self,
710 _end_entity: &CertificateDer<'_>,
711 _intermediates: &[CertificateDer<'_>],
712 _server_name: &ServerName<'_>,
713 _ocsp: &[u8],
714 _now: UnixTime,
715 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
716 Ok(rustls::client::danger::ServerCertVerified::assertion())
717 }
718
719 fn verify_tls12_signature(
720 &self,
721 message: &[u8],
722 cert: &CertificateDer<'_>,
723 dss: &rustls::DigitallySignedStruct,
724 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
725 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
726 }
727
728 fn verify_tls13_signature(
729 &self,
730 message: &[u8],
731 cert: &CertificateDer<'_>,
732 dss: &rustls::DigitallySignedStruct,
733 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
734 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
735 }
736
737 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
738 self.0.signature_verification_algorithms.supported_schemes()
739 }
740}
741
742#[derive(Debug)]
745pub(crate) struct FingerprintVerifier {
746 provider: crypto::Provider,
747 fingerprints: Vec<Vec<u8>>,
748}
749
750impl FingerprintVerifier {
751 pub fn new(provider: crypto::Provider, fingerprints: Vec<Vec<u8>>) -> Self {
752 Self { provider, fingerprints }
753 }
754}
755
756impl rustls::client::danger::ServerCertVerifier for FingerprintVerifier {
757 fn verify_server_cert(
758 &self,
759 end_entity: &CertificateDer<'_>,
760 _intermediates: &[CertificateDer<'_>],
761 _server_name: &ServerName<'_>,
762 _ocsp: &[u8],
763 _now: UnixTime,
764 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
765 let fingerprint = crypto::sha256(&self.provider, end_entity);
766 if self.fingerprints.iter().any(|fp| fingerprint.as_ref() == fp.as_slice()) {
767 Ok(rustls::client::danger::ServerCertVerified::assertion())
768 } else {
769 Err(rustls::Error::General("fingerprint mismatch".into()))
770 }
771 }
772
773 fn verify_tls12_signature(
774 &self,
775 message: &[u8],
776 cert: &CertificateDer<'_>,
777 dss: &rustls::DigitallySignedStruct,
778 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
779 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
780 }
781
782 fn verify_tls13_signature(
783 &self,
784 message: &[u8],
785 cert: &CertificateDer<'_>,
786 dss: &rustls::DigitallySignedStruct,
787 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
788 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
789 }
790
791 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
792 self.provider.signature_verification_algorithms.supported_schemes()
793 }
794}
795
796#[cfg(test)]
797#[cfg(all(any(feature = "quinn", feature = "noq", feature = "quiche"), feature = "aws-lc-rs"))]
798mod tests {
799 use super::*;
800 use rustls::client::danger::ServerCertVerifier;
801 use rustls::pki_types::ServerName;
802
803 fn self_signed() -> CertificateDer<'static> {
804 let key = rcgen::KeyPair::generate().unwrap();
805 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
806 params.self_signed(&key).unwrap().into()
807 }
808
809 #[cfg(any(feature = "quinn", feature = "noq"))]
810 #[test]
811 fn peer_identity_expiry_reads_not_after() {
812 let not_after = ::time::OffsetDateTime::from_unix_timestamp(2_000_000_000).unwrap();
814
815 let key = rcgen::KeyPair::generate().unwrap();
816 let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
817 params.not_after = not_after;
818 let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into();
819
820 let identity: Box<dyn std::any::Any> = Box::new(vec![cert]);
822 let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed");
823 let expiry = parsed.expiry().expect("expiry parsed");
824 assert_eq!(
825 expiry.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
826 2_000_000_000
827 );
828 }
829
830 #[cfg(any(feature = "quinn", feature = "noq"))]
831 #[test]
832 fn peer_identity_none_without_chain() {
833 assert!(PeerIdentity::from_any(None).is_none());
834 let bogus: Box<dyn std::any::Any> = Box::new(42u32);
836 assert!(PeerIdentity::from_any(Some(bogus)).is_none());
837 }
838
839 #[test]
840 fn fingerprint_verifier_matches_and_rejects() {
841 let provider = crypto::provider();
842 let cert = self_signed();
843 let fingerprint = crypto::sha256(&provider, cert.as_ref()).as_ref().to_vec();
844
845 let name = ServerName::try_from("localhost").unwrap();
846 let now = UnixTime::now();
847
848 let verifier = FingerprintVerifier::new(provider.clone(), vec![fingerprint]);
849 assert!(verifier.verify_server_cert(&cert, &[], &name, &[], now).is_ok());
850
851 let other = self_signed();
853 assert!(verifier.verify_server_cert(&other, &[], &name, &[], now).is_err());
854 }
855
856 #[test]
857 fn build_installs_fingerprint_verifier() {
858 let cert = self_signed();
859 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
860
861 let config = Client {
863 fingerprint: vec![fingerprint],
864 ..Default::default()
865 };
866 assert!(config.build().is_ok());
867 }
868
869 #[test]
870 fn build_rejects_invalid_fingerprint_hex() {
871 let config = Client {
872 fingerprint: vec!["not-hex".to_string()],
873 ..Default::default()
874 };
875 assert!(matches!(config.build(), Err(Error::Fingerprint(_))));
876 }
877
878 #[test]
879 fn build_rejects_wrong_length_fingerprint() {
880 let config = Client {
882 fingerprint: vec!["abcd".to_string()],
883 ..Default::default()
884 };
885 assert!(matches!(config.build(), Err(Error::FingerprintLength(2))));
886 }
887
888 #[test]
889 fn build_rejects_no_roots() {
890 let config = Client {
893 system_roots: Some(false),
894 ..Default::default()
895 };
896 assert!(matches!(config.build(), Err(Error::NoRoots)));
897 }
898
899 #[test]
900 fn build_allows_no_roots_when_verification_overridden() {
901 let config = Client {
903 system_roots: Some(false),
904 disable_verify: Some(true),
905 ..Default::default()
906 };
907 assert!(config.build().is_ok());
908
909 let cert = self_signed();
911 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
912 let config = Client {
913 system_roots: Some(false),
914 fingerprint: vec![fingerprint],
915 ..Default::default()
916 };
917 assert!(config.build().is_ok());
918 }
919
920 #[test]
921 fn build_rejects_fingerprint_with_roots() {
922 let cert = self_signed();
923 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
924
925 let with_system = Client {
928 fingerprint: vec![fingerprint.clone()],
929 system_roots: Some(true),
930 ..Default::default()
931 };
932 assert!(matches!(with_system.build(), Err(Error::FingerprintWithRoots)));
933
934 let with_custom = Client {
937 fingerprint: vec![fingerprint],
938 root: vec![PathBuf::from("/does-not-exist.pem")],
939 ..Default::default()
940 };
941 assert!(matches!(with_custom.build(), Err(Error::FingerprintWithRoots)));
942 }
943
944 fn self_signed_root() -> (tempfile::NamedTempFile, PathBuf) {
947 use std::io::Write;
948 let key = rcgen::KeyPair::generate().unwrap();
949 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
950 let cert = params.self_signed(&key).unwrap();
951 let mut file = tempfile::NamedTempFile::new().unwrap();
952 file.write_all(cert.pem().as_bytes()).unwrap();
953 let path = file.path().to_path_buf();
954 (file, path)
955 }
956
957 #[test]
958 fn build_uses_platform_verifier_by_default() {
959 assert!(Client::default().build().is_ok());
962 }
963
964 #[test]
965 fn build_with_custom_roots_only() {
966 let (_keep, path) = self_signed_root();
969 let config = Client {
970 root: vec![path],
971 ..Default::default()
972 };
973 assert!(config.build().is_ok());
974 }
975
976 #[test]
977 fn build_with_custom_and_system_roots() {
978 let (_keep, path) = self_signed_root();
981 let config = Client {
982 root: vec![path],
983 system_roots: Some(true),
984 ..Default::default()
985 };
986 assert!(config.build().is_ok());
987 }
988}
989
990#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
993#[derive(Debug)]
994pub(crate) struct ServeCerts {
995 pub info: Arc<RwLock<Info>>,
996 provider: crypto::Provider,
997}
998
999#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1000impl ServeCerts {
1001 pub fn new(provider: crypto::Provider) -> Self {
1002 Self {
1003 info: Arc::new(RwLock::new(Info {
1004 certs: Vec::new(),
1005 fingerprints: Vec::new(),
1006 })),
1007 provider,
1008 }
1009 }
1010
1011 pub fn load_certs(&self, config: &Server) -> Result<()> {
1012 if config.cert.len() != config.key.len() {
1013 return Err(Error::CertKeyCountMismatch);
1014 }
1015 if config.cert.is_empty() && config.generate.is_empty() {
1016 return Err(Error::NoCertSource);
1017 }
1018
1019 let mut certs = Vec::new();
1020
1021 for (cert, key) in config.cert.iter().zip(config.key.iter()) {
1023 certs.push(Arc::new(self.load(cert, key)?));
1024 }
1025
1026 if !config.generate.is_empty() {
1028 certs.push(Arc::new(self.generate(&config.generate)?));
1029 }
1030
1031 self.set_certs(certs);
1032 Ok(())
1033 }
1034
1035 fn load(&self, chain_path: &Path, key_path: &Path) -> Result<rustls::sign::CertifiedKey> {
1037 let chain = read_certs(chain_path)?;
1038 if chain.is_empty() {
1039 return Err(Error::Empty);
1040 }
1041
1042 let key = PrivateKeyDer::from_pem_file(key_path).map_err(Error::Key)?;
1044 let key = self.provider.key_provider.load_private_key(key)?;
1045
1046 let certified_key = rustls::sign::CertifiedKey::new(chain, key);
1047
1048 certified_key.keys_match().map_err(|source| Error::KeyMismatch {
1049 key: key_path.to_path_buf(),
1050 cert: chain_path.to_path_buf(),
1051 source,
1052 })?;
1053
1054 Ok(certified_key)
1055 }
1056
1057 #[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
1058 fn generate(&self, hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1059 let key_pair = rcgen::KeyPair::generate()?;
1060
1061 let mut params = rcgen::CertificateParams::new(hostnames)?;
1062
1063 params.not_before = ::time::OffsetDateTime::now_utc() - ::time::Duration::days(1);
1066 params.not_after = params.not_before + ::time::Duration::days(14);
1067
1068 let cert = params.self_signed(&key_pair)?;
1070
1071 let key_der = key_pair.serialized_der().to_vec();
1073 let key_der = PrivatePkcs8KeyDer::from(key_der);
1074 let key = self.provider.key_provider.load_private_key(key_der.into())?;
1075
1076 Ok(rustls::sign::CertifiedKey::new(vec![cert.into()], key))
1078 }
1079
1080 #[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
1081 fn generate(&self, _hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1082 Err(Error::NoCryptoProvider)
1083 }
1084
1085 pub fn set_certs(&self, certs: Vec<Arc<rustls::sign::CertifiedKey>>) {
1087 let fingerprints = certs
1088 .iter()
1089 .map(|ck| {
1090 let fingerprint = crate::crypto::sha256(&self.provider, ck.cert[0].as_ref());
1091 hex::encode(fingerprint)
1092 })
1093 .collect();
1094
1095 let mut info = self.info.write().expect("info write lock poisoned");
1096 info.certs = certs;
1097 info.fingerprints = fingerprints;
1098 }
1099
1100 fn best_certificate(
1102 &self,
1103 client_hello: &rustls::server::ClientHello<'_>,
1104 ) -> Option<Arc<rustls::sign::CertifiedKey>> {
1105 let server_name = client_hello.server_name()?;
1106 let dns_name = rustls::pki_types::ServerName::try_from(server_name).ok()?;
1107
1108 for ck in self.info.read().expect("info read lock poisoned").certs.iter() {
1109 let leaf: webpki::EndEntityCert = ck
1110 .end_entity_cert()
1111 .expect("missing certificate")
1112 .try_into()
1113 .expect("failed to parse certificate");
1114
1115 if leaf.verify_is_valid_for_subject_name(&dns_name).is_ok() {
1116 return Some(ck.clone());
1117 }
1118 }
1119
1120 None
1121 }
1122}
1123
1124#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1125impl rustls::server::ResolvesServerCert for ServeCerts {
1126 fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<rustls::sign::CertifiedKey>> {
1127 if let Some(cert) = self.best_certificate(&client_hello) {
1128 return Some(cert);
1129 }
1130
1131 tracing::warn!(server_name = ?client_hello.server_name(), "no SNI certificate found");
1134
1135 self.info
1136 .read()
1137 .expect("info read lock poisoned")
1138 .certs
1139 .first()
1140 .cloned()
1141 }
1142}
1143
1144#[cfg(any(feature = "quinn", feature = "noq"))]
1152pub(crate) async fn reload_certs(certs: Arc<ServeCerts>, tls_config: Server) {
1153 let paths: Vec<PathBuf> = tls_config.cert.iter().chain(tls_config.key.iter()).cloned().collect();
1154 if paths.is_empty() {
1155 return;
1156 }
1157
1158 let mut watcher = match crate::watch::FileWatcher::new(&paths) {
1159 Ok(watcher) => watcher,
1160 Err(err) => {
1161 tracing::error!(%err, "failed to watch certificate files; hot reload disabled");
1162 return;
1163 }
1164 };
1165
1166 loop {
1167 watcher.changed().await;
1168 tracing::info!("reloading server certificates");
1169
1170 if let Err(err) = certs.load_certs(&tls_config) {
1171 tracing::warn!(%err, "failed to reload server certificates");
1172 }
1173 }
1174}