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 #[serde(skip_serializing_if = "Option::is_none")]
207 #[arg(
208 id = "client-tls-host-name",
209 long = "client-tls-host-name",
210 env = "MOQ_CLIENT_TLS_HOST_NAME"
211 )]
212 pub host_name: Option<String>,
213
214 #[command(flatten)]
218 #[serde(skip)]
219 deprecated: Deprecated,
220}
221
222#[derive(Clone, Default, Debug, clap::Args)]
227struct Deprecated {
228 #[arg(long = "tls-root", hide = true)]
229 root: Vec<PathBuf>,
230
231 #[arg(
232 long = "tls-system-roots",
233 hide = true,
234 default_missing_value = "true",
235 num_args = 0..=1,
236 require_equals = true,
237 value_parser = clap::value_parser!(bool),
238 )]
239 system_roots: Option<bool>,
240
241 #[arg(long = "tls-fingerprint", hide = true)]
242 fingerprint: Vec<String>,
243
244 #[arg(
245 long = "tls-disable-verify",
246 hide = true,
247 default_missing_value = "true",
248 num_args = 0..=1,
249 require_equals = true,
250 value_parser = clap::value_parser!(bool),
251 )]
252 disable_verify: Option<bool>,
253}
254
255#[derive(Clone)]
262pub(crate) enum Verification {
263 Disabled,
265
266 Fingerprints(Vec<[u8; 32]>),
269
270 Roots {
275 custom: Vec<CertificateDer<'static>>,
276 system: bool,
277 },
278}
279
280impl Client {
281 pub(crate) fn warn_deprecated(&self) {
284 if !self.deprecated.root.is_empty() {
285 tracing::warn!("--tls-root is deprecated; use --client-tls-root");
286 }
287 if self.deprecated.system_roots.is_some() {
288 tracing::warn!("--tls-system-roots is deprecated; use --client-tls-system-roots");
289 }
290 if !self.deprecated.fingerprint.is_empty() {
291 tracing::warn!("--tls-fingerprint is deprecated; use --client-tls-fingerprint");
292 }
293 if self.deprecated.disable_verify.is_some() {
294 tracing::warn!("--tls-disable-verify is deprecated; use --client-tls-disable-verify");
295 }
296 }
297
298 pub(crate) fn effective_root(&self) -> Vec<PathBuf> {
300 let mut root = self.root.clone();
301 root.extend(self.deprecated.root.iter().cloned());
302 root
303 }
304
305 pub(crate) fn effective_fingerprint(&self) -> Vec<String> {
307 let mut fp = self.fingerprint.clone();
308 fp.extend(self.deprecated.fingerprint.iter().cloned());
309 fp
310 }
311
312 pub(crate) fn effective_system_roots(&self) -> Option<bool> {
314 self.system_roots.or(self.deprecated.system_roots)
315 }
316
317 pub(crate) fn effective_disable_verify(&self) -> Option<bool> {
319 self.disable_verify.or(self.deprecated.disable_verify)
320 }
321
322 pub(crate) fn verification(&self) -> Result<Verification> {
333 self.warn_deprecated();
334
335 if self.effective_disable_verify().unwrap_or_default() {
336 return Ok(Verification::Disabled);
337 }
338
339 let fingerprints = self.fingerprints()?;
340 if !fingerprints.is_empty() {
341 if !self.effective_root().is_empty() || self.effective_system_roots() == Some(true) {
342 return Err(Error::FingerprintWithRoots);
343 }
344 return Ok(Verification::Fingerprints(fingerprints));
345 }
346
347 let root = self.effective_root();
348 let system = self.effective_system_roots().unwrap_or(root.is_empty());
351
352 let mut custom = Vec::new();
353 for root in &root {
354 let certs = read_certs(root)?;
355 if certs.is_empty() {
356 return Err(Error::EmptyRoots(root.clone()));
357 }
358 custom.extend(certs);
359 }
360
361 if !system && custom.is_empty() {
366 return Err(Error::NoRoots);
367 }
368
369 Ok(Verification::Roots { custom, system })
370 }
371
372 pub(crate) fn allows_http_bootstrap(&self) -> bool {
381 self.effective_fingerprint().is_empty() && !self.effective_disable_verify().unwrap_or_default()
382 }
383
384 fn fingerprints(&self) -> Result<Vec<[u8; 32]>> {
386 self.effective_fingerprint()
387 .iter()
388 .map(|fp| {
389 let bytes = hex::decode(fp.trim()).map_err(Error::Fingerprint)?;
390 bytes.try_into().map_err(|v: Vec<u8>| Error::FingerprintLength(v.len()))
391 })
392 .collect()
393 }
394
395 pub fn build(&self) -> Result<rustls::ClientConfig> {
400 let provider = crypto::provider();
401 let verification = self.verification()?;
402
403 let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
406 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])?;
407
408 let builder = match &verification {
411 Verification::Roots { custom, system: true } => Self::system_verifier(builder, custom, &provider)?,
412 Verification::Roots { custom, system: false } => builder.with_root_certificates(root_store(custom)?),
413 Verification::Disabled | Verification::Fingerprints(_) => {
414 builder.with_root_certificates(rustls::RootCertStore::empty())
415 }
416 };
417
418 let mut tls = self.with_client_auth(builder)?;
419
420 match verification {
421 Verification::Disabled => {
422 tracing::warn!(
423 "TLS server certificate verification is disabled; A man-in-the-middle attack is possible."
424 );
425 tls.dangerous()
426 .set_certificate_verifier(Arc::new(NoCertificateVerification(provider)));
427 }
428 Verification::Fingerprints(fingerprints) => {
429 let fingerprints = fingerprints.into_iter().map(|fp| fp.to_vec()).collect();
430 let verifier = FingerprintVerifier::new(provider, fingerprints);
431 tls.dangerous().set_certificate_verifier(Arc::new(verifier));
432 }
433 Verification::Roots { .. } => {}
435 }
436
437 Ok(tls)
438 }
439
440 fn system_verifier(
448 builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>,
449 custom: &[CertificateDer<'static>],
450 provider: &crypto::Provider,
451 ) -> Result<rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>> {
452 #[cfg(target_os = "android")]
457 {
458 if ANDROID_INITIALIZED.load(std::sync::atomic::Ordering::Acquire) && custom.is_empty() {
459 let verifier = rustls_platform_verifier::Verifier::new(provider.clone())?;
460 return Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)));
461 }
462
463 let mut roots = rustls::RootCertStore::empty();
464 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
465 for cert in custom {
466 roots.add(cert.clone()).map_err(Error::AddRoot)?;
467 }
468 Ok(builder.with_root_certificates(roots))
469 }
470
471 #[cfg(not(target_os = "android"))]
472 {
473 let verifier = if custom.is_empty() {
474 rustls_platform_verifier::Verifier::new(provider.clone())?
475 } else {
476 rustls_platform_verifier::Verifier::new_with_extra_roots(custom.iter().cloned(), provider.clone())?
477 };
478 Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)))
479 }
480 }
481
482 fn with_client_auth(
484 &self,
485 builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>,
486 ) -> Result<rustls::ClientConfig> {
487 Ok(match (&self.cert, &self.key) {
488 (Some(cert_path), Some(key_path)) => {
489 let cert_pem = fs::read(cert_path).map_err(Error::ReadFile)?;
490 let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_pem)
491 .collect::<std::result::Result<_, _>>()
492 .map_err(Error::Read)?;
493 if chain.is_empty() {
494 return Err(Error::Empty);
495 }
496 let key_pem = fs::read(key_path).map_err(Error::ReadFile)?;
497 let key = PrivateKeyDer::from_pem_slice(&key_pem).map_err(Error::Key)?;
498 builder.with_client_auth_cert(chain, key).map_err(Error::ClientAuth)?
499 }
500 (None, None) => builder.with_no_client_auth(),
501 _ => return Err(Error::IncompleteClientAuth),
502 })
503 }
504}
505
506fn root_store(custom: &[CertificateDer<'static>]) -> Result<rustls::RootCertStore> {
508 let mut roots = rustls::RootCertStore::empty();
509 for cert in custom {
510 roots.add(cert.clone()).map_err(Error::AddRoot)?;
511 }
512 Ok(roots)
513}
514
515#[cfg(target_os = "android")]
517static ANDROID_INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
518
519#[cfg(target_os = "android")]
531pub fn init_android(env: &mut jni::Env, context: jni::objects::JObject) -> Result<()> {
532 rustls_platform_verifier::android::init_with_env(env, context).map_err(Error::AndroidInit)?;
533 ANDROID_INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
534 Ok(())
535}
536
537#[serde_with::serde_as]
546#[derive(clap::Args, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
547#[serde(deny_unknown_fields)]
548#[group(id = "tls-server")]
549#[non_exhaustive]
550pub struct Server {
551 #[arg(long = "tls-cert", id = "tls-cert", env = "MOQ_SERVER_TLS_CERT")]
553 #[serde(default, skip_serializing_if = "Vec::is_empty")]
554 #[serde_as(as = "serde_with::OneOrMany<_>")]
555 pub cert: Vec<PathBuf>,
556
557 #[arg(long = "tls-key", id = "tls-key", env = "MOQ_SERVER_TLS_KEY")]
559 #[serde(default, skip_serializing_if = "Vec::is_empty")]
560 #[serde_as(as = "serde_with::OneOrMany<_>")]
561 pub key: Vec<PathBuf>,
562
563 #[arg(
566 long = "tls-generate",
567 id = "tls-generate",
568 value_delimiter = ',',
569 env = "MOQ_SERVER_TLS_GENERATE"
570 )]
571 #[serde(default, skip_serializing_if = "Vec::is_empty")]
572 #[serde_as(as = "serde_with::OneOrMany<_>")]
573 pub generate: Vec<String>,
574
575 #[arg(
587 long = "server-tls-root",
588 id = "server-tls-root",
589 value_delimiter = ',',
590 env = "MOQ_SERVER_TLS_ROOT"
591 )]
592 #[serde(default, skip_serializing_if = "Vec::is_empty")]
593 #[serde_as(as = "serde_with::OneOrMany<_>")]
594 pub root: Vec<PathBuf>,
595}
596
597impl Server {
598 pub fn load_roots(&self) -> Result<rustls::RootCertStore> {
600 let mut roots = rustls::RootCertStore::empty();
601 for path in &self.root {
602 let certs = read_certs(path)?;
603 if certs.is_empty() {
604 return Err(Error::Empty);
605 }
606 for cert in certs {
607 roots.add(cert).map_err(Error::AddRoot)?;
608 }
609 }
610 Ok(roots)
611 }
612
613 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
622 pub fn server_config(&self, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
623 server_config(self, alpn)
624 }
625}
626
627#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
629fn server_config(config: &Server, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
630 let provider = crypto::provider();
631
632 let certs = ServeCerts::new(provider.clone());
633 certs.load_certs(config)?;
634 let certs = Arc::new(certs);
635
636 let builder =
638 rustls::ServerConfig::builder_with_provider(provider.clone()).with_safe_default_protocol_versions()?;
639
640 let mut tls = if config.root.is_empty() {
641 builder.with_no_client_auth().with_cert_resolver(certs)
642 } else {
643 let roots = config.load_roots()?;
644 let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider)
645 .allow_unauthenticated()
646 .build()
647 .map_err(Error::ClientVerifier)?;
648 builder.with_client_cert_verifier(verifier).with_cert_resolver(certs)
649 };
650
651 tls.alpn_protocols = alpn;
652 Ok(Arc::new(tls))
653}
654
655pub struct PeerIdentity {
662 chain: Vec<CertificateDer<'static>>,
663}
664
665impl PeerIdentity {
666 #[cfg(any(feature = "quinn", feature = "noq"))]
670 pub(crate) fn from_any(identity: Option<Box<dyn std::any::Any>>) -> Option<Self> {
671 let chain = identity?.downcast::<Vec<CertificateDer<'static>>>().ok()?;
672 Some(Self { chain: *chain })
673 }
674
675 pub fn chain(&self) -> &[CertificateDer<'static>] {
681 &self.chain
682 }
683
684 pub fn expiry(&self) -> Option<std::time::SystemTime> {
687 use std::time::{Duration, UNIX_EPOCH};
688
689 let leaf = self.chain.first()?;
690 let (_, cert) = x509_parser::parse_x509_certificate(leaf).ok()?;
691 let secs = u64::try_from(cert.validity().not_after.timestamp()).ok()?;
692 Some(UNIX_EPOCH + Duration::from_secs(secs))
693 }
694}
695
696#[derive(Debug)]
698pub struct Info {
699 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
700 pub(crate) certs: Vec<Arc<rustls::sign::CertifiedKey>>,
701 pub fingerprints: Vec<String>,
702}
703
704impl Info {
705 pub(crate) fn empty() -> Self {
707 Self {
708 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
709 certs: Vec::new(),
710 fingerprints: Vec::new(),
711 }
712 }
713}
714
715#[derive(Debug)]
718struct NoCertificateVerification(crypto::Provider);
719
720impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
721 fn verify_server_cert(
722 &self,
723 _end_entity: &CertificateDer<'_>,
724 _intermediates: &[CertificateDer<'_>],
725 _server_name: &ServerName<'_>,
726 _ocsp: &[u8],
727 _now: UnixTime,
728 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
729 Ok(rustls::client::danger::ServerCertVerified::assertion())
730 }
731
732 fn verify_tls12_signature(
733 &self,
734 message: &[u8],
735 cert: &CertificateDer<'_>,
736 dss: &rustls::DigitallySignedStruct,
737 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
738 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
739 }
740
741 fn verify_tls13_signature(
742 &self,
743 message: &[u8],
744 cert: &CertificateDer<'_>,
745 dss: &rustls::DigitallySignedStruct,
746 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
747 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
748 }
749
750 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
751 self.0.signature_verification_algorithms.supported_schemes()
752 }
753}
754
755#[derive(Debug)]
758pub(crate) struct FingerprintVerifier {
759 provider: crypto::Provider,
760 fingerprints: Vec<Vec<u8>>,
761}
762
763impl FingerprintVerifier {
764 pub fn new(provider: crypto::Provider, fingerprints: Vec<Vec<u8>>) -> Self {
765 Self { provider, fingerprints }
766 }
767}
768
769impl rustls::client::danger::ServerCertVerifier for FingerprintVerifier {
770 fn verify_server_cert(
771 &self,
772 end_entity: &CertificateDer<'_>,
773 _intermediates: &[CertificateDer<'_>],
774 _server_name: &ServerName<'_>,
775 _ocsp: &[u8],
776 _now: UnixTime,
777 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
778 let fingerprint = crypto::sha256(&self.provider, end_entity);
779 if self.fingerprints.iter().any(|fp| fingerprint.as_ref() == fp.as_slice()) {
780 Ok(rustls::client::danger::ServerCertVerified::assertion())
781 } else {
782 Err(rustls::Error::General("fingerprint mismatch".into()))
783 }
784 }
785
786 fn verify_tls12_signature(
787 &self,
788 message: &[u8],
789 cert: &CertificateDer<'_>,
790 dss: &rustls::DigitallySignedStruct,
791 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
792 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
793 }
794
795 fn verify_tls13_signature(
796 &self,
797 message: &[u8],
798 cert: &CertificateDer<'_>,
799 dss: &rustls::DigitallySignedStruct,
800 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
801 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
802 }
803
804 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
805 self.provider.signature_verification_algorithms.supported_schemes()
806 }
807}
808
809#[cfg(test)]
810#[cfg(all(any(feature = "quinn", feature = "noq", feature = "quiche"), feature = "aws-lc-rs"))]
811mod tests {
812 use super::*;
813 use rustls::client::danger::ServerCertVerifier;
814 use rustls::pki_types::ServerName;
815
816 fn self_signed() -> CertificateDer<'static> {
817 let key = rcgen::KeyPair::generate().unwrap();
818 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
819 params.self_signed(&key).unwrap().into()
820 }
821
822 #[cfg(any(feature = "quinn", feature = "noq"))]
823 #[test]
824 fn peer_identity_expiry_reads_not_after() {
825 let not_after = ::time::OffsetDateTime::from_unix_timestamp(2_000_000_000).unwrap();
827
828 let key = rcgen::KeyPair::generate().unwrap();
829 let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
830 params.not_after = not_after;
831 let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into();
832
833 let identity: Box<dyn std::any::Any> = Box::new(vec![cert]);
835 let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed");
836 let expiry = parsed.expiry().expect("expiry parsed");
837 assert_eq!(
838 expiry.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
839 2_000_000_000
840 );
841 }
842
843 #[cfg(any(feature = "quinn", feature = "noq"))]
844 #[test]
845 fn peer_identity_none_without_chain() {
846 assert!(PeerIdentity::from_any(None).is_none());
847 let bogus: Box<dyn std::any::Any> = Box::new(42u32);
849 assert!(PeerIdentity::from_any(Some(bogus)).is_none());
850 }
851
852 #[test]
853 fn fingerprint_verifier_matches_and_rejects() {
854 let provider = crypto::provider();
855 let cert = self_signed();
856 let fingerprint = crypto::sha256(&provider, cert.as_ref()).as_ref().to_vec();
857
858 let name = ServerName::try_from("localhost").unwrap();
859 let now = UnixTime::now();
860
861 let verifier = FingerprintVerifier::new(provider.clone(), vec![fingerprint]);
862 assert!(verifier.verify_server_cert(&cert, &[], &name, &[], now).is_ok());
863
864 let other = self_signed();
866 assert!(verifier.verify_server_cert(&other, &[], &name, &[], now).is_err());
867 }
868
869 #[test]
870 fn build_installs_fingerprint_verifier() {
871 let cert = self_signed();
872 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
873
874 let config = Client {
876 fingerprint: vec![fingerprint],
877 ..Default::default()
878 };
879 assert!(config.build().is_ok());
880 }
881
882 #[test]
883 fn build_rejects_invalid_fingerprint_hex() {
884 let config = Client {
885 fingerprint: vec!["not-hex".to_string()],
886 ..Default::default()
887 };
888 assert!(matches!(config.build(), Err(Error::Fingerprint(_))));
889 }
890
891 #[test]
892 fn build_rejects_wrong_length_fingerprint() {
893 let config = Client {
895 fingerprint: vec!["abcd".to_string()],
896 ..Default::default()
897 };
898 assert!(matches!(config.build(), Err(Error::FingerprintLength(2))));
899 }
900
901 #[test]
902 fn build_rejects_no_roots() {
903 let config = Client {
906 system_roots: Some(false),
907 ..Default::default()
908 };
909 assert!(matches!(config.build(), Err(Error::NoRoots)));
910 }
911
912 #[test]
913 fn build_allows_no_roots_when_verification_overridden() {
914 let config = Client {
916 system_roots: Some(false),
917 disable_verify: Some(true),
918 ..Default::default()
919 };
920 assert!(config.build().is_ok());
921
922 let cert = self_signed();
924 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
925 let config = Client {
926 system_roots: Some(false),
927 fingerprint: vec![fingerprint],
928 ..Default::default()
929 };
930 assert!(config.build().is_ok());
931 }
932
933 #[test]
934 fn build_rejects_fingerprint_with_roots() {
935 let cert = self_signed();
936 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
937
938 let with_system = Client {
941 fingerprint: vec![fingerprint.clone()],
942 system_roots: Some(true),
943 ..Default::default()
944 };
945 assert!(matches!(with_system.build(), Err(Error::FingerprintWithRoots)));
946
947 let with_custom = Client {
950 fingerprint: vec![fingerprint],
951 root: vec![PathBuf::from("/does-not-exist.pem")],
952 ..Default::default()
953 };
954 assert!(matches!(with_custom.build(), Err(Error::FingerprintWithRoots)));
955 }
956
957 fn self_signed_root() -> (tempfile::NamedTempFile, PathBuf) {
960 use std::io::Write;
961 let key = rcgen::KeyPair::generate().unwrap();
962 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
963 let cert = params.self_signed(&key).unwrap();
964 let mut file = tempfile::NamedTempFile::new().unwrap();
965 file.write_all(cert.pem().as_bytes()).unwrap();
966 let path = file.path().to_path_buf();
967 (file, path)
968 }
969
970 #[test]
971 fn build_uses_platform_verifier_by_default() {
972 assert!(Client::default().build().is_ok());
975 }
976
977 #[test]
978 fn build_with_custom_roots_only() {
979 let (_keep, path) = self_signed_root();
982 let config = Client {
983 root: vec![path],
984 ..Default::default()
985 };
986 assert!(config.build().is_ok());
987 }
988
989 #[test]
990 fn build_with_custom_and_system_roots() {
991 let (_keep, path) = self_signed_root();
994 let config = Client {
995 root: vec![path],
996 system_roots: Some(true),
997 ..Default::default()
998 };
999 assert!(config.build().is_ok());
1000 }
1001}
1002
1003#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1006#[derive(Debug)]
1007pub(crate) struct ServeCerts {
1008 pub info: Arc<RwLock<Info>>,
1009 provider: crypto::Provider,
1010}
1011
1012#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1013impl ServeCerts {
1014 pub fn new(provider: crypto::Provider) -> Self {
1015 Self {
1016 info: Arc::new(RwLock::new(Info {
1017 certs: Vec::new(),
1018 fingerprints: Vec::new(),
1019 })),
1020 provider,
1021 }
1022 }
1023
1024 pub fn load_certs(&self, config: &Server) -> Result<()> {
1025 if config.cert.len() != config.key.len() {
1026 return Err(Error::CertKeyCountMismatch);
1027 }
1028 if config.cert.is_empty() && config.generate.is_empty() {
1029 return Err(Error::NoCertSource);
1030 }
1031
1032 let mut certs = Vec::new();
1033
1034 for (cert, key) in config.cert.iter().zip(config.key.iter()) {
1036 certs.push(Arc::new(self.load(cert, key)?));
1037 }
1038
1039 if !config.generate.is_empty() {
1041 certs.push(Arc::new(self.generate(&config.generate)?));
1042 }
1043
1044 self.set_certs(certs);
1045 Ok(())
1046 }
1047
1048 fn load(&self, chain_path: &Path, key_path: &Path) -> Result<rustls::sign::CertifiedKey> {
1050 let chain = read_certs(chain_path)?;
1051 if chain.is_empty() {
1052 return Err(Error::Empty);
1053 }
1054
1055 let key = PrivateKeyDer::from_pem_file(key_path).map_err(Error::Key)?;
1057 let key = self.provider.key_provider.load_private_key(key)?;
1058
1059 let certified_key = rustls::sign::CertifiedKey::new(chain, key);
1060
1061 certified_key.keys_match().map_err(|source| Error::KeyMismatch {
1062 key: key_path.to_path_buf(),
1063 cert: chain_path.to_path_buf(),
1064 source,
1065 })?;
1066
1067 Ok(certified_key)
1068 }
1069
1070 #[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
1071 fn generate(&self, hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1072 let key_pair = rcgen::KeyPair::generate()?;
1073
1074 let mut params = rcgen::CertificateParams::new(hostnames)?;
1075
1076 params.not_before = ::time::OffsetDateTime::now_utc() - ::time::Duration::days(1);
1079 params.not_after = params.not_before + ::time::Duration::days(14);
1080
1081 let cert = params.self_signed(&key_pair)?;
1083
1084 let key_der = key_pair.serialized_der().to_vec();
1086 let key_der = PrivatePkcs8KeyDer::from(key_der);
1087 let key = self.provider.key_provider.load_private_key(key_der.into())?;
1088
1089 Ok(rustls::sign::CertifiedKey::new(vec![cert.into()], key))
1091 }
1092
1093 #[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
1094 fn generate(&self, _hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1095 Err(Error::NoCryptoProvider)
1096 }
1097
1098 pub fn set_certs(&self, certs: Vec<Arc<rustls::sign::CertifiedKey>>) {
1100 let fingerprints = certs
1101 .iter()
1102 .map(|ck| {
1103 let fingerprint = crate::crypto::sha256(&self.provider, ck.cert[0].as_ref());
1104 hex::encode(fingerprint)
1105 })
1106 .collect();
1107
1108 let mut info = self.info.write().expect("info write lock poisoned");
1109 info.certs = certs;
1110 info.fingerprints = fingerprints;
1111 }
1112
1113 fn best_certificate(
1115 &self,
1116 client_hello: &rustls::server::ClientHello<'_>,
1117 ) -> Option<Arc<rustls::sign::CertifiedKey>> {
1118 let server_name = client_hello.server_name()?;
1119 let dns_name = rustls::pki_types::ServerName::try_from(server_name).ok()?;
1120
1121 for ck in self.info.read().expect("info read lock poisoned").certs.iter() {
1122 let leaf: webpki::EndEntityCert = ck
1123 .end_entity_cert()
1124 .expect("missing certificate")
1125 .try_into()
1126 .expect("failed to parse certificate");
1127
1128 if leaf.verify_is_valid_for_subject_name(&dns_name).is_ok() {
1129 return Some(ck.clone());
1130 }
1131 }
1132
1133 None
1134 }
1135}
1136
1137#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1138impl rustls::server::ResolvesServerCert for ServeCerts {
1139 fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<rustls::sign::CertifiedKey>> {
1140 if let Some(cert) = self.best_certificate(&client_hello) {
1141 return Some(cert);
1142 }
1143
1144 tracing::warn!(server_name = ?client_hello.server_name(), "no SNI certificate found");
1147
1148 self.info
1149 .read()
1150 .expect("info read lock poisoned")
1151 .certs
1152 .first()
1153 .cloned()
1154 }
1155}
1156
1157#[cfg(any(feature = "quinn", feature = "noq"))]
1165pub(crate) async fn reload_certs(certs: Arc<ServeCerts>, tls_config: Server) {
1166 let paths: Vec<PathBuf> = tls_config.cert.iter().chain(tls_config.key.iter()).cloned().collect();
1167 if paths.is_empty() {
1168 return;
1169 }
1170
1171 let mut watcher = match crate::watch::FileWatcher::new(&paths) {
1172 Ok(watcher) => watcher,
1173 Err(err) => {
1174 tracing::error!(%err, "failed to watch certificate files; hot reload disabled");
1175 return;
1176 }
1177 };
1178
1179 loop {
1180 watcher.changed().await;
1181 tracing::info!("reloading server certificates");
1182
1183 if let Err(err) = certs.load_certs(&tls_config) {
1184 tracing::warn!(%err, "failed to reload server certificates");
1185 }
1186 }
1187}