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(any(feature = "quinn", feature = "noq"))]
9use rustls::pki_types::PrivatePkcs8KeyDer;
10#[cfg(any(feature = "quinn", feature = "noq"))]
11use std::sync::RwLock;
12
13#[derive(Debug, thiserror::Error)]
18#[non_exhaustive]
19pub enum Error {
20 #[error("failed to open certificate file")]
21 Open(#[source] std::io::Error),
22
23 #[error("failed to read file")]
24 ReadFile(#[source] std::io::Error),
25
26 #[error("failed to read certificates")]
27 Read(#[source] rustls::pki_types::pem::Error),
28
29 #[error("failed to parse private key")]
30 Key(#[source] rustls::pki_types::pem::Error),
31
32 #[error("no certificates found")]
33 Empty,
34
35 #[error("no roots found in {}", .0.display())]
36 EmptyRoots(PathBuf),
37
38 #[error(
39 "no trusted roots: provide --tls-root, enable --tls-system-roots, or use --tls-fingerprint / --tls-disable-verify"
40 )]
41 NoRoots,
42
43 #[error("invalid TLS fingerprint (expected hex-encoded SHA-256)")]
44 Fingerprint(#[source] hex::FromHexError),
45
46 #[error("invalid TLS fingerprint length: expected 32 bytes (SHA-256), got {0}")]
47 FingerprintLength(usize),
48
49 #[error("failed to add root certificate")]
50 AddRoot(#[source] rustls::Error),
51
52 #[error("failed to configure client certificate")]
53 ClientAuth(#[source] rustls::Error),
54
55 #[error("both --client-tls-cert and --client-tls-key must be provided")]
56 IncompleteClientAuth,
57
58 #[error("must provide both cert and key")]
59 CertKeyCountMismatch,
60
61 #[error("must provide at least one cert/key pair or generate entry")]
62 NoCertSource,
63
64 #[error("private key {} doesn't match certificate {}", key.display(), cert.display())]
65 KeyMismatch {
66 key: PathBuf,
67 cert: PathBuf,
68 #[source]
69 source: rustls::Error,
70 },
71
72 #[error(transparent)]
73 Rustls(#[from] rustls::Error),
74
75 #[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
76 #[error(transparent)]
77 Rcgen(#[from] rcgen::Error),
78
79 #[error("no crypto provider available; enable aws-lc-rs or ring feature")]
80 NoCryptoProvider,
81}
82
83pub type Result<T> = std::result::Result<T, Error>;
85
86pub(crate) fn read_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
88 let file = fs::File::open(path).map_err(Error::Open)?;
89 let mut reader = io::BufReader::new(file);
90 CertificateDer::pem_reader_iter(&mut reader)
91 .collect::<std::result::Result<_, _>>()
92 .map_err(Error::Read)
93}
94
95#[serde_with::serde_as]
99#[derive(Clone, Default, Debug, clap::Args, serde::Serialize, serde::Deserialize)]
100#[serde(default, deny_unknown_fields)]
101#[group(id = "tls-client")]
102#[non_exhaustive]
103pub struct Client {
104 #[serde(skip_serializing_if = "Vec::is_empty")]
114 #[arg(id = "tls-root", long = "tls-root", env = "MOQ_CLIENT_TLS_ROOT")]
115 #[serde_as(as = "serde_with::OneOrMany<_>")]
116 pub root: Vec<PathBuf>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
125 #[arg(
126 id = "tls-system-roots",
127 long = "tls-system-roots",
128 env = "MOQ_CLIENT_TLS_SYSTEM_ROOTS",
129 default_missing_value = "true",
130 num_args = 0..=1,
131 require_equals = true,
132 value_parser = clap::value_parser!(bool),
133 )]
134 pub system_roots: Option<bool>,
135
136 #[serde(skip_serializing_if = "Vec::is_empty")]
147 #[arg(id = "tls-fingerprint", long = "tls-fingerprint", env = "MOQ_CLIENT_TLS_FINGERPRINT")]
148 #[serde_as(as = "serde_with::OneOrMany<_>")]
149 pub fingerprint: Vec<String>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
156 #[arg(id = "client-tls-cert", long = "client-tls-cert", env = "MOQ_CLIENT_TLS_CERT")]
157 pub cert: Option<PathBuf>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
164 #[arg(id = "client-tls-key", long = "client-tls-key", env = "MOQ_CLIENT_TLS_KEY")]
165 pub key: Option<PathBuf>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
171 #[arg(
172 id = "tls-disable-verify",
173 long = "tls-disable-verify",
174 env = "MOQ_CLIENT_TLS_DISABLE_VERIFY",
175 default_missing_value = "true",
176 num_args = 0..=1,
177 require_equals = true,
178 value_parser = clap::value_parser!(bool),
179 )]
180 pub disable_verify: Option<bool>,
181}
182
183impl Client {
184 pub fn build(&self) -> Result<rustls::ClientConfig> {
190 let provider = crypto::provider();
191
192 let system_roots = self.system_roots.unwrap_or(self.root.is_empty());
195
196 let custom_verifier = self.disable_verify.unwrap_or_default() || !self.fingerprint.is_empty();
201 if !system_roots && self.root.is_empty() && !custom_verifier {
202 return Err(Error::NoRoots);
203 }
204
205 let mut roots = rustls::RootCertStore::empty();
206 if system_roots {
207 let native = rustls_native_certs::load_native_certs();
208 for err in native.errors {
209 tracing::warn!(%err, "failed to load root cert");
210 }
211 for cert in native.certs {
212 roots.add(cert).map_err(Error::AddRoot)?;
213 }
214 }
215 for root in &self.root {
216 let certs = read_certs(root)?;
217 if certs.is_empty() {
218 return Err(Error::EmptyRoots(root.clone()));
219 }
220 for cert in certs {
221 roots.add(cert).map_err(Error::AddRoot)?;
222 }
223 }
224
225 let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
228 .with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])?
229 .with_root_certificates(roots);
230
231 let mut tls = match (&self.cert, &self.key) {
232 (Some(cert_path), Some(key_path)) => {
233 let cert_pem = fs::read(cert_path).map_err(Error::ReadFile)?;
234 let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_pem)
235 .collect::<std::result::Result<_, _>>()
236 .map_err(Error::Read)?;
237 if chain.is_empty() {
238 return Err(Error::Empty);
239 }
240 let key_pem = fs::read(key_path).map_err(Error::ReadFile)?;
241 let key = PrivateKeyDer::from_pem_slice(&key_pem).map_err(Error::Key)?;
242 builder.with_client_auth_cert(chain, key).map_err(Error::ClientAuth)?
243 }
244 (None, None) => builder.with_no_client_auth(),
245 _ => return Err(Error::IncompleteClientAuth),
246 };
247
248 if self.disable_verify.unwrap_or_default() {
249 tracing::warn!("TLS server certificate verification is disabled; A man-in-the-middle attack is possible.");
250 let noop = NoCertificateVerification(provider);
251 tls.dangerous().set_certificate_verifier(Arc::new(noop));
252 } else if !self.fingerprint.is_empty() {
253 let fingerprints = self
254 .fingerprint
255 .iter()
256 .map(|fp| {
257 let bytes = hex::decode(fp.trim()).map_err(Error::Fingerprint)?;
258 match bytes.len() {
259 32 => Ok(bytes),
260 len => Err(Error::FingerprintLength(len)),
261 }
262 })
263 .collect::<Result<Vec<_>>>()?;
264
265 let verifier = FingerprintVerifier::new(provider, fingerprints);
266 tls.dangerous().set_certificate_verifier(Arc::new(verifier));
267 }
268
269 Ok(tls)
270 }
271}
272
273#[serde_with::serde_as]
282#[derive(clap::Args, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
283#[serde(deny_unknown_fields)]
284#[group(id = "tls-server")]
285#[non_exhaustive]
286pub struct Server {
287 #[arg(long = "tls-cert", id = "tls-cert", env = "MOQ_SERVER_TLS_CERT")]
289 #[serde(default, skip_serializing_if = "Vec::is_empty")]
290 #[serde_as(as = "serde_with::OneOrMany<_>")]
291 pub cert: Vec<PathBuf>,
292
293 #[arg(long = "tls-key", id = "tls-key", env = "MOQ_SERVER_TLS_KEY")]
295 #[serde(default, skip_serializing_if = "Vec::is_empty")]
296 #[serde_as(as = "serde_with::OneOrMany<_>")]
297 pub key: Vec<PathBuf>,
298
299 #[arg(
302 long = "tls-generate",
303 id = "tls-generate",
304 value_delimiter = ',',
305 env = "MOQ_SERVER_TLS_GENERATE"
306 )]
307 #[serde(default, skip_serializing_if = "Vec::is_empty")]
308 #[serde_as(as = "serde_with::OneOrMany<_>")]
309 pub generate: Vec<String>,
310
311 #[arg(
320 long = "server-tls-root",
321 id = "server-tls-root",
322 value_delimiter = ',',
323 env = "MOQ_SERVER_TLS_ROOT"
324 )]
325 #[serde(default, skip_serializing_if = "Vec::is_empty")]
326 #[serde_as(as = "serde_with::OneOrMany<_>")]
327 pub root: Vec<PathBuf>,
328}
329
330impl Server {
331 pub fn load_roots(&self) -> Result<rustls::RootCertStore> {
333 let mut roots = rustls::RootCertStore::empty();
334 for path in &self.root {
335 let certs = read_certs(path)?;
336 if certs.is_empty() {
337 return Err(Error::Empty);
338 }
339 for cert in certs {
340 roots.add(cert).map_err(Error::AddRoot)?;
341 }
342 }
343 Ok(roots)
344 }
345}
346
347pub struct PeerIdentity {
354 chain: Vec<CertificateDer<'static>>,
355}
356
357impl PeerIdentity {
358 #[cfg(any(feature = "quinn", feature = "noq"))]
362 pub(crate) fn from_any(identity: Option<Box<dyn std::any::Any>>) -> Option<Self> {
363 let chain = identity?.downcast::<Vec<CertificateDer<'static>>>().ok()?;
364 Some(Self { chain: *chain })
365 }
366
367 pub fn chain(&self) -> &[CertificateDer<'static>] {
373 &self.chain
374 }
375
376 pub fn expiry(&self) -> Option<std::time::SystemTime> {
379 use std::time::{Duration, UNIX_EPOCH};
380
381 let leaf = self.chain.first()?;
382 let (_, cert) = x509_parser::parse_x509_certificate(leaf).ok()?;
383 let secs = u64::try_from(cert.validity().not_after.timestamp()).ok()?;
384 Some(UNIX_EPOCH + Duration::from_secs(secs))
385 }
386}
387
388#[derive(Debug)]
390pub struct Info {
391 #[cfg(any(feature = "noq", feature = "quinn"))]
392 pub(crate) certs: Vec<Arc<rustls::sign::CertifiedKey>>,
393 pub fingerprints: Vec<String>,
394}
395
396#[derive(Debug)]
399struct NoCertificateVerification(crypto::Provider);
400
401impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
402 fn verify_server_cert(
403 &self,
404 _end_entity: &CertificateDer<'_>,
405 _intermediates: &[CertificateDer<'_>],
406 _server_name: &ServerName<'_>,
407 _ocsp: &[u8],
408 _now: UnixTime,
409 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
410 Ok(rustls::client::danger::ServerCertVerified::assertion())
411 }
412
413 fn verify_tls12_signature(
414 &self,
415 message: &[u8],
416 cert: &CertificateDer<'_>,
417 dss: &rustls::DigitallySignedStruct,
418 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
419 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
420 }
421
422 fn verify_tls13_signature(
423 &self,
424 message: &[u8],
425 cert: &CertificateDer<'_>,
426 dss: &rustls::DigitallySignedStruct,
427 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
428 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
429 }
430
431 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
432 self.0.signature_verification_algorithms.supported_schemes()
433 }
434}
435
436#[derive(Debug)]
439pub(crate) struct FingerprintVerifier {
440 provider: crypto::Provider,
441 fingerprints: Vec<Vec<u8>>,
442}
443
444impl FingerprintVerifier {
445 pub fn new(provider: crypto::Provider, fingerprints: Vec<Vec<u8>>) -> Self {
446 Self { provider, fingerprints }
447 }
448}
449
450impl rustls::client::danger::ServerCertVerifier for FingerprintVerifier {
451 fn verify_server_cert(
452 &self,
453 end_entity: &CertificateDer<'_>,
454 _intermediates: &[CertificateDer<'_>],
455 _server_name: &ServerName<'_>,
456 _ocsp: &[u8],
457 _now: UnixTime,
458 ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
459 let fingerprint = crypto::sha256(&self.provider, end_entity);
460 if self.fingerprints.iter().any(|fp| fingerprint.as_ref() == fp.as_slice()) {
461 Ok(rustls::client::danger::ServerCertVerified::assertion())
462 } else {
463 Err(rustls::Error::General("fingerprint mismatch".into()))
464 }
465 }
466
467 fn verify_tls12_signature(
468 &self,
469 message: &[u8],
470 cert: &CertificateDer<'_>,
471 dss: &rustls::DigitallySignedStruct,
472 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
473 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
474 }
475
476 fn verify_tls13_signature(
477 &self,
478 message: &[u8],
479 cert: &CertificateDer<'_>,
480 dss: &rustls::DigitallySignedStruct,
481 ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
482 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
483 }
484
485 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
486 self.provider.signature_verification_algorithms.supported_schemes()
487 }
488}
489
490#[cfg(test)]
491#[cfg(all(any(feature = "quinn", feature = "noq", feature = "quiche"), feature = "aws-lc-rs"))]
492mod tests {
493 use super::*;
494 use rustls::client::danger::ServerCertVerifier;
495 use rustls::pki_types::ServerName;
496
497 fn self_signed() -> CertificateDer<'static> {
498 let key = rcgen::KeyPair::generate().unwrap();
499 let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
500 params.self_signed(&key).unwrap().into()
501 }
502
503 #[cfg(any(feature = "quinn", feature = "noq"))]
504 #[test]
505 fn peer_identity_expiry_reads_not_after() {
506 let not_after = ::time::OffsetDateTime::from_unix_timestamp(2_000_000_000).unwrap();
508
509 let key = rcgen::KeyPair::generate().unwrap();
510 let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
511 params.not_after = not_after;
512 let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into();
513
514 let identity: Box<dyn std::any::Any> = Box::new(vec![cert]);
516 let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed");
517 let expiry = parsed.expiry().expect("expiry parsed");
518 assert_eq!(
519 expiry.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
520 2_000_000_000
521 );
522 }
523
524 #[cfg(any(feature = "quinn", feature = "noq"))]
525 #[test]
526 fn peer_identity_none_without_chain() {
527 assert!(PeerIdentity::from_any(None).is_none());
528 let bogus: Box<dyn std::any::Any> = Box::new(42u32);
530 assert!(PeerIdentity::from_any(Some(bogus)).is_none());
531 }
532
533 #[test]
534 fn fingerprint_verifier_matches_and_rejects() {
535 let provider = crypto::provider();
536 let cert = self_signed();
537 let fingerprint = crypto::sha256(&provider, cert.as_ref()).as_ref().to_vec();
538
539 let name = ServerName::try_from("localhost").unwrap();
540 let now = UnixTime::now();
541
542 let verifier = FingerprintVerifier::new(provider.clone(), vec![fingerprint]);
543 assert!(verifier.verify_server_cert(&cert, &[], &name, &[], now).is_ok());
544
545 let other = self_signed();
547 assert!(verifier.verify_server_cert(&other, &[], &name, &[], now).is_err());
548 }
549
550 #[test]
551 fn build_installs_fingerprint_verifier() {
552 let cert = self_signed();
553 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
554
555 let config = Client {
557 fingerprint: vec![fingerprint],
558 ..Default::default()
559 };
560 assert!(config.build().is_ok());
561 }
562
563 #[test]
564 fn build_rejects_invalid_fingerprint_hex() {
565 let config = Client {
566 fingerprint: vec!["not-hex".to_string()],
567 ..Default::default()
568 };
569 assert!(matches!(config.build(), Err(Error::Fingerprint(_))));
570 }
571
572 #[test]
573 fn build_rejects_wrong_length_fingerprint() {
574 let config = Client {
576 fingerprint: vec!["abcd".to_string()],
577 ..Default::default()
578 };
579 assert!(matches!(config.build(), Err(Error::FingerprintLength(2))));
580 }
581
582 #[test]
583 fn build_rejects_no_roots() {
584 let config = Client {
587 system_roots: Some(false),
588 ..Default::default()
589 };
590 assert!(matches!(config.build(), Err(Error::NoRoots)));
591 }
592
593 #[test]
594 fn build_allows_no_roots_when_verification_overridden() {
595 let config = Client {
597 system_roots: Some(false),
598 disable_verify: Some(true),
599 ..Default::default()
600 };
601 assert!(config.build().is_ok());
602
603 let cert = self_signed();
605 let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
606 let config = Client {
607 system_roots: Some(false),
608 fingerprint: vec![fingerprint],
609 ..Default::default()
610 };
611 assert!(config.build().is_ok());
612 }
613}
614
615#[cfg(any(feature = "quinn", feature = "noq"))]
618#[derive(Debug)]
619pub(crate) struct ServeCerts {
620 pub info: Arc<RwLock<Info>>,
621 provider: crypto::Provider,
622}
623
624#[cfg(any(feature = "quinn", feature = "noq"))]
625impl ServeCerts {
626 pub fn new(provider: crypto::Provider) -> Self {
627 Self {
628 info: Arc::new(RwLock::new(Info {
629 certs: Vec::new(),
630 fingerprints: Vec::new(),
631 })),
632 provider,
633 }
634 }
635
636 pub fn load_certs(&self, config: &Server) -> Result<()> {
637 if config.cert.len() != config.key.len() {
638 return Err(Error::CertKeyCountMismatch);
639 }
640 if config.cert.is_empty() && config.generate.is_empty() {
641 return Err(Error::NoCertSource);
642 }
643
644 let mut certs = Vec::new();
645
646 for (cert, key) in config.cert.iter().zip(config.key.iter()) {
648 certs.push(Arc::new(self.load(cert, key)?));
649 }
650
651 if !config.generate.is_empty() {
653 certs.push(Arc::new(self.generate(&config.generate)?));
654 }
655
656 self.set_certs(certs);
657 Ok(())
658 }
659
660 fn load(&self, chain_path: &Path, key_path: &Path) -> Result<rustls::sign::CertifiedKey> {
662 let chain = read_certs(chain_path)?;
663 if chain.is_empty() {
664 return Err(Error::Empty);
665 }
666
667 let key = PrivateKeyDer::from_pem_file(key_path).map_err(Error::Key)?;
669 let key = self.provider.key_provider.load_private_key(key)?;
670
671 let certified_key = rustls::sign::CertifiedKey::new(chain, key);
672
673 certified_key.keys_match().map_err(|source| Error::KeyMismatch {
674 key: key_path.to_path_buf(),
675 cert: chain_path.to_path_buf(),
676 source,
677 })?;
678
679 Ok(certified_key)
680 }
681
682 #[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
683 fn generate(&self, hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
684 let key_pair = rcgen::KeyPair::generate()?;
685
686 let mut params = rcgen::CertificateParams::new(hostnames)?;
687
688 params.not_before = ::time::OffsetDateTime::now_utc() - ::time::Duration::days(1);
691 params.not_after = params.not_before + ::time::Duration::days(14);
692
693 let cert = params.self_signed(&key_pair)?;
695
696 let key_der = key_pair.serialized_der().to_vec();
698 let key_der = PrivatePkcs8KeyDer::from(key_der);
699 let key = self.provider.key_provider.load_private_key(key_der.into())?;
700
701 Ok(rustls::sign::CertifiedKey::new(vec![cert.into()], key))
703 }
704
705 #[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
706 fn generate(&self, _hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
707 Err(Error::NoCryptoProvider)
708 }
709
710 pub fn set_certs(&self, certs: Vec<Arc<rustls::sign::CertifiedKey>>) {
712 let fingerprints = certs
713 .iter()
714 .map(|ck| {
715 let fingerprint = crate::crypto::sha256(&self.provider, ck.cert[0].as_ref());
716 hex::encode(fingerprint)
717 })
718 .collect();
719
720 let mut info = self.info.write().expect("info write lock poisoned");
721 info.certs = certs;
722 info.fingerprints = fingerprints;
723 }
724
725 fn best_certificate(
727 &self,
728 client_hello: &rustls::server::ClientHello<'_>,
729 ) -> Option<Arc<rustls::sign::CertifiedKey>> {
730 let server_name = client_hello.server_name()?;
731 let dns_name = rustls::pki_types::ServerName::try_from(server_name).ok()?;
732
733 for ck in self.info.read().expect("info read lock poisoned").certs.iter() {
734 let leaf: webpki::EndEntityCert = ck
735 .end_entity_cert()
736 .expect("missing certificate")
737 .try_into()
738 .expect("failed to parse certificate");
739
740 if leaf.verify_is_valid_for_subject_name(&dns_name).is_ok() {
741 return Some(ck.clone());
742 }
743 }
744
745 None
746 }
747}
748
749#[cfg(any(feature = "quinn", feature = "noq"))]
750impl rustls::server::ResolvesServerCert for ServeCerts {
751 fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<rustls::sign::CertifiedKey>> {
752 if let Some(cert) = self.best_certificate(&client_hello) {
753 return Some(cert);
754 }
755
756 tracing::warn!(server_name = ?client_hello.server_name(), "no SNI certificate found");
759
760 self.info
761 .read()
762 .expect("info read lock poisoned")
763 .certs
764 .first()
765 .cloned()
766 }
767}
768
769#[cfg(any(feature = "quinn", feature = "noq"))]
777pub(crate) async fn reload_certs(certs: Arc<ServeCerts>, tls_config: Server) {
778 let paths: Vec<PathBuf> = tls_config.cert.iter().chain(tls_config.key.iter()).cloned().collect();
779 if paths.is_empty() {
780 return;
781 }
782
783 let mut watcher = match crate::watch::FileWatcher::new(&paths) {
784 Ok(watcher) => watcher,
785 Err(err) => {
786 tracing::error!(%err, "failed to watch certificate files; hot reload disabled");
787 return;
788 }
789 };
790
791 loop {
792 watcher.changed().await;
793 tracing::info!("reloading server certificates");
794
795 if let Err(err) = certs.load_certs(&tls_config) {
796 tracing::warn!(%err, "failed to reload server certificates");
797 }
798 }
799}