Skip to main content

moq_native/
tls.rs

1//! TLS trust, certificates, and keys, split by role.
2//!
3//! [`Client`] (`--client-tls-*`) picks who to trust: system roots, custom roots,
4//! a pinned SHA-256 fingerprint, or nothing at all. [`Server`] (`--server-tls-*`)
5//! supplies the certificate chain to serve, loaded from disk or self-signed on
6//! startup, and optionally the roots that authenticate mTLS clients.
7//!
8//! Certificates loaded from disk are watched and hot reloaded, so rotating them
9//! needs no restart. [`Certificates`] reads the current set back out.
10
11use 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/// Errors loading or generating TLS certificates and keys.
27///
28/// Shared by the client TLS config and the quinn/noq servers so each backend's
29/// error type can compose it via `#[from]`.
30#[derive(Debug, thiserror::Error)]
31#[non_exhaustive]
32pub enum Error {
33	/// A certificate file couldn't be opened, usually a bad path or permissions.
34	#[error("failed to open certificate file")]
35	Open(#[source] std::io::Error),
36
37	/// A certificate or key file was opened but couldn't be read to the end.
38	#[error("failed to read file")]
39	ReadFile(#[source] std::io::Error),
40
41	/// A file's contents aren't valid PEM certificates.
42	#[error("failed to read certificates")]
43	Read(#[source] rustls::pki_types::pem::Error),
44
45	/// A file's contents aren't a valid PEM private key.
46	#[error("failed to parse private key")]
47	Key(#[source] rustls::pki_types::pem::Error),
48
49	/// A PEM file parsed cleanly but held no certificates.
50	#[error("no certificates found")]
51	Empty,
52
53	/// A root PEM file parsed cleanly but held no certificates, so it would trust nothing.
54	#[error("no roots found in {}", .0.display())]
55	EmptyRoots(PathBuf),
56
57	/// Nothing is configured that could ever verify a server certificate.
58	#[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	/// A configured fingerprint isn't valid hex.
64	#[error("invalid TLS fingerprint (expected hex-encoded SHA-256)")]
65	Fingerprint(#[source] hex::FromHexError),
66
67	/// A configured fingerprint is valid hex but the wrong size for a SHA-256 digest.
68	#[error("invalid TLS fingerprint length: expected 32 bytes (SHA-256), got {0}")]
69	FingerprintLength(usize),
70
71	/// Fingerprint pinning was combined with CA roots. Pinning bypasses the chain, so one of
72	/// the two would be silently ignored.
73	#[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	/// A root certificate parsed as PEM but rustls rejected it as a trust anchor.
79	#[error("failed to add root certificate")]
80	AddRoot(#[source] rustls::Error),
81
82	/// The JNI call in [`init_android`] failed, so the platform verifier is unavailable.
83	#[cfg(target_os = "android")]
84	#[error("failed to initialize the Android platform verifier")]
85	AndroidInit(#[source] jni::errors::Error),
86
87	/// rustls rejected the mTLS client certificate and key, e.g. they don't match.
88	#[error("failed to configure client certificate")]
89	ClientAuth(#[source] rustls::Error),
90
91	/// Only one half of the mTLS client identity was given; it needs both a cert and a key.
92	#[error("both --client-tls-cert and --client-tls-key must be provided")]
93	IncompleteClientAuth,
94
95	/// The server was given a different number of certificates than keys. They pair by index.
96	#[error("must provide both cert and key")]
97	CertKeyCountMismatch,
98
99	/// The server has no certificate to serve: no cert/key pair and no hostnames to generate one for.
100	#[error("must provide at least one cert/key pair or generate entry")]
101	NoCertSource,
102
103	/// A server cert/key pair was paired up by index but the key isn't the certificate's.
104	#[error("private key {} doesn't match certificate {}", key.display(), cert.display())]
105	KeyMismatch {
106		/// Path of the private key file.
107		key: PathBuf,
108		/// Path of the certificate file it was paired with.
109		cert: PathBuf,
110		/// Why rustls says the two don't match.
111		#[source]
112		source: rustls::Error,
113	},
114
115	/// A rustls error with no more specific context, e.g. building a config.
116	#[error(transparent)]
117	Rustls(#[from] rustls::Error),
118
119	/// The mTLS client-certificate verifier couldn't be built from the configured roots.
120	#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
121	#[error("failed to build client certificate verifier")]
122	ClientVerifier(#[source] rustls::server::VerifierBuilderError),
123
124	/// Generating a self-signed certificate failed.
125	#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
126	#[error(transparent)]
127	Rcgen(#[from] rcgen::Error),
128
129	/// The crate was built without a crypto provider, so no TLS is possible.
130	#[error("no crypto provider available; enable aws-lc-rs or ring feature")]
131	NoCryptoProvider,
132}
133
134/// Convenience alias for results produced by this module.
135pub type Result<T> = std::result::Result<T, Error>;
136
137/// Read a PEM file into its list of certificates.
138pub(crate) fn read_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
139	let file = fs::File::open(path).map_err(Error::Open)?;
140	let mut reader = io::BufReader::new(file);
141	CertificateDer::pem_reader_iter(&mut reader)
142		.collect::<std::result::Result<_, _>>()
143		.map_err(Error::Read)
144}
145
146// ── Client ──────────────────────────────────────────────────────────
147
148/// TLS configuration for the client.
149#[serde_with::serde_as]
150#[derive(Clone, Default, Debug, clap::Args, serde::Serialize, serde::Deserialize)]
151#[serde(default, deny_unknown_fields)]
152#[group(id = "tls-client")]
153#[non_exhaustive]
154pub struct Client {
155	/// Trust the TLS root at this path, encoded as PEM.
156	///
157	/// This value can be provided multiple times for multiple roots.
158	/// In config files, accepts either a single string or a TOML array.
159	///
160	/// These roots are added on top of the system roots. By default the system
161	/// roots are only loaded when no custom root is given, so passing a root
162	/// replaces them; set `--client-tls-system-roots` to trust both (e.g. to reach a
163	/// local relay with a private CA and a remote one with a public CA).
164	#[serde(skip_serializing_if = "Vec::is_empty")]
165	#[arg(id = "client-tls-root", long = "client-tls-root", env = "MOQ_CLIENT_TLS_ROOT")]
166	#[serde_as(as = "serde_with::OneOrMany<_>")]
167	pub root: Vec<PathBuf>,
168
169	/// Also trust the platform's native root certificates.
170	///
171	/// Defaults to enabled only when no `--client-tls-root` is given. Set it
172	/// explicitly to trust the system roots alongside any custom roots, or set it
173	/// to false to trust only the custom roots. Trusting neither (no custom root
174	/// and system roots disabled) is rejected, since verification could never pass.
175	#[serde(skip_serializing_if = "Option::is_none")]
176	#[arg(
177		id = "client-tls-system-roots",
178		long = "client-tls-system-roots",
179		env = "MOQ_CLIENT_TLS_SYSTEM_ROOTS",
180		default_missing_value = "true",
181		num_args = 0..=1,
182		require_equals = true,
183		value_parser = clap::value_parser!(bool),
184	)]
185	pub system_roots: Option<bool>,
186
187	/// Pin the peer to a certificate with one of these SHA-256 fingerprints, encoded as hex.
188	///
189	/// This is the native equivalent of the browser's WebTransport `serverCertificateHashes`,
190	/// and accepts the same values a server reports via its certificate fingerprints. Use it to
191	/// trust a self-signed certificate without disabling verification or fetching the hash over
192	/// an insecure `http://` request. When set, the normal CA/root chain is bypassed: only the
193	/// leaf certificate's fingerprint is checked.
194	///
195	/// This value can be provided multiple times to accept any of several fingerprints (e.g.
196	/// across a certificate rotation). In config files, accepts either a single string or a TOML array.
197	#[serde(skip_serializing_if = "Vec::is_empty")]
198	#[arg(
199		id = "client-tls-fingerprint",
200		long = "client-tls-fingerprint",
201		env = "MOQ_CLIENT_TLS_FINGERPRINT"
202	)]
203	#[serde_as(as = "serde_with::OneOrMany<_>")]
204	pub fingerprint: Vec<String>,
205
206	/// PEM file containing the client certificate chain for mTLS.
207	///
208	/// Only certificates are extracted; any private keys in the file are ignored.
209	/// Must be paired with `--client-tls-key`.
210	#[serde(skip_serializing_if = "Option::is_none")]
211	#[arg(id = "client-tls-cert", long = "client-tls-cert", env = "MOQ_CLIENT_TLS_CERT")]
212	pub cert: Option<PathBuf>,
213
214	/// PEM file containing the private key for mTLS.
215	///
216	/// Only the private key is extracted; any certificates in the file are ignored.
217	/// Must be paired with `--client-tls-cert`.
218	#[serde(skip_serializing_if = "Option::is_none")]
219	#[arg(id = "client-tls-key", long = "client-tls-key", env = "MOQ_CLIENT_TLS_KEY")]
220	pub key: Option<PathBuf>,
221
222	/// Danger: Disable TLS certificate verification.
223	///
224	/// Fine for local development and between relays, but should be used in caution in production.
225	#[serde(skip_serializing_if = "Option::is_none")]
226	#[arg(
227		id = "client-tls-disable-verify",
228		long = "client-tls-disable-verify",
229		env = "MOQ_CLIENT_TLS_DISABLE_VERIFY",
230		default_missing_value = "true",
231		num_args = 0..=1,
232		require_equals = true,
233		value_parser = clap::value_parser!(bool),
234	)]
235	pub disable_verify: Option<bool>,
236
237	/// Override the TLS SNI and certificate verification hostname for outbound connections.
238	///
239	/// When unset, the connect URL's host is used (default behavior). Useful when dialing a
240	/// raw IP address but needing to present/verify a DNS name the server certificate covers.
241	#[serde(skip_serializing_if = "Option::is_none")]
242	#[arg(
243		id = "client-tls-host-name",
244		long = "client-tls-host-name",
245		env = "MOQ_CLIENT_TLS_HOST_NAME"
246	)]
247	pub host_name: Option<String>,
248
249	/// Deprecated `--tls-*` spellings, folded into the canonical fields above with
250	/// a warning. Private and hidden so they stay off the public surface; not a
251	/// TOML field (config files use the canonical names).
252	#[command(flatten)]
253	#[serde(skip)]
254	deprecated: Deprecated,
255}
256
257/// Holds the deprecated bare `--tls-*` flag spellings (renamed to `--client-tls-*`).
258/// Flattened into [`Client`] so they keep parsing; folded into the canonical
259/// fields by [`Client::build`] with a deprecation warning. No env (the env names
260/// were never renamed) and no TOML.
261#[derive(Clone, Default, Debug, clap::Args)]
262struct Deprecated {
263	#[arg(long = "tls-root", hide = true)]
264	root: Vec<PathBuf>,
265
266	#[arg(
267		long = "tls-system-roots",
268		hide = true,
269		default_missing_value = "true",
270		num_args = 0..=1,
271		require_equals = true,
272		value_parser = clap::value_parser!(bool),
273	)]
274	system_roots: Option<bool>,
275
276	#[arg(long = "tls-fingerprint", hide = true)]
277	fingerprint: Vec<String>,
278
279	#[arg(
280		long = "tls-disable-verify",
281		hide = true,
282		default_missing_value = "true",
283		num_args = 0..=1,
284		require_equals = true,
285		value_parser = clap::value_parser!(bool),
286	)]
287	disable_verify: Option<bool>,
288}
289
290/// The resolved server-certificate verification policy.
291///
292/// Computed once by [Client::verification] and shared by every backend (the
293/// rustls-based quinn/noq via [Client::build], and quiche directly) so they
294/// agree on precedence, the system-roots default, and which flag combinations
295/// are valid.
296#[derive(Clone)]
297pub(crate) enum Verification {
298	/// No verification at all. Insecure; only via `--client-tls-disable-verify`.
299	Disabled,
300
301	/// Pin the leaf certificate by SHA-256. The CA chain is not consulted, so
302	/// this is mutually exclusive with any roots.
303	Fingerprints(Vec<[u8; 32]>),
304
305	/// Standard CA verification. When `system` is set the platform/default trust
306	/// store is trusted too; each backend resolves that its own way (the rustls
307	/// backends use the OS platform verifier, quiche loads the native roots).
308	/// `custom` are extra PEM roots trusted in addition.
309	Roots {
310		custom: Vec<CertificateDer<'static>>,
311		system: bool,
312	},
313}
314
315impl Client {
316	/// Log a warning for each deprecated `--tls-*` flag in use. Called once from
317	/// [`Self::verification`], which every backend runs, so a deprecated flag warns once.
318	pub(crate) fn warn_deprecated(&self) {
319		if !self.deprecated.root.is_empty() {
320			tracing::warn!("--tls-root is deprecated; use --client-tls-root");
321		}
322		if self.deprecated.system_roots.is_some() {
323			tracing::warn!("--tls-system-roots is deprecated; use --client-tls-system-roots");
324		}
325		if !self.deprecated.fingerprint.is_empty() {
326			tracing::warn!("--tls-fingerprint is deprecated; use --client-tls-fingerprint");
327		}
328		if self.deprecated.disable_verify.is_some() {
329			tracing::warn!("--tls-disable-verify is deprecated; use --client-tls-disable-verify");
330		}
331	}
332
333	/// Roots from the canonical field plus the deprecated `--tls-root` spelling.
334	pub(crate) fn effective_root(&self) -> Vec<PathBuf> {
335		let mut root = self.root.clone();
336		root.extend(self.deprecated.root.iter().cloned());
337		root
338	}
339
340	/// Fingerprints from the canonical field plus the deprecated `--tls-fingerprint`.
341	pub(crate) fn effective_fingerprint(&self) -> Vec<String> {
342		let mut fp = self.fingerprint.clone();
343		fp.extend(self.deprecated.fingerprint.iter().cloned());
344		fp
345	}
346
347	/// `system_roots`, preferring the canonical flag over the deprecated alias.
348	pub(crate) fn effective_system_roots(&self) -> Option<bool> {
349		self.system_roots.or(self.deprecated.system_roots)
350	}
351
352	/// `disable_verify`, preferring the canonical flag over the deprecated alias.
353	pub(crate) fn effective_disable_verify(&self) -> Option<bool> {
354		self.disable_verify.or(self.deprecated.disable_verify)
355	}
356
357	/// Resolve the verification policy from the configured flags.
358	///
359	/// Precedence and rules (shared by all backends):
360	/// - `--client-tls-disable-verify` wins and disables verification.
361	/// - `--client-tls-fingerprint` pins the leaf and bypasses the CA chain; combining
362	///   it with `--client-tls-root` or `--client-tls-system-roots` is rejected rather than
363	///   silently ignoring one of them.
364	/// - Otherwise, verify against the system roots (default) plus any custom
365	///   roots. The system roots are dropped once a custom root is given unless
366	///   `--client-tls-system-roots` re-enables them.
367	pub(crate) fn verification(&self) -> Result<Verification> {
368		self.warn_deprecated();
369
370		if self.effective_disable_verify().unwrap_or_default() {
371			return Ok(Verification::Disabled);
372		}
373
374		let fingerprints = self.fingerprints()?;
375		if !fingerprints.is_empty() {
376			if !self.effective_root().is_empty() || self.effective_system_roots() == Some(true) {
377				return Err(Error::FingerprintWithRoots);
378			}
379			return Ok(Verification::Fingerprints(fingerprints));
380		}
381
382		let root = self.effective_root();
383		// Default to system roots only when no custom root is given, so passing a
384		// root replaces them unless the system roots are explicitly re-enabled.
385		let system = self.effective_system_roots().unwrap_or(root.is_empty());
386
387		let mut custom = Vec::new();
388		for root in &root {
389			let certs = read_certs(root)?;
390			if certs.is_empty() {
391				return Err(Error::EmptyRoots(root.clone()));
392			}
393			custom.extend(certs);
394		}
395
396		// WebPKI needs at least one trusted root to ever succeed, so fail fast
397		// instead of producing confusing handshake errors later. With system
398		// trust enabled the verifier supplies its own roots, so custom roots are
399		// optional.
400		if !system && custom.is_empty() {
401			return Err(Error::NoRoots);
402		}
403
404		Ok(Verification::Roots { custom, system })
405	}
406
407	/// Whether an insecure `http://` certificate-fingerprint bootstrap may be
408	/// honored for a connection.
409	///
410	/// Only when no stronger verification is configured: an explicit
411	/// `--client-tls-fingerprint` must never be weakened by an attacker-controlled
412	/// plaintext fetch, and there is nothing to bootstrap when verification is
413	/// disabled. With CA roots (the default), `http://` is the deliberate
414	/// per-connection way to pin a self-signed relay, so it is allowed.
415	pub(crate) fn allows_http_bootstrap(&self) -> bool {
416		self.effective_fingerprint().is_empty() && !self.effective_disable_verify().unwrap_or_default()
417	}
418
419	/// Parse the configured fingerprints into fixed-size SHA-256 digests.
420	fn fingerprints(&self) -> Result<Vec<[u8; 32]>> {
421		self.effective_fingerprint()
422			.iter()
423			.map(|fp| {
424				let bytes = hex::decode(fp.trim()).map_err(Error::Fingerprint)?;
425				bytes.try_into().map_err(|v: Vec<u8>| Error::FingerprintLength(v.len()))
426			})
427			.collect()
428	}
429
430	/// Build a [`rustls::ClientConfig`] from this configuration.
431	///
432	/// Resolves the verification policy, optionally attaches a client identity
433	/// for mTLS, and installs the matching verifier.
434	pub fn build(&self) -> Result<rustls::ClientConfig> {
435		let provider = crypto::provider();
436		let verification = self.verification()?;
437
438		// Allow TLS 1.2 in addition to 1.3 for WebSocket compatibility.
439		// QUIC always negotiates TLS 1.3 regardless of this setting.
440		let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
441			.with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])?;
442
443		// Install the server-certificate verifier. Disabled/Fingerprints get a
444		// placeholder empty store here and swap in their own verifier below.
445		let builder = match &verification {
446			Verification::Roots { custom, system: true } => Self::system_verifier(builder, custom, &provider)?,
447			Verification::Roots { custom, system: false } => builder.with_root_certificates(root_store(custom)?),
448			Verification::Disabled | Verification::Fingerprints(_) => {
449				builder.with_root_certificates(rustls::RootCertStore::empty())
450			}
451		};
452
453		let mut tls = self.with_client_auth(builder)?;
454
455		match verification {
456			Verification::Disabled => {
457				tracing::warn!(
458					"TLS server certificate verification is disabled; A man-in-the-middle attack is possible."
459				);
460				tls.dangerous()
461					.set_certificate_verifier(Arc::new(NoCertificateVerification(provider)));
462			}
463			Verification::Fingerprints(fingerprints) => {
464				let fingerprints = fingerprints.into_iter().map(|fp| fp.to_vec()).collect();
465				let verifier = FingerprintVerifier::new(provider, fingerprints);
466				tls.dangerous().set_certificate_verifier(Arc::new(verifier));
467			}
468			// The verifier was installed by the builder above.
469			Verification::Roots { .. } => {}
470		}
471
472		Ok(tls)
473	}
474
475	/// Build the verifier for system/default trust on the rustls backends.
476	///
477	/// Uses the OS-native platform verifier (Keychain/SecTrust, Windows
478	/// CryptoAPI, or the native store on Linux) everywhere it works, optionally
479	/// extended with `custom` PEM roots. Android's platform verifier needs JNI
480	/// setup (see [`init_android`]); until that has run we trust the bundled
481	/// Mozilla roots so verification still works out of the box.
482	fn system_verifier(
483		builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>,
484		custom: &[CertificateDer<'static>],
485		provider: &crypto::Provider,
486	) -> Result<rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>> {
487		// Android's platform verifier needs JNI init (see `init_android`) and,
488		// unlike the other platforms, can't be extended with custom roots. So use
489		// it only once initialized and with no custom roots; otherwise trust the
490		// bundled Mozilla roots (plus any custom roots) so verification still works.
491		#[cfg(target_os = "android")]
492		{
493			if ANDROID_INITIALIZED.load(std::sync::atomic::Ordering::Acquire) && custom.is_empty() {
494				let verifier = rustls_platform_verifier::Verifier::new(provider.clone())?;
495				return Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)));
496			}
497
498			let mut roots = rustls::RootCertStore::empty();
499			roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
500			for cert in custom {
501				roots.add(cert.clone()).map_err(Error::AddRoot)?;
502			}
503			Ok(builder.with_root_certificates(roots))
504		}
505
506		#[cfg(not(target_os = "android"))]
507		{
508			let verifier = if custom.is_empty() {
509				rustls_platform_verifier::Verifier::new(provider.clone())?
510			} else {
511				rustls_platform_verifier::Verifier::new_with_extra_roots(custom.iter().cloned(), provider.clone())?
512			};
513			Ok(builder.dangerous().with_custom_certificate_verifier(Arc::new(verifier)))
514		}
515	}
516
517	/// Attach the optional mTLS client identity, finishing the rustls builder.
518	fn with_client_auth(
519		&self,
520		builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>,
521	) -> Result<rustls::ClientConfig> {
522		Ok(match (&self.cert, &self.key) {
523			(Some(cert_path), Some(key_path)) => {
524				let cert_pem = fs::read(cert_path).map_err(Error::ReadFile)?;
525				let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_pem)
526					.collect::<std::result::Result<_, _>>()
527					.map_err(Error::Read)?;
528				if chain.is_empty() {
529					return Err(Error::Empty);
530				}
531				let key_pem = fs::read(key_path).map_err(Error::ReadFile)?;
532				let key = PrivateKeyDer::from_pem_slice(&key_pem).map_err(Error::Key)?;
533				builder.with_client_auth_cert(chain, key).map_err(Error::ClientAuth)?
534			}
535			(None, None) => builder.with_no_client_auth(),
536			_ => return Err(Error::IncompleteClientAuth),
537		})
538	}
539}
540
541/// Build a [`rustls::RootCertStore`] from a list of custom PEM roots.
542fn root_store(custom: &[CertificateDer<'static>]) -> Result<rustls::RootCertStore> {
543	let mut roots = rustls::RootCertStore::empty();
544	for cert in custom {
545		roots.add(cert.clone()).map_err(Error::AddRoot)?;
546	}
547	Ok(roots)
548}
549
550/// Whether [`init_android`] has successfully wired up the platform verifier.
551#[cfg(target_os = "android")]
552static ANDROID_INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
553
554/// Initialize Android platform certificate verification.
555///
556/// On Android the OS trust store is only reachable through the JVM, so the
557/// platform verifier needs a JNI handle to the application `Context` before it
558/// can be used. Call this once at startup (e.g. from `JNI_OnLoad`) with an
559/// attached [`jni::Env`] for the calling thread and the application `Context`.
560/// The `moq-ffi` bindings call it automatically, so most consumers never touch
561/// this directly.
562///
563/// Until it succeeds, clients fall back to the bundled Mozilla roots, so a
564/// missing or failed init degrades to webpki verification rather than failing.
565#[cfg(target_os = "android")]
566pub fn init_android(env: &mut jni::Env, context: jni::objects::JObject) -> Result<()> {
567	rustls_platform_verifier::android::init_with_env(env, context).map_err(Error::AndroidInit)?;
568	ANDROID_INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
569	Ok(())
570}
571
572// ── Server ──────────────────────────────────────────────────────────
573
574/// TLS configuration for the server.
575///
576/// Certificate and keys must currently be files on disk.
577/// Alternatively, you can generate a self-signed certificate given a list of hostnames.
578///
579/// In config files, each list field accepts either a single string or a TOML array.
580#[serde_with::serde_as]
581#[derive(clap::Args, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
582#[serde(deny_unknown_fields)]
583#[group(id = "tls-server")]
584#[non_exhaustive]
585pub struct Server {
586	/// Load the given certificate from disk.
587	#[arg(long = "tls-cert", id = "tls-cert", env = "MOQ_SERVER_TLS_CERT")]
588	#[serde(default, skip_serializing_if = "Vec::is_empty")]
589	#[serde_as(as = "serde_with::OneOrMany<_>")]
590	pub cert: Vec<PathBuf>,
591
592	/// Load the given key from disk.
593	#[arg(long = "tls-key", id = "tls-key", env = "MOQ_SERVER_TLS_KEY")]
594	#[serde(default, skip_serializing_if = "Vec::is_empty")]
595	#[serde_as(as = "serde_with::OneOrMany<_>")]
596	pub key: Vec<PathBuf>,
597
598	/// Or generate a new certificate and key with the given hostnames.
599	/// This won't be valid unless the client uses the fingerprint or disables verification.
600	#[arg(
601		long = "tls-generate",
602		id = "tls-generate",
603		value_delimiter = ',',
604		env = "MOQ_SERVER_TLS_GENERATE"
605	)]
606	#[serde(default, skip_serializing_if = "Vec::is_empty")]
607	#[serde_as(as = "serde_with::OneOrMany<_>")]
608	pub generate: Vec<String>,
609
610	/// PEM file(s) of root CAs for validating optional client certificates (mTLS).
611	///
612	/// When set, clients *may* present a certificate during the TLS handshake.
613	/// Valid presentations are reported via [`crate::Request::peer_identity`]
614	/// and can be used by the application to grant elevated access. Clients that
615	/// do not present a certificate are unaffected.
616	///
617	/// Plain-TLS listeners built via [`Self::server_config`] also use these roots
618	/// for optional mTLS.
619	#[arg(
620		long = "server-tls-root",
621		id = "server-tls-root",
622		value_delimiter = ',',
623		env = "MOQ_SERVER_TLS_ROOT"
624	)]
625	#[serde(default, skip_serializing_if = "Vec::is_empty")]
626	#[serde_as(as = "serde_with::OneOrMany<_>")]
627	pub root: Vec<PathBuf>,
628}
629
630impl Server {
631	/// Load all configured root CAs into a [`rustls::RootCertStore`].
632	pub fn load_roots(&self) -> Result<rustls::RootCertStore> {
633		let mut roots = rustls::RootCertStore::empty();
634		for path in &self.root {
635			let certs = read_certs(path)?;
636			if certs.is_empty() {
637				return Err(Error::Empty);
638			}
639			for cert in certs {
640				roots.add(cert).map_err(Error::AddRoot)?;
641			}
642		}
643		Ok(roots)
644	}
645
646	/// Build a [`rustls::ServerConfig`] for a plain-TLS (non-QUIC) server, e.g. an
647	/// RTMPS or HTTPS listener fronting the QUIC endpoint, reusing the QUIC
648	/// backend's certificate handling: on-disk `cert`/`key` pairs, `generate`
649	/// self-signed certs, and optional mTLS `root` client CAs.
650	///
651	/// `alpn` sets the advertised ALPN protocols (e.g.
652	/// `vec![b"h2".to_vec(), b"http/1.1".to_vec()]`); pass an empty list for a
653	/// protocol like RTMPS that doesn't use ALPN.
654	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
655	pub fn server_config(&self, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
656		server_config(self, alpn)
657	}
658}
659
660/// Build a [`rustls::ServerConfig`] from a [`Server`] for a plain-TLS listener.
661#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
662fn server_config(config: &Server, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
663	let provider = crypto::provider();
664
665	let certs = ServeCerts::new(provider.clone());
666	certs.load_certs(config)?;
667	let certs = Arc::new(certs);
668
669	// TCP can negotiate TLS 1.2 as well as 1.3, unlike QUIC which is 1.3-only.
670	let builder =
671		rustls::ServerConfig::builder_with_provider(provider.clone()).with_safe_default_protocol_versions()?;
672
673	let mut tls = if config.root.is_empty() {
674		builder.with_no_client_auth().with_cert_resolver(certs)
675	} else {
676		let roots = config.load_roots()?;
677		let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider)
678			.allow_unauthenticated()
679			.build()
680			.map_err(Error::ClientVerifier)?;
681		builder.with_client_cert_verifier(verifier).with_cert_resolver(certs)
682	};
683
684	tls.alpn_protocols = alpn;
685	Ok(Arc::new(tls))
686}
687
688/// A peer's validated client-certificate chain from the mTLS handshake.
689///
690/// Returned by [`crate::Request::peer_identity`] when the peer presented a
691/// certificate that chained to a configured [`Server::root`]. Owns the chain
692/// (leaf first) so callers can inspect it, e.g. [`expiry`](Self::expiry),
693/// without re-parsing the type-erased QUIC identity.
694#[derive(Clone)]
695pub struct PeerIdentity {
696	chain: Vec<CertificateDer<'static>>,
697}
698
699impl PeerIdentity {
700	/// Wrap the type-erased identity from `quinn::Connection::peer_identity`.
701	/// Returns `None` if the peer presented no certificate or the identity is
702	/// not a certificate chain.
703	#[cfg(any(feature = "quinn", feature = "noq"))]
704	pub(crate) fn from_any(identity: Option<Box<dyn std::any::Any>>) -> Option<Self> {
705		let chain = identity?.downcast::<Vec<CertificateDer<'static>>>().ok()?;
706		Some(Self { chain: *chain })
707	}
708
709	/// Wrap a certificate chain already exposed by a QUIC backend.
710	#[cfg(feature = "quiche")]
711	pub(crate) fn from_chain(chain: Vec<CertificateDer<'static>>) -> Self {
712		Self { chain }
713	}
714
715	/// The validated certificate chain, leaf first.
716	///
717	/// Exposes [`rustls::pki_types::CertificateDer`] directly (already part of
718	/// this crate's public API via the `rustls` re-export), so a major `rustls`
719	/// bump is a breaking change for consumers of this method.
720	pub fn chain(&self) -> &[CertificateDer<'static>] {
721		&self.chain
722	}
723
724	/// The leaf certificate's `notAfter`, if it parses. A `notAfter` before the
725	/// Unix epoch is reported as `None`.
726	pub fn expiry(&self) -> Option<std::time::SystemTime> {
727		use std::time::{Duration, UNIX_EPOCH};
728
729		let leaf = self.chain.first()?;
730		let (_, cert) = x509_parser::parse_x509_certificate(leaf).ok()?;
731		let secs = u64::try_from(cert.validity().not_after.timestamp()).ok()?;
732		Some(UNIX_EPOCH + Duration::from_secs(secs))
733	}
734}
735
736/// The certificates a server is currently serving.
737#[derive(Debug, Default)]
738pub(crate) struct Info {
739	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
740	pub(crate) certs: Vec<Arc<rustls::sign::CertifiedKey>>,
741	pub(crate) fingerprints: Vec<String>,
742}
743
744/// A live handle to the certificates a [`crate::Server`] is serving.
745///
746/// Cheap to clone, and every read reflects the latest hot reload of the files on
747/// disk, so a caller can build one at startup and hold it for the process
748/// lifetime. Obtained from [`crate::Server::certificates`].
749#[derive(Clone, Debug)]
750pub struct Certificates {
751	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
752	info: Arc<RwLock<Info>>,
753}
754
755impl Certificates {
756	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
757	pub(crate) fn new(info: Arc<RwLock<Info>>) -> Self {
758		Self { info }
759	}
760
761	/// An empty set, used when no TLS-bearing backend is configured.
762	pub(crate) fn empty() -> Self {
763		Self {
764			#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
765			info: Arc::new(RwLock::new(Info::default())),
766		}
767	}
768
769	/// The SHA-256 fingerprints of the certificates being served right now, hex
770	/// encoded, one per certificate and in configuration order.
771	///
772	/// Empty when the server has no TLS-bearing backend. Re-read this per use
773	/// rather than caching it: a cert rotation on disk changes the values.
774	pub fn fingerprints(&self) -> Vec<String> {
775		#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
776		{
777			// A panicking writer can't leave the cert list half-updated (it is
778			// replaced wholesale), so a poisoned lock is still safe to read.
779			let info = self.info.read().unwrap_or_else(std::sync::PoisonError::into_inner);
780			info.fingerprints.clone()
781		}
782		#[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
783		Vec::new()
784	}
785}
786
787// ── NoCertificateVerification ───────────────────────────────────────
788
789#[derive(Debug)]
790struct NoCertificateVerification(crypto::Provider);
791
792impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
793	fn verify_server_cert(
794		&self,
795		_end_entity: &CertificateDer<'_>,
796		_intermediates: &[CertificateDer<'_>],
797		_server_name: &ServerName<'_>,
798		_ocsp: &[u8],
799		_now: UnixTime,
800	) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
801		Ok(rustls::client::danger::ServerCertVerified::assertion())
802	}
803
804	fn verify_tls12_signature(
805		&self,
806		message: &[u8],
807		cert: &CertificateDer<'_>,
808		dss: &rustls::DigitallySignedStruct,
809	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
810		rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
811	}
812
813	fn verify_tls13_signature(
814		&self,
815		message: &[u8],
816		cert: &CertificateDer<'_>,
817		dss: &rustls::DigitallySignedStruct,
818	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
819		rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
820	}
821
822	fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
823		self.0.signature_verification_algorithms.supported_schemes()
824	}
825}
826
827// ── FingerprintVerifier ─────────────────────────────────────────────
828
829#[derive(Debug)]
830pub(crate) struct FingerprintVerifier {
831	provider: crypto::Provider,
832	fingerprints: Vec<Vec<u8>>,
833}
834
835impl FingerprintVerifier {
836	pub fn new(provider: crypto::Provider, fingerprints: Vec<Vec<u8>>) -> Self {
837		Self { provider, fingerprints }
838	}
839}
840
841impl rustls::client::danger::ServerCertVerifier for FingerprintVerifier {
842	fn verify_server_cert(
843		&self,
844		end_entity: &CertificateDer<'_>,
845		_intermediates: &[CertificateDer<'_>],
846		_server_name: &ServerName<'_>,
847		_ocsp: &[u8],
848		_now: UnixTime,
849	) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
850		let fingerprint = crypto::sha256(&self.provider, end_entity);
851		if self.fingerprints.iter().any(|fp| fingerprint.as_ref() == fp.as_slice()) {
852			Ok(rustls::client::danger::ServerCertVerified::assertion())
853		} else {
854			Err(rustls::Error::General("fingerprint mismatch".into()))
855		}
856	}
857
858	fn verify_tls12_signature(
859		&self,
860		message: &[u8],
861		cert: &CertificateDer<'_>,
862		dss: &rustls::DigitallySignedStruct,
863	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
864		rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
865	}
866
867	fn verify_tls13_signature(
868		&self,
869		message: &[u8],
870		cert: &CertificateDer<'_>,
871		dss: &rustls::DigitallySignedStruct,
872	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
873		rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
874	}
875
876	fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
877		self.provider.signature_verification_algorithms.supported_schemes()
878	}
879}
880
881#[cfg(test)]
882#[cfg(all(any(feature = "quinn", feature = "noq", feature = "quiche"), feature = "aws-lc-rs"))]
883mod tests {
884	use super::*;
885	use rustls::client::danger::ServerCertVerifier;
886	use rustls::pki_types::ServerName;
887
888	fn self_signed() -> CertificateDer<'static> {
889		let key = rcgen::KeyPair::generate().unwrap();
890		let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
891		params.self_signed(&key).unwrap().into()
892	}
893
894	#[cfg(any(feature = "quinn", feature = "noq"))]
895	#[test]
896	fn peer_identity_expiry_reads_not_after() {
897		// notAfter at a whole second so the round-trip is exact.
898		let not_after = ::time::OffsetDateTime::from_unix_timestamp(2_000_000_000).unwrap();
899
900		let key = rcgen::KeyPair::generate().unwrap();
901		let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
902		params.not_after = not_after;
903		let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into();
904
905		// quinn/noq hand back the chain as a boxed Vec<CertificateDer>.
906		let identity: Box<dyn std::any::Any> = Box::new(vec![cert]);
907		let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed");
908		let expiry = parsed.expiry().expect("expiry parsed");
909		assert_eq!(
910			expiry.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
911			2_000_000_000
912		);
913	}
914
915	#[cfg(any(feature = "quinn", feature = "noq"))]
916	#[test]
917	fn peer_identity_none_without_chain() {
918		assert!(PeerIdentity::from_any(None).is_none());
919		// A wrong downcast type (not a cert chain) yields None rather than panicking.
920		let bogus: Box<dyn std::any::Any> = Box::new(42u32);
921		assert!(PeerIdentity::from_any(Some(bogus)).is_none());
922	}
923
924	#[test]
925	fn fingerprint_verifier_matches_and_rejects() {
926		let provider = crypto::provider();
927		let cert = self_signed();
928		let fingerprint = crypto::sha256(&provider, cert.as_ref()).as_ref().to_vec();
929
930		let name = ServerName::try_from("localhost").unwrap();
931		let now = UnixTime::now();
932
933		let verifier = FingerprintVerifier::new(provider.clone(), vec![fingerprint]);
934		assert!(verifier.verify_server_cert(&cert, &[], &name, &[], now).is_ok());
935
936		// A different leaf certificate must not satisfy the pin.
937		let other = self_signed();
938		assert!(verifier.verify_server_cert(&other, &[], &name, &[], now).is_err());
939	}
940
941	#[test]
942	fn build_installs_fingerprint_verifier() {
943		let cert = self_signed();
944		let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
945
946		// A bogus hash still builds; verification happens at handshake time.
947		let config = Client {
948			fingerprint: vec![fingerprint],
949			..Default::default()
950		};
951		assert!(config.build().is_ok());
952	}
953
954	#[test]
955	fn build_rejects_invalid_fingerprint_hex() {
956		let config = Client {
957			fingerprint: vec!["not-hex".to_string()],
958			..Default::default()
959		};
960		assert!(matches!(config.build(), Err(Error::Fingerprint(_))));
961	}
962
963	#[test]
964	fn build_rejects_wrong_length_fingerprint() {
965		// Valid hex, but only 2 bytes instead of 32.
966		let config = Client {
967			fingerprint: vec!["abcd".to_string()],
968			..Default::default()
969		};
970		assert!(matches!(config.build(), Err(Error::FingerprintLength(2))));
971	}
972
973	#[test]
974	fn build_rejects_no_roots() {
975		// System roots disabled with no custom root and no alternate verifier:
976		// nothing could ever verify, so reject up front.
977		let config = Client {
978			system_roots: Some(false),
979			..Default::default()
980		};
981		assert!(matches!(config.build(), Err(Error::NoRoots)));
982	}
983
984	#[test]
985	fn build_allows_no_roots_when_verification_overridden() {
986		// disable_verify swaps in its own verifier, so an empty store is fine.
987		let config = Client {
988			system_roots: Some(false),
989			disable_verify: Some(true),
990			..Default::default()
991		};
992		assert!(config.build().is_ok());
993
994		// Same for fingerprint pinning.
995		let cert = self_signed();
996		let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
997		let config = Client {
998			system_roots: Some(false),
999			fingerprint: vec![fingerprint],
1000			..Default::default()
1001		};
1002		assert!(config.build().is_ok());
1003	}
1004
1005	#[test]
1006	fn build_rejects_fingerprint_with_roots() {
1007		let cert = self_signed();
1008		let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1009
1010		// Fingerprint pinning bypasses the CA chain, so combining it with roots
1011		// is rejected rather than silently ignoring one of them.
1012		let with_system = Client {
1013			fingerprint: vec![fingerprint.clone()],
1014			system_roots: Some(true),
1015			..Default::default()
1016		};
1017		assert!(matches!(with_system.build(), Err(Error::FingerprintWithRoots)));
1018
1019		// The conflict is detected before any root file is read, so the path
1020		// need not exist.
1021		let with_custom = Client {
1022			fingerprint: vec![fingerprint],
1023			root: vec![PathBuf::from("/does-not-exist.pem")],
1024			..Default::default()
1025		};
1026		assert!(matches!(with_custom.build(), Err(Error::FingerprintWithRoots)));
1027	}
1028
1029	/// Write a self-signed cert to a temp PEM file, returning the keep-alive
1030	/// handle alongside its path.
1031	fn self_signed_root() -> (tempfile::NamedTempFile, PathBuf) {
1032		use std::io::Write;
1033		let key = rcgen::KeyPair::generate().unwrap();
1034		let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
1035		let cert = params.self_signed(&key).unwrap();
1036		let mut file = tempfile::NamedTempFile::new().unwrap();
1037		file.write_all(cert.pem().as_bytes()).unwrap();
1038		let path = file.path().to_path_buf();
1039		(file, path)
1040	}
1041
1042	#[test]
1043	fn build_uses_platform_verifier_by_default() {
1044		// No custom roots, system trust on: resolves to the OS platform verifier
1045		// (bundled Mozilla roots on Android) and must build cleanly everywhere.
1046		assert!(Client::default().build().is_ok());
1047	}
1048
1049	#[test]
1050	fn build_with_custom_roots_only() {
1051		// A custom root with system trust left at its default disables the system
1052		// roots, verifying against the custom PEM alone.
1053		let (_keep, path) = self_signed_root();
1054		let config = Client {
1055			root: vec![path],
1056			..Default::default()
1057		};
1058		assert!(config.build().is_ok());
1059	}
1060
1061	#[test]
1062	fn build_with_custom_and_system_roots() {
1063		// Custom roots layered on top of system trust: exercises the platform
1064		// verifier's extra-roots path (or the bundled roots plus custom on Android).
1065		let (_keep, path) = self_signed_root();
1066		let config = Client {
1067			root: vec![path],
1068			system_roots: Some(true),
1069			..Default::default()
1070		};
1071		assert!(config.build().is_ok());
1072	}
1073}
1074
1075// ── ServeCerts ──────────────────────────────────────────────────────
1076
1077#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1078#[derive(Debug)]
1079pub(crate) struct ServeCerts {
1080	pub info: Arc<RwLock<Info>>,
1081	provider: crypto::Provider,
1082}
1083
1084#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1085impl ServeCerts {
1086	pub fn new(provider: crypto::Provider) -> Self {
1087		Self {
1088			info: Arc::new(RwLock::new(Info::default())),
1089			provider,
1090		}
1091	}
1092
1093	pub fn load_certs(&self, config: &Server) -> Result<()> {
1094		if config.cert.len() != config.key.len() {
1095			return Err(Error::CertKeyCountMismatch);
1096		}
1097		if config.cert.is_empty() && config.generate.is_empty() {
1098			return Err(Error::NoCertSource);
1099		}
1100
1101		let mut certs = Vec::new();
1102
1103		// Load the certificate and key files based on their index.
1104		for (cert, key) in config.cert.iter().zip(config.key.iter()) {
1105			certs.push(Arc::new(self.load(cert, key)?));
1106		}
1107
1108		// Generate a new certificate if requested.
1109		if !config.generate.is_empty() {
1110			certs.push(Arc::new(self.generate(&config.generate)?));
1111		}
1112
1113		self.set_certs(certs);
1114		Ok(())
1115	}
1116
1117	// Load a certificate and corresponding key from a file, but don't add it to the certs
1118	fn load(&self, chain_path: &Path, key_path: &Path) -> Result<rustls::sign::CertifiedKey> {
1119		let chain = read_certs(chain_path)?;
1120		if chain.is_empty() {
1121			return Err(Error::Empty);
1122		}
1123
1124		// Read the PEM private key
1125		let key = PrivateKeyDer::from_pem_file(key_path).map_err(Error::Key)?;
1126		let key = self.provider.key_provider.load_private_key(key)?;
1127
1128		let certified_key = rustls::sign::CertifiedKey::new(chain, key);
1129
1130		certified_key.keys_match().map_err(|source| Error::KeyMismatch {
1131			key: key_path.to_path_buf(),
1132			cert: chain_path.to_path_buf(),
1133			source,
1134		})?;
1135
1136		Ok(certified_key)
1137	}
1138
1139	#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
1140	fn generate(&self, hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1141		let key_pair = rcgen::KeyPair::generate()?;
1142
1143		let mut params = rcgen::CertificateParams::new(hostnames)?;
1144
1145		// Make the certificate valid for two weeks, starting yesterday (in case of clock drift).
1146		// WebTransport certificates MUST be valid for two weeks at most.
1147		params.not_before = ::time::OffsetDateTime::now_utc() - ::time::Duration::days(1);
1148		params.not_after = params.not_before + ::time::Duration::days(14);
1149
1150		// Generate the certificate
1151		let cert = params.self_signed(&key_pair)?;
1152
1153		// Convert the rcgen type to the rustls type.
1154		let key_der = key_pair.serialized_der().to_vec();
1155		let key_der = PrivatePkcs8KeyDer::from(key_der);
1156		let key = self.provider.key_provider.load_private_key(key_der.into())?;
1157
1158		// Create a rustls::sign::CertifiedKey
1159		Ok(rustls::sign::CertifiedKey::new(vec![cert.into()], key))
1160	}
1161
1162	#[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
1163	fn generate(&self, _hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1164		Err(Error::NoCryptoProvider)
1165	}
1166
1167	// Replace the certificates
1168	pub fn set_certs(&self, certs: Vec<Arc<rustls::sign::CertifiedKey>>) {
1169		let fingerprints = certs
1170			.iter()
1171			.map(|ck| {
1172				let fingerprint = crate::crypto::sha256(&self.provider, ck.cert[0].as_ref());
1173				hex::encode(fingerprint)
1174			})
1175			.collect();
1176
1177		let mut info = self.info.write().expect("info write lock poisoned");
1178		info.certs = certs;
1179		info.fingerprints = fingerprints;
1180	}
1181
1182	// Return the best certificate for the given ClientHello.
1183	fn best_certificate(
1184		&self,
1185		client_hello: &rustls::server::ClientHello<'_>,
1186	) -> Option<Arc<rustls::sign::CertifiedKey>> {
1187		let server_name = client_hello.server_name()?;
1188		let dns_name = rustls::pki_types::ServerName::try_from(server_name).ok()?;
1189
1190		for ck in self.info.read().expect("info read lock poisoned").certs.iter() {
1191			let leaf: webpki::EndEntityCert = ck
1192				.end_entity_cert()
1193				.expect("missing certificate")
1194				.try_into()
1195				.expect("failed to parse certificate");
1196
1197			if leaf.verify_is_valid_for_subject_name(&dns_name).is_ok() {
1198				return Some(ck.clone());
1199			}
1200		}
1201
1202		None
1203	}
1204}
1205
1206#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1207impl rustls::server::ResolvesServerCert for ServeCerts {
1208	fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<rustls::sign::CertifiedKey>> {
1209		if let Some(cert) = self.best_certificate(&client_hello) {
1210			return Some(cert);
1211		}
1212
1213		// If this happens, it means the client was trying to connect to an unknown hostname.
1214		// We do our best and return the first certificate.
1215		tracing::warn!(server_name = ?client_hello.server_name(), "no SNI certificate found");
1216
1217		self.info
1218			.read()
1219			.expect("info read lock poisoned")
1220			.certs
1221			.first()
1222			.cloned()
1223	}
1224}
1225
1226// ── reload_certs ────────────────────────────────────────────────────
1227
1228/// Watch the on-disk cert/key files and reload them whenever they change.
1229///
1230/// Reacting to the filesystem means cert-manager, Kubernetes secret mounts, and
1231/// `mv`-into-place rotate certs with no external signal. Returns immediately when
1232/// only generated certs are configured: there's nothing on disk to watch.
1233#[cfg(any(feature = "quinn", feature = "noq"))]
1234pub(crate) async fn reload_certs(certs: Arc<ServeCerts>, tls_config: Server) {
1235	let paths: Vec<PathBuf> = tls_config.cert.iter().chain(tls_config.key.iter()).cloned().collect();
1236	if paths.is_empty() {
1237		return;
1238	}
1239
1240	let mut watcher = match crate::watch::FileWatcher::new(&paths) {
1241		Ok(watcher) => watcher,
1242		Err(err) => {
1243			tracing::error!(%err, "failed to watch certificate files; hot reload disabled");
1244			return;
1245		}
1246	};
1247
1248	loop {
1249		watcher.changed().await;
1250		tracing::info!("reloading server certificates");
1251
1252		if let Err(err) = certs.load_certs(&tls_config) {
1253			tracing::warn!(%err, "failed to reload server certificates");
1254		}
1255	}
1256}