1use crate::crypto;
12use rustls::pki_types::pem::PemObject;
13use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
14use std::path::{Path, PathBuf};
15use std::sync::Arc;
16use std::{fs, io};
17
18#[cfg(all(
19 any(feature = "quinn", feature = "noq", feature = "quiche"),
20 any(feature = "aws-lc-rs", feature = "ring")
21))]
22use rustls::pki_types::PrivatePkcs8KeyDer;
23#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
24use std::sync::RwLock;
25
26#[derive(Debug, thiserror::Error)]
31#[non_exhaustive]
32pub enum Error {
33 #[error("failed to open certificate file")]
35 Open(#[source] std::io::Error),
36
37 #[error("failed to read file")]
39 ReadFile(#[source] std::io::Error),
40
41 #[error("failed to read certificates")]
43 Read(#[source] rustls::pki_types::pem::Error),
44
45 #[error("failed to parse private key")]
47 Key(#[source] rustls::pki_types::pem::Error),
48
49 #[error("no certificates found")]
51 Empty,
52
53 #[error("no roots found in {}", .0.display())]
55 EmptyRoots(PathBuf),
56
57 #[error(
59 "no trusted roots: provide --client-tls-root, enable --client-tls-system-roots, or use --client-tls-fingerprint / --client-tls-disable-verify"
60 )]
61 NoRoots,
62
63 #[error("invalid TLS fingerprint (expected hex-encoded SHA-256)")]
65 Fingerprint(#[source] hex::FromHexError),
66
67 #[error("invalid TLS fingerprint length: expected 32 bytes (SHA-256), got {0}")]
69 FingerprintLength(usize),
70
71 #[error(
74 "--client-tls-fingerprint cannot be combined with --client-tls-root or --client-tls-system-roots: fingerprint pinning bypasses CA verification"
75 )]
76 FingerprintWithRoots,
77
78 #[error(
80 "--client-tls-disable-verify cannot be combined with --client-tls-fingerprint, --client-tls-root or --client-tls-system-roots: it accepts every certificate, so the trust material would be ignored"
81 )]
82 DisableVerifyWithTrust,
83
84 #[error("failed to add root certificate")]
86 AddRoot(#[source] rustls::Error),
87
88 #[cfg(target_os = "android")]
90 #[error("failed to initialize the Android platform verifier")]
91 AndroidInit(#[source] jni::errors::Error),
92
93 #[error("failed to configure client certificate")]
95 ClientAuth(#[source] rustls::Error),
96
97 #[error("both --client-tls-cert and --client-tls-key must be provided")]
99 IncompleteClientAuth,
100
101 #[error("must provide both cert and key")]
103 CertKeyCountMismatch,
104
105 #[error("must provide at least one cert/key pair or generate entry")]
107 NoCertSource,
108
109 #[error("private key {} doesn't match certificate {}", key.display(), cert.display())]
111 KeyMismatch {
112 key: PathBuf,
114 cert: PathBuf,
116 #[source]
118 source: rustls::Error,
119 },
120
121 #[error(transparent)]
123 Rustls(#[from] rustls::Error),
124
125 #[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
127 #[error("failed to build client certificate verifier")]
128 ClientVerifier(#[source] rustls::server::VerifierBuilderError),
129
130 #[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
132 #[error(transparent)]
133 Rcgen(#[from] rcgen::Error),
134
135 #[error("no crypto provider available; enable aws-lc-rs or ring feature")]
137 NoCryptoProvider,
138}
139
140pub type Result<T> = std::result::Result<T, Error>;
142
143pub fn parse_fingerprint(value: &str) -> Result<[u8; 32]> {
145 let bytes = hex::decode(value.trim()).map_err(Error::Fingerprint)?;
146 bytes.try_into().map_err(|v: Vec<u8>| Error::FingerprintLength(v.len()))
147}
148
149pub(crate) fn read_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
151 let file = fs::File::open(path).map_err(Error::Open)?;
152 let mut reader = io::BufReader::new(file);
153 CertificateDer::pem_reader_iter(&mut reader)
154 .collect::<std::result::Result<_, _>>()
155 .map_err(Error::Read)
156}
157
158#[serde_with::serde_as]
162#[derive(Clone, Default, Debug, clap::Args, serde::Serialize, serde::Deserialize)]
163#[serde(default, deny_unknown_fields)]
164#[group(id = "tls-client")]
165#[non_exhaustive]
166pub struct Client {
167 #[serde(skip_serializing_if = "Vec::is_empty")]
177 #[arg(id = "client-tls-root", long = "client-tls-root", env = "MOQ_CLIENT_TLS_ROOT")]
178 #[serde_as(as = "serde_with::OneOrMany<_>")]
179 pub root: Vec<PathBuf>,
180
181 #[serde(skip_serializing_if = "Option::is_none")]
188 #[arg(
189 id = "client-tls-system-roots",
190 long = "client-tls-system-roots",
191 env = "MOQ_CLIENT_TLS_SYSTEM_ROOTS",
192 default_missing_value = "true",
193 num_args = 0..=1,
194 require_equals = true,
195 value_parser = clap::value_parser!(bool),
196 )]
197 pub system_roots: Option<bool>,
198
199 #[serde(skip_serializing_if = "Vec::is_empty")]
210 #[arg(
211 id = "client-tls-fingerprint",
212 long = "client-tls-fingerprint",
213 env = "MOQ_CLIENT_TLS_FINGERPRINT"
214 )]
215 #[serde_as(as = "serde_with::OneOrMany<_>")]
216 pub fingerprint: Vec<String>,
217
218 #[serde(skip_serializing_if = "Option::is_none")]
223 #[arg(id = "client-tls-cert", long = "client-tls-cert", env = "MOQ_CLIENT_TLS_CERT")]
224 pub cert: Option<PathBuf>,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
231 #[arg(id = "client-tls-key", long = "client-tls-key", env = "MOQ_CLIENT_TLS_KEY")]
232 pub key: Option<PathBuf>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
238 #[arg(
239 id = "client-tls-disable-verify",
240 long = "client-tls-disable-verify",
241 env = "MOQ_CLIENT_TLS_DISABLE_VERIFY",
242 default_missing_value = "true",
243 num_args = 0..=1,
244 require_equals = true,
245 value_parser = clap::value_parser!(bool),
246 )]
247 pub disable_verify: Option<bool>,
248
249 #[serde(skip_serializing_if = "Option::is_none")]
254 #[arg(
255 id = "client-tls-host-name",
256 long = "client-tls-host-name",
257 env = "MOQ_CLIENT_TLS_HOST_NAME"
258 )]
259 pub host_name: Option<String>,
260
261 #[command(flatten)]
265 #[serde(skip)]
266 deprecated: Deprecated,
267}
268
269#[derive(Clone, Default, Debug, clap::Args)]
274struct Deprecated {
275 #[arg(long = "tls-root", hide = true)]
276 root: Vec<PathBuf>,
277
278 #[arg(
279 long = "tls-system-roots",
280 hide = true,
281 default_missing_value = "true",
282 num_args = 0..=1,
283 require_equals = true,
284 value_parser = clap::value_parser!(bool),
285 )]
286 system_roots: Option<bool>,
287
288 #[arg(long = "tls-fingerprint", hide = true)]
289 fingerprint: Vec<String>,
290
291 #[arg(
292 long = "tls-disable-verify",
293 hide = true,
294 default_missing_value = "true",
295 num_args = 0..=1,
296 require_equals = true,
297 value_parser = clap::value_parser!(bool),
298 )]
299 disable_verify: Option<bool>,
300}
301
302#[derive(Clone)]
309pub(crate) enum Verification {
310 Disabled,
312
313 Fingerprints(Vec<[u8; 32]>),
316
317 Roots {
322 custom: Vec<CertificateDer<'static>>,
323 system: bool,
324 },
325}
326
327impl Client {
328 pub(crate) fn warn_deprecated(&self) {
331 if !self.deprecated.root.is_empty() {
332 tracing::warn!("--tls-root is deprecated; use --client-tls-root");
333 }
334 if self.deprecated.system_roots.is_some() {
335 tracing::warn!("--tls-system-roots is deprecated; use --client-tls-system-roots");
336 }
337 if !self.deprecated.fingerprint.is_empty() {
338 tracing::warn!("--tls-fingerprint is deprecated; use --client-tls-fingerprint");
339 }
340 if self.deprecated.disable_verify.is_some() {
341 tracing::warn!("--tls-disable-verify is deprecated; use --client-tls-disable-verify");
342 }
343 }
344
345 pub(crate) fn effective_root(&self) -> Vec<PathBuf> {
347 let mut root = self.root.clone();
348 root.extend(self.deprecated.root.iter().cloned());
349 root
350 }
351
352 pub(crate) fn effective_fingerprint(&self) -> Vec<String> {
354 let mut fp = self.fingerprint.clone();
355 fp.extend(self.deprecated.fingerprint.iter().cloned());
356 fp
357 }
358
359 pub(crate) fn effective_system_roots(&self) -> Option<bool> {
361 self.system_roots.or(self.deprecated.system_roots)
362 }
363
364 pub(crate) fn effective_disable_verify(&self) -> Option<bool> {
366 self.disable_verify.or(self.deprecated.disable_verify)
367 }
368
369 pub(crate) fn verification(&self) -> Result<Verification> {
386 self.warn_deprecated();
387
388 let fingerprints = self.fingerprints()?;
389 let roots = self.effective_root();
390 let system_roots = self.effective_system_roots();
391
392 if self.effective_disable_verify().unwrap_or_default() {
393 if !fingerprints.is_empty() || !roots.is_empty() || system_roots == Some(true) {
394 return Err(Error::DisableVerifyWithTrust);
395 }
396 return Ok(Verification::Disabled);
397 }
398
399 if !fingerprints.is_empty() {
400 if !roots.is_empty() || system_roots == Some(true) {
401 return Err(Error::FingerprintWithRoots);
402 }
403 return Ok(Verification::Fingerprints(fingerprints));
404 }
405
406 let system = system_roots.unwrap_or(roots.is_empty());
409
410 let mut custom = Vec::new();
411 for root in &roots {
412 let certs = read_certs(root)?;
413 if certs.is_empty() {
414 return Err(Error::EmptyRoots(root.clone()));
415 }
416 custom.extend(certs);
417 }
418
419 if !system && custom.is_empty() {
424 return Err(Error::NoRoots);
425 }
426
427 Ok(Verification::Roots { custom, system })
428 }
429
430 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
439 pub(crate) fn allows_http_bootstrap(&self) -> bool {
440 self.effective_fingerprint().is_empty() && !self.effective_disable_verify().unwrap_or_default()
441 }
442
443 fn fingerprints(&self) -> Result<Vec<[u8; 32]>> {
445 self.effective_fingerprint()
446 .iter()
447 .map(|fp| parse_fingerprint(fp))
448 .collect()
449 }
450
451 pub fn build(&self) -> Result<rustls::ClientConfig> {
456 let provider = crypto::provider();
457 let verification = self.verification()?;
458
459 let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
462 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])?;
463
464 let builder = match &verification {
467 Verification::Roots { custom, system: true } => Self::system_verifier(builder, custom, &provider)?,
468 Verification::Roots { custom, system: false } => builder.with_root_certificates(root_store(custom)?),
469 Verification::Disabled | Verification::Fingerprints(_) => {
470 builder.with_root_certificates(rustls::RootCertStore::empty())
471 }
472 };
473
474 let mut tls = self.with_client_auth(builder)?;
475
476 match verification {
477 Verification::Disabled => {
478 tracing::warn!(
479 "TLS server certificate verification is disabled; A man-in-the-middle attack is possible."
480 );
481 tls.dangerous()
482 .set_certificate_verifier(Arc::new(NoCertificateVerification(provider)));
483 }
484 Verification::Fingerprints(fingerprints) => {
485 let fingerprints = fingerprints.into_iter().map(|fp| fp.to_vec()).collect();
486 let verifier = FingerprintVerifier::new(provider, fingerprints);
487 tls.dangerous().set_certificate_verifier(Arc::new(verifier));
488 }
489 Verification::Roots { .. } => {}
491 }
492
493 Ok(tls)
494 }
495
496 fn system_verifier(
504 builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>,
505 custom: &[CertificateDer<'static>],
506 provider: &crypto::Provider,
507 ) -> Result<rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>> {
508 #[cfg(target_os = "android")]
513 {
514 if ANDROID_INITIALIZED.load(std::sync::atomic::Ordering::Acquire) && custom.is_empty() {
515 let verifier = rustls_platform_verifier::Verifier::new(provider.clone())?;
516 return Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)));
517 }
518
519 let mut roots = rustls::RootCertStore::empty();
520 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
521 for cert in custom {
522 roots.add(cert.clone()).map_err(Error::AddRoot)?;
523 }
524 Ok(builder.with_root_certificates(roots))
525 }
526
527 #[cfg(not(target_os = "android"))]
528 {
529 let verifier = if custom.is_empty() {
530 rustls_platform_verifier::Verifier::new(provider.clone())?
531 } else {
532 rustls_platform_verifier::Verifier::new_with_extra_roots(custom.iter().cloned(), provider.clone())?
533 };
534 Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)))
535 }
536 }
537
538 fn with_client_auth(
540 &self,
541 builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>,
542 ) -> Result<rustls::ClientConfig> {
543 Ok(match (&self.cert, &self.key) {
544 (Some(cert_path), Some(key_path)) => {
545 let cert_pem = fs::read(cert_path).map_err(Error::ReadFile)?;
546 let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_pem)
547 .collect::<std::result::Result<_, _>>()
548 .map_err(Error::Read)?;
549 if chain.is_empty() {
550 return Err(Error::Empty);
551 }
552 let key_pem = fs::read(key_path).map_err(Error::ReadFile)?;
553 let key = PrivateKeyDer::from_pem_slice(&key_pem).map_err(Error::Key)?;
554 builder.with_client_auth_cert(chain, key).map_err(Error::ClientAuth)?
555 }
556 (None, None) => builder.with_no_client_auth(),
557 _ => return Err(Error::IncompleteClientAuth),
558 })
559 }
560}
561
562fn root_store(custom: &[CertificateDer<'static>]) -> Result<rustls::RootCertStore> {
564 let mut roots = rustls::RootCertStore::empty();
565 for cert in custom {
566 roots.add(cert.clone()).map_err(Error::AddRoot)?;
567 }
568 Ok(roots)
569}
570
571#[cfg(target_os = "android")]
573static ANDROID_INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
574
575#[cfg(target_os = "android")]
587pub fn init_android(env: &mut jni::Env, context: jni::objects::JObject) -> Result<()> {
588 rustls_platform_verifier::android::init_with_env(env, context).map_err(Error::AndroidInit)?;
589 ANDROID_INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
590 Ok(())
591}
592
593#[serde_with::serde_as]
602#[derive(clap::Args, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
603#[serde(deny_unknown_fields)]
604#[group(id = "tls-server")]
605#[non_exhaustive]
606pub struct Server {
607 #[arg(long = "tls-cert", id = "tls-cert", env = "MOQ_SERVER_TLS_CERT")]
609 #[serde(default, skip_serializing_if = "Vec::is_empty")]
610 #[serde_as(as = "serde_with::OneOrMany<_>")]
611 pub cert: Vec<PathBuf>,
612
613 #[arg(long = "tls-key", id = "tls-key", env = "MOQ_SERVER_TLS_KEY")]
615 #[serde(default, skip_serializing_if = "Vec::is_empty")]
616 #[serde_as(as = "serde_with::OneOrMany<_>")]
617 pub key: Vec<PathBuf>,
618
619 #[arg(
622 long = "tls-generate",
623 id = "tls-generate",
624 value_delimiter = ',',
625 env = "MOQ_SERVER_TLS_GENERATE"
626 )]
627 #[serde(default, skip_serializing_if = "Vec::is_empty")]
628 #[serde_as(as = "serde_with::OneOrMany<_>")]
629 pub generate: Vec<String>,
630
631 #[arg(
641 long = "server-tls-root",
642 id = "server-tls-root",
643 value_delimiter = ',',
644 env = "MOQ_SERVER_TLS_ROOT"
645 )]
646 #[serde(default, skip_serializing_if = "Vec::is_empty")]
647 #[serde_as(as = "serde_with::OneOrMany<_>")]
648 pub root: Vec<PathBuf>,
649}
650
651impl Server {
652 pub fn load_roots(&self) -> Result<rustls::RootCertStore> {
654 let mut roots = rustls::RootCertStore::empty();
655 for path in &self.root {
656 let certs = read_certs(path)?;
657 if certs.is_empty() {
658 return Err(Error::Empty);
659 }
660 for cert in certs {
661 roots.add(cert).map_err(Error::AddRoot)?;
662 }
663 }
664 Ok(roots)
665 }
666
667 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
676 pub fn server_config(&self, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
677 server_config(self, alpn)
678 }
679}
680
681#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
683fn server_config(config: &Server, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
684 let provider = crypto::provider();
685
686 let certs = ServeCerts::new(provider.clone());
687 certs.load_certs(config)?;
688 let certs = Arc::new(certs);
689
690 let builder =
692 rustls::ServerConfig::builder_with_provider(provider.clone()).with_safe_default_protocol_versions()?;
693
694 let mut tls = if config.root.is_empty() {
695 builder.with_no_client_auth().with_cert_resolver(certs)
696 } else {
697 let roots = config.load_roots()?;
698 let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider)
699 .allow_unauthenticated()
700 .build()
701 .map_err(Error::ClientVerifier)?;
702 builder.with_client_cert_verifier(verifier).with_cert_resolver(certs)
703 };
704
705 tls.alpn_protocols = alpn;
706 Ok(Arc::new(tls))
707}
708
709#[derive(Clone)]
716pub struct PeerIdentity {
717 chain: Vec<CertificateDer<'static>>,
718}
719
720impl PeerIdentity {
721 #[cfg(any(feature = "quinn", feature = "noq"))]
725 pub(crate) fn from_any(identity: Option<Box<dyn std::any::Any>>) -> Option<Self> {
726 let chain = identity?.downcast::<Vec<CertificateDer<'static>>>().ok()?;
727 Some(Self { chain: *chain })
728 }
729
730 #[cfg(feature = "quiche")]
732 pub(crate) fn from_chain(chain: Vec<CertificateDer<'static>>) -> Self {
733 Self { chain }
734 }
735
736 pub fn chain(&self) -> &[CertificateDer<'static>] {
742 &self.chain
743 }
744
745 pub fn expiry(&self) -> Option<std::time::SystemTime> {
748 use std::time::{Duration, UNIX_EPOCH};
749
750 let leaf = self.chain.first()?;
751 let (_, cert) = x509_parser::parse_x509_certificate(leaf).ok()?;
752 let secs = u64::try_from(cert.validity().not_after.timestamp()).ok()?;
753 Some(UNIX_EPOCH + Duration::from_secs(secs))
754 }
755}
756
757#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
761#[derive(Debug, Default)]
762pub(crate) struct Info {
763 pub(crate) certs: Vec<Arc<rustls::sign::CertifiedKey>>,
764 pub(crate) fingerprints: Vec<String>,
765}
766
767#[derive(Clone, Debug)]
773pub struct Certificates {
774 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
775 info: Arc<RwLock<Info>>,
776}
777
778impl Certificates {
779 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
780 pub(crate) fn new(info: Arc<RwLock<Info>>) -> Self {
781 Self { info }
782 }
783
784 pub(crate) fn empty() -> Self {
786 Self {
787 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
788 info: Arc::new(RwLock::new(Info::default())),
789 }
790 }
791
792 pub fn fingerprints(&self) -> Vec<String> {
798 #[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
799 {
800 let info = self.info.read().unwrap_or_else(std::sync::PoisonError::into_inner);
803 info.fingerprints.clone()
804 }
805 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
806 Vec::new()
807 }
808}
809
810#[derive(Debug)]
813struct NoCertificateVerification(crypto::Provider);
814
815impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
816 fn verify_server_cert(
817 &self,
818 _end_entity: &CertificateDer<'_>,
819 _intermediates: &[CertificateDer<'_>],
820 _server_name: &ServerName<'_>,
821 _ocsp: &[u8],
822 _now: UnixTime,
823 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
824 Ok(rustls::client::danger::ServerCertVerified::assertion())
825 }
826
827 fn verify_tls12_signature(
828 &self,
829 message: &[u8],
830 cert: &CertificateDer<'_>,
831 dss: &rustls::DigitallySignedStruct,
832 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
833 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
834 }
835
836 fn verify_tls13_signature(
837 &self,
838 message: &[u8],
839 cert: &CertificateDer<'_>,
840 dss: &rustls::DigitallySignedStruct,
841 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
842 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
843 }
844
845 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
846 self.0.signature_verification_algorithms.supported_schemes()
847 }
848}
849
850#[derive(Debug)]
853pub(crate) struct FingerprintVerifier {
854 provider: crypto::Provider,
855 fingerprints: Vec<Vec<u8>>,
856}
857
858impl FingerprintVerifier {
859 pub fn new(provider: crypto::Provider, fingerprints: Vec<Vec<u8>>) -> Self {
860 Self { provider, fingerprints }
861 }
862}
863
864impl rustls::client::danger::ServerCertVerifier for FingerprintVerifier {
865 fn verify_server_cert(
866 &self,
867 end_entity: &CertificateDer<'_>,
868 _intermediates: &[CertificateDer<'_>],
869 _server_name: &ServerName<'_>,
870 _ocsp: &[u8],
871 _now: UnixTime,
872 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
873 let fingerprint = crypto::sha256(&self.provider, end_entity);
874 if self.fingerprints.iter().any(|fp| fingerprint.as_ref() == fp.as_slice()) {
875 Ok(rustls::client::danger::ServerCertVerified::assertion())
876 } else {
877 Err(rustls::Error::General("fingerprint mismatch".into()))
878 }
879 }
880
881 fn verify_tls12_signature(
882 &self,
883 message: &[u8],
884 cert: &CertificateDer<'_>,
885 dss: &rustls::DigitallySignedStruct,
886 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
887 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
888 }
889
890 fn verify_tls13_signature(
891 &self,
892 message: &[u8],
893 cert: &CertificateDer<'_>,
894 dss: &rustls::DigitallySignedStruct,
895 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
896 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
897 }
898
899 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
900 self.provider.signature_verification_algorithms.supported_schemes()
901 }
902}
903
904#[cfg(test)]
905#[cfg(all(any(feature = "quinn", feature = "noq", feature = "quiche"), feature = "aws-lc-rs"))]
906mod tests {
907 #[test]
910 fn disable_verify_rejects_trust_material() {
911 let insecure = Client {
912 disable_verify: Some(true),
913 ..Default::default()
914 };
915 assert!(matches!(insecure.verification(), Ok(Verification::Disabled)));
916
917 let with_fingerprint = Client {
918 disable_verify: Some(true),
919 fingerprint: vec!["ab".repeat(32)],
920 ..Default::default()
921 };
922 assert!(matches!(
923 with_fingerprint.verification(),
924 Err(Error::DisableVerifyWithTrust)
925 ));
926
927 let with_root = Client {
928 disable_verify: Some(true),
929 root: vec!["/tmp/root.pem".into()],
930 ..Default::default()
931 };
932 assert!(matches!(with_root.verification(), Err(Error::DisableVerifyWithTrust)));
933
934 let with_system_roots = Client {
935 disable_verify: Some(true),
936 system_roots: Some(true),
937 ..Default::default()
938 };
939 assert!(matches!(
940 with_system_roots.verification(),
941 Err(Error::DisableVerifyWithTrust)
942 ));
943
944 let without_system_roots = Client {
945 disable_verify: Some(true),
946 system_roots: Some(false),
947 ..Default::default()
948 };
949 assert!(matches!(
950 without_system_roots.verification(),
951 Ok(Verification::Disabled)
952 ));
953 }
954
955 use super::*;
956 use rustls::client::danger::ServerCertVerifier;
957 use rustls::pki_types::ServerName;
958
959 fn self_signed() -> CertificateDer<'static> {
960 let key = rcgen::KeyPair::generate().unwrap();
961 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
962 params.self_signed(&key).unwrap().into()
963 }
964
965 #[cfg(any(feature = "quinn", feature = "noq"))]
966 #[test]
967 fn peer_identity_expiry_reads_not_after() {
968 let not_after = ::time::OffsetDateTime::from_unix_timestamp(2_000_000_000).unwrap();
970
971 let key = rcgen::KeyPair::generate().unwrap();
972 let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
973 params.not_after = not_after;
974 let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into();
975
976 let identity: Box<dyn std::any::Any> = Box::new(vec![cert]);
978 let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed");
979 let expiry = parsed.expiry().expect("expiry parsed");
980 assert_eq!(
981 expiry.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
982 2_000_000_000
983 );
984 }
985
986 #[cfg(any(feature = "quinn", feature = "noq"))]
987 #[test]
988 fn peer_identity_none_without_chain() {
989 assert!(PeerIdentity::from_any(None).is_none());
990 let bogus: Box<dyn std::any::Any> = Box::new(42u32);
992 assert!(PeerIdentity::from_any(Some(bogus)).is_none());
993 }
994
995 #[test]
996 fn fingerprint_verifier_matches_and_rejects() {
997 let provider = crypto::provider();
998 let cert = self_signed();
999 let fingerprint = crypto::sha256(&provider, cert.as_ref()).as_ref().to_vec();
1000
1001 let name = ServerName::try_from("localhost").unwrap();
1002 let now = UnixTime::now();
1003
1004 let verifier = FingerprintVerifier::new(provider.clone(), vec![fingerprint]);
1005 assert!(verifier.verify_server_cert(&cert, &[], &name, &[], now).is_ok());
1006
1007 let other = self_signed();
1009 assert!(verifier.verify_server_cert(&other, &[], &name, &[], now).is_err());
1010 }
1011
1012 #[test]
1013 fn build_installs_fingerprint_verifier() {
1014 let cert = self_signed();
1015 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1016
1017 let config = Client {
1019 fingerprint: vec![fingerprint],
1020 ..Default::default()
1021 };
1022 assert!(config.build().is_ok());
1023 }
1024
1025 #[test]
1026 fn build_rejects_invalid_fingerprint_hex() {
1027 let config = Client {
1028 fingerprint: vec!["not-hex".to_string()],
1029 ..Default::default()
1030 };
1031 assert!(matches!(config.build(), Err(Error::Fingerprint(_))));
1032 }
1033
1034 #[test]
1035 fn build_rejects_wrong_length_fingerprint() {
1036 let config = Client {
1038 fingerprint: vec!["abcd".to_string()],
1039 ..Default::default()
1040 };
1041 assert!(matches!(config.build(), Err(Error::FingerprintLength(2))));
1042 }
1043
1044 #[test]
1045 fn build_rejects_no_roots() {
1046 let config = Client {
1049 system_roots: Some(false),
1050 ..Default::default()
1051 };
1052 assert!(matches!(config.build(), Err(Error::NoRoots)));
1053 }
1054
1055 #[test]
1056 fn build_allows_no_roots_when_verification_overridden() {
1057 let config = Client {
1059 system_roots: Some(false),
1060 disable_verify: Some(true),
1061 ..Default::default()
1062 };
1063 assert!(config.build().is_ok());
1064
1065 let cert = self_signed();
1067 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1068 let config = Client {
1069 system_roots: Some(false),
1070 fingerprint: vec![fingerprint],
1071 ..Default::default()
1072 };
1073 assert!(config.build().is_ok());
1074 }
1075
1076 #[test]
1077 fn build_rejects_fingerprint_with_roots() {
1078 let cert = self_signed();
1079 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1080
1081 let with_system = Client {
1084 fingerprint: vec![fingerprint.clone()],
1085 system_roots: Some(true),
1086 ..Default::default()
1087 };
1088 assert!(matches!(with_system.build(), Err(Error::FingerprintWithRoots)));
1089
1090 let with_custom = Client {
1093 fingerprint: vec![fingerprint],
1094 root: vec![PathBuf::from("/does-not-exist.pem")],
1095 ..Default::default()
1096 };
1097 assert!(matches!(with_custom.build(), Err(Error::FingerprintWithRoots)));
1098 }
1099
1100 fn self_signed_root() -> (tempfile::NamedTempFile, PathBuf) {
1103 use std::io::Write;
1104 let key = rcgen::KeyPair::generate().unwrap();
1105 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
1106 let cert = params.self_signed(&key).unwrap();
1107 let mut file = tempfile::NamedTempFile::new().unwrap();
1108 file.write_all(cert.pem().as_bytes()).unwrap();
1109 let path = file.path().to_path_buf();
1110 (file, path)
1111 }
1112
1113 #[test]
1114 fn build_uses_platform_verifier_by_default() {
1115 assert!(Client::default().build().is_ok());
1118 }
1119
1120 #[test]
1121 fn build_with_custom_roots_only() {
1122 let (_keep, path) = self_signed_root();
1125 let config = Client {
1126 root: vec![path],
1127 ..Default::default()
1128 };
1129 assert!(config.build().is_ok());
1130 }
1131
1132 #[test]
1133 fn build_with_custom_and_system_roots() {
1134 let (_keep, path) = self_signed_root();
1137 let config = Client {
1138 root: vec![path],
1139 system_roots: Some(true),
1140 ..Default::default()
1141 };
1142 assert!(config.build().is_ok());
1143 }
1144}
1145
1146#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1149#[derive(Debug)]
1150pub(crate) struct ServeCerts {
1151 pub info: Arc<RwLock<Info>>,
1152 provider: crypto::Provider,
1153}
1154
1155#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1156impl ServeCerts {
1157 pub fn new(provider: crypto::Provider) -> Self {
1158 Self {
1159 info: Arc::new(RwLock::new(Info::default())),
1160 provider,
1161 }
1162 }
1163
1164 pub fn load_certs(&self, config: &Server) -> Result<()> {
1165 if config.cert.len() != config.key.len() {
1166 return Err(Error::CertKeyCountMismatch);
1167 }
1168 if config.cert.is_empty() && config.generate.is_empty() {
1169 return Err(Error::NoCertSource);
1170 }
1171
1172 let mut certs = Vec::new();
1173
1174 for (cert, key) in config.cert.iter().zip(config.key.iter()) {
1176 certs.push(Arc::new(self.load(cert, key)?));
1177 }
1178
1179 if !config.generate.is_empty() {
1181 certs.push(Arc::new(self.generate(&config.generate)?));
1182 }
1183
1184 self.set_certs(certs);
1185 Ok(())
1186 }
1187
1188 fn load(&self, chain_path: &Path, key_path: &Path) -> Result<rustls::sign::CertifiedKey> {
1190 let chain = read_certs(chain_path)?;
1191 if chain.is_empty() {
1192 return Err(Error::Empty);
1193 }
1194
1195 let key = PrivateKeyDer::from_pem_file(key_path).map_err(Error::Key)?;
1197 let key = self.provider.key_provider.load_private_key(key)?;
1198
1199 let certified_key = rustls::sign::CertifiedKey::new(chain, key);
1200
1201 certified_key.keys_match().map_err(|source| Error::KeyMismatch {
1202 key: key_path.to_path_buf(),
1203 cert: chain_path.to_path_buf(),
1204 source,
1205 })?;
1206
1207 Ok(certified_key)
1208 }
1209
1210 #[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
1211 fn generate(&self, hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1212 let key_pair = rcgen::KeyPair::generate()?;
1213
1214 let mut params = rcgen::CertificateParams::new(hostnames)?;
1215
1216 params.not_before = ::time::OffsetDateTime::now_utc() - ::time::Duration::days(1);
1219 params.not_after = params.not_before + ::time::Duration::days(14);
1220
1221 let cert = params.self_signed(&key_pair)?;
1223
1224 let key_der = key_pair.serialized_der().to_vec();
1226 let key_der = PrivatePkcs8KeyDer::from(key_der);
1227 let key = self.provider.key_provider.load_private_key(key_der.into())?;
1228
1229 Ok(rustls::sign::CertifiedKey::new(vec![cert.into()], key))
1231 }
1232
1233 #[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
1234 fn generate(&self, _hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1235 Err(Error::NoCryptoProvider)
1236 }
1237
1238 pub fn set_certs(&self, certs: Vec<Arc<rustls::sign::CertifiedKey>>) {
1240 let fingerprints = certs
1241 .iter()
1242 .map(|ck| {
1243 let fingerprint = crate::crypto::sha256(&self.provider, ck.cert[0].as_ref());
1244 hex::encode(fingerprint)
1245 })
1246 .collect();
1247
1248 let mut info = self.info.write().expect("info write lock poisoned");
1249 info.certs = certs;
1250 info.fingerprints = fingerprints;
1251 }
1252
1253 fn best_certificate(
1255 &self,
1256 client_hello: &rustls::server::ClientHello<'_>,
1257 ) -> Option<Arc<rustls::sign::CertifiedKey>> {
1258 let server_name = client_hello.server_name()?;
1259 let dns_name = rustls::pki_types::ServerName::try_from(server_name).ok()?;
1260
1261 for ck in self.info.read().expect("info read lock poisoned").certs.iter() {
1262 let leaf: webpki::EndEntityCert = ck
1263 .end_entity_cert()
1264 .expect("missing certificate")
1265 .try_into()
1266 .expect("failed to parse certificate");
1267
1268 if leaf.verify_is_valid_for_subject_name(&dns_name).is_ok() {
1269 return Some(ck.clone());
1270 }
1271 }
1272
1273 None
1274 }
1275}
1276
1277#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1278impl rustls::server::ResolvesServerCert for ServeCerts {
1279 fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<rustls::sign::CertifiedKey>> {
1280 if let Some(cert) = self.best_certificate(&client_hello) {
1281 return Some(cert);
1282 }
1283
1284 tracing::warn!(server_name = ?client_hello.server_name(), "no SNI certificate found");
1287
1288 self.info
1289 .read()
1290 .expect("info read lock poisoned")
1291 .certs
1292 .first()
1293 .cloned()
1294 }
1295}
1296
1297#[cfg(any(feature = "quinn", feature = "noq"))]
1305pub(crate) async fn reload_certs(certs: Arc<ServeCerts>, tls_config: Server) {
1306 let paths: Vec<PathBuf> = tls_config.cert.iter().chain(tls_config.key.iter()).cloned().collect();
1307 if paths.is_empty() {
1308 return;
1309 }
1310
1311 let mut watcher = match crate::watch::FileWatcher::new(&paths) {
1312 Ok(watcher) => watcher,
1313 Err(err) => {
1314 tracing::error!(%err, "failed to watch certificate files; hot reload disabled");
1315 return;
1316 }
1317 };
1318
1319 loop {
1320 watcher.changed().await;
1321 tracing::info!("reloading server certificates");
1322
1323 if let Err(err) = certs.load_certs(&tls_config) {
1324 tracing::warn!(%err, "failed to reload server certificates");
1325 }
1326 }
1327}