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, keys, and custom root CAs loaded from disk are normally hot
9//! reloaded for new handshakes. Quiche servers are the exception: all inbound
10//! TLS material is fixed when the listener is built. [`Certificates`] reads the
11//! current served set back out.
12
13use crate::crypto;
14use rustls::pki_types::pem::PemObject;
15use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
16use std::path::{Path, PathBuf};
17use std::sync::{Arc, RwLock};
18use std::{fs, io};
19
20#[cfg(all(
21	any(feature = "quinn", feature = "noq", feature = "quiche"),
22	any(feature = "aws-lc-rs", feature = "ring")
23))]
24use rustls::pki_types::PrivatePkcs8KeyDer;
25/// Errors loading or generating TLS certificates and keys.
26///
27/// Shared by the client TLS config and the quinn/noq servers so each backend's
28/// error type can compose it via `#[from]`.
29#[derive(Debug, thiserror::Error)]
30#[non_exhaustive]
31pub enum Error {
32	/// A certificate file couldn't be opened, usually a bad path or permissions.
33	#[error("failed to open certificate file")]
34	Open(#[source] std::io::Error),
35
36	/// A certificate or key file was opened but couldn't be read to the end.
37	#[error("failed to read file")]
38	ReadFile(#[source] std::io::Error),
39
40	/// A file's contents aren't valid PEM certificates.
41	#[error("failed to read certificates")]
42	Read(#[source] rustls::pki_types::pem::Error),
43
44	/// A file's contents aren't a valid PEM private key.
45	#[error("failed to parse private key")]
46	Key(#[source] rustls::pki_types::pem::Error),
47
48	/// A PEM file parsed cleanly but held no certificates.
49	#[error("no certificates found")]
50	Empty,
51
52	/// A root PEM file parsed cleanly but held no certificates, so it would trust nothing.
53	#[error("no roots found in {}", .0.display())]
54	EmptyRoots(PathBuf),
55
56	/// Nothing is configured that could ever verify a server certificate.
57	#[error(
58		"no trusted roots: provide --client-tls-root, enable --client-tls-system-roots, or use --client-tls-fingerprint / --client-tls-disable-verify"
59	)]
60	NoRoots,
61
62	/// A configured fingerprint isn't valid hex.
63	#[error("invalid TLS fingerprint (expected hex-encoded SHA-256)")]
64	Fingerprint(#[source] hex::FromHexError),
65
66	/// A configured fingerprint is valid hex but the wrong size for a SHA-256 digest.
67	#[error("invalid TLS fingerprint length: expected 32 bytes (SHA-256), got {0}")]
68	FingerprintLength(usize),
69
70	/// Fingerprint pinning was combined with CA roots. Pinning bypasses the chain, so one of
71	/// the two would be silently ignored.
72	#[error(
73		"--client-tls-fingerprint cannot be combined with --client-tls-root or --client-tls-system-roots: fingerprint pinning bypasses CA verification"
74	)]
75	FingerprintWithRoots,
76
77	/// Trust material was configured alongside the flag that ignores all of it.
78	#[error(
79		"--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"
80	)]
81	DisableVerifyWithTrust,
82
83	/// A root certificate parsed as PEM but rustls rejected it as a trust anchor.
84	#[error("failed to add root certificate")]
85	AddRoot(#[source] rustls::Error),
86
87	/// The JNI call in [`init_android`] failed, so the platform verifier is unavailable.
88	#[cfg(target_os = "android")]
89	#[error("failed to initialize the Android platform verifier")]
90	AndroidInit(#[source] jni::errors::Error),
91
92	/// rustls rejected the mTLS client certificate and key, e.g. they don't match.
93	#[error("failed to configure client certificate")]
94	ClientAuth(#[source] rustls::Error),
95
96	/// Only one half of the mTLS client identity was given; it needs both a cert and a key.
97	#[error("both --client-tls-cert and --client-tls-key must be provided")]
98	IncompleteClientAuth,
99
100	/// The server was given a different number of certificates than keys. They pair by index.
101	#[error("must provide both cert and key")]
102	CertKeyCountMismatch,
103
104	/// The server has no certificate to serve: no cert/key pair and no hostnames to generate one for.
105	#[error("must provide at least one cert/key pair or generate entry")]
106	NoCertSource,
107
108	/// A server cert/key pair was paired up by index but the key isn't the certificate's.
109	#[error("private key {} doesn't match certificate {}", key.display(), cert.display())]
110	KeyMismatch {
111		/// Path of the private key file.
112		key: PathBuf,
113		/// Path of the certificate file it was paired with.
114		cert: PathBuf,
115		/// Why rustls says the two don't match.
116		#[source]
117		source: rustls::Error,
118	},
119
120	/// A rustls error with no more specific context, e.g. building a config.
121	#[error(transparent)]
122	Rustls(#[from] rustls::Error),
123
124	/// The mTLS client-certificate verifier couldn't be built from the configured roots.
125	#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
126	#[error("failed to build client certificate verifier")]
127	ClientVerifier(#[source] rustls::server::VerifierBuilderError),
128
129	/// The server-certificate verifier couldn't be built from the configured roots.
130	#[error("failed to build server certificate verifier")]
131	ServerVerifier(#[source] rustls::client::VerifierBuilderError),
132
133	/// Generating a self-signed certificate failed.
134	#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
135	#[error(transparent)]
136	Rcgen(#[from] rcgen::Error),
137
138	/// The crate was built without a crypto provider, so no TLS is possible.
139	#[error("no crypto provider available; enable aws-lc-rs or ring feature")]
140	NoCryptoProvider,
141}
142
143/// Convenience alias for results produced by this module.
144pub type Result<T> = std::result::Result<T, Error>;
145
146/// Parse a hex-encoded SHA-256 certificate fingerprint.
147pub fn parse_fingerprint(value: &str) -> Result<[u8; 32]> {
148	let bytes = hex::decode(value.trim()).map_err(Error::Fingerprint)?;
149	bytes.try_into().map_err(|v: Vec<u8>| Error::FingerprintLength(v.len()))
150}
151
152/// Read a PEM file into its list of certificates.
153pub(crate) fn read_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>> {
154	let file = fs::File::open(path).map_err(Error::Open)?;
155	let mut reader = io::BufReader::new(file);
156	CertificateDer::pem_reader_iter(&mut reader)
157		.collect::<std::result::Result<_, _>>()
158		.map_err(Error::Read)
159}
160
161/// Load every configured custom root, rejecting a path that contains no certificates.
162fn read_roots(paths: &[PathBuf]) -> Result<Vec<CertificateDer<'static>>> {
163	let mut roots = Vec::new();
164	for path in paths {
165		let certs = read_certs(path)?;
166		if certs.is_empty() {
167			return Err(Error::EmptyRoots(path.clone()));
168		}
169		roots.extend(certs);
170	}
171	Ok(roots)
172}
173
174// ── Client ──────────────────────────────────────────────────────────
175
176/// TLS configuration for the client.
177#[serde_with::serde_as]
178#[derive(Clone, Default, Debug, clap::Args, serde::Serialize, serde::Deserialize)]
179#[serde(default, deny_unknown_fields)]
180#[group(id = "tls-client")]
181#[non_exhaustive]
182pub struct Client {
183	/// Trust the TLS root at this path, encoded as PEM.
184	///
185	/// This value can be provided multiple times for multiple roots.
186	/// In config files, accepts either a single string or a TOML array.
187	///
188	/// These roots are added on top of the system roots. By default the system
189	/// roots are only loaded when no custom root is given, so passing a root
190	/// replaces them; set `--client-tls-system-roots` to trust both (e.g. to reach a
191	/// local relay with a private CA and a remote one with a public CA).
192	/// Files are hot reloaded for new connections, retaining the last valid roots
193	/// if a rotation is temporarily missing or malformed.
194	#[serde(skip_serializing_if = "Vec::is_empty")]
195	#[arg(id = "client-tls-root", long = "client-tls-root", env = "MOQ_CLIENT_TLS_ROOT")]
196	#[serde_as(as = "serde_with::OneOrMany<_>")]
197	pub root: Vec<PathBuf>,
198
199	/// Also trust the platform's native root certificates.
200	///
201	/// Defaults to enabled only when no `--client-tls-root` is given. Set it
202	/// explicitly to trust the system roots alongside any custom roots, or set it
203	/// to false to trust only the custom roots. Trusting neither (no custom root
204	/// and system roots disabled) is rejected, since verification could never pass.
205	#[serde(skip_serializing_if = "Option::is_none")]
206	#[arg(
207		id = "client-tls-system-roots",
208		long = "client-tls-system-roots",
209		env = "MOQ_CLIENT_TLS_SYSTEM_ROOTS",
210		default_missing_value = "true",
211		num_args = 0..=1,
212		require_equals = true,
213		value_parser = clap::value_parser!(bool),
214	)]
215	pub system_roots: Option<bool>,
216
217	/// Pin the peer to a certificate with one of these SHA-256 fingerprints, encoded as hex.
218	///
219	/// This is the native equivalent of the browser's WebTransport `serverCertificateHashes`,
220	/// and accepts the same values a server reports via its certificate fingerprints. Use it to
221	/// trust a self-signed certificate without disabling verification or fetching the hash over
222	/// an insecure `http://` request. When set, the normal CA/root chain is bypassed: only the
223	/// leaf certificate's fingerprint is checked.
224	///
225	/// This value can be provided multiple times to accept any of several fingerprints (e.g.
226	/// across a certificate rotation). In config files, accepts either a single string or a TOML array.
227	#[serde(skip_serializing_if = "Vec::is_empty")]
228	#[arg(
229		id = "client-tls-fingerprint",
230		long = "client-tls-fingerprint",
231		env = "MOQ_CLIENT_TLS_FINGERPRINT"
232	)]
233	#[serde_as(as = "serde_with::OneOrMany<_>")]
234	pub fingerprint: Vec<String>,
235
236	/// PEM file containing the client certificate chain for mTLS.
237	///
238	/// Only certificates are extracted; any private keys in the file are ignored.
239	/// Must be paired with `--client-tls-key`.
240	#[serde(skip_serializing_if = "Option::is_none")]
241	#[arg(id = "client-tls-cert", long = "client-tls-cert", env = "MOQ_CLIENT_TLS_CERT")]
242	pub cert: Option<PathBuf>,
243
244	/// PEM file containing the private key for mTLS.
245	///
246	/// Only the private key is extracted; any certificates in the file are ignored.
247	/// Must be paired with `--client-tls-cert`.
248	#[serde(skip_serializing_if = "Option::is_none")]
249	#[arg(id = "client-tls-key", long = "client-tls-key", env = "MOQ_CLIENT_TLS_KEY")]
250	pub key: Option<PathBuf>,
251
252	/// Danger: Disable TLS certificate verification.
253	///
254	/// Fine for local development and between relays, but should be used in caution in production.
255	#[serde(skip_serializing_if = "Option::is_none")]
256	#[arg(
257		id = "client-tls-disable-verify",
258		long = "client-tls-disable-verify",
259		env = "MOQ_CLIENT_TLS_DISABLE_VERIFY",
260		default_missing_value = "true",
261		num_args = 0..=1,
262		require_equals = true,
263		value_parser = clap::value_parser!(bool),
264	)]
265	pub disable_verify: Option<bool>,
266
267	/// Override the TLS SNI and certificate verification hostname for outbound connections.
268	///
269	/// When unset, the connect URL's host is used (default behavior). Useful when dialing a
270	/// raw IP address but needing to present/verify a DNS name the server certificate covers.
271	#[serde(skip_serializing_if = "Option::is_none")]
272	#[arg(
273		id = "client-tls-host-name",
274		long = "client-tls-host-name",
275		env = "MOQ_CLIENT_TLS_HOST_NAME"
276	)]
277	pub host_name: Option<String>,
278
279	/// Deprecated `--tls-*` spellings, folded into the canonical fields above with
280	/// a warning. Private and hidden so they stay off the public surface; not a
281	/// TOML field (config files use the canonical names).
282	#[command(flatten)]
283	#[serde(skip)]
284	deprecated: Deprecated,
285}
286
287/// Holds the deprecated bare `--tls-*` flag spellings (renamed to `--client-tls-*`).
288/// Flattened into [`Client`] so they keep parsing; folded into the canonical
289/// fields by [`Client::build`] with a deprecation warning. No env (the env names
290/// were never renamed) and no TOML.
291#[derive(Clone, Default, Debug, clap::Args)]
292struct Deprecated {
293	#[arg(long = "tls-root", hide = true)]
294	root: Vec<PathBuf>,
295
296	#[arg(
297		long = "tls-system-roots",
298		hide = true,
299		default_missing_value = "true",
300		num_args = 0..=1,
301		require_equals = true,
302		value_parser = clap::value_parser!(bool),
303	)]
304	system_roots: Option<bool>,
305
306	#[arg(long = "tls-fingerprint", hide = true)]
307	fingerprint: Vec<String>,
308
309	#[arg(
310		long = "tls-disable-verify",
311		hide = true,
312		default_missing_value = "true",
313		num_args = 0..=1,
314		require_equals = true,
315		value_parser = clap::value_parser!(bool),
316	)]
317	disable_verify: Option<bool>,
318}
319
320/// The last valid contents of a set of custom root files.
321///
322/// Quiche consumes concrete DER roots per connection, while rustls consumes a
323/// verifier. Keeping the parsed roots here gives both paths identical
324/// last-known-good behavior when a file is briefly absent or incomplete during
325/// rotation.
326#[derive(Clone)]
327pub(crate) struct CustomRoots {
328	paths: Vec<PathBuf>,
329	current: Arc<RwLock<Vec<CertificateDer<'static>>>>,
330}
331
332impl CustomRoots {
333	fn new(paths: Vec<PathBuf>) -> Result<Self> {
334		let current = read_roots(&paths)?;
335		Ok(Self {
336			paths,
337			current: Arc::new(RwLock::new(current)),
338		})
339	}
340
341	fn load(&self) -> Result<Vec<CertificateDer<'static>>> {
342		read_roots(&self.paths)
343	}
344
345	fn replace(&self, roots: Vec<CertificateDer<'static>>) {
346		*self.current.write().unwrap_or_else(std::sync::PoisonError::into_inner) = roots;
347	}
348
349	pub(crate) fn current(&self) -> Vec<CertificateDer<'static>> {
350		self.current
351			.read()
352			.unwrap_or_else(std::sync::PoisonError::into_inner)
353			.clone()
354	}
355
356	/// Refresh from disk, retaining and returning the last valid roots on failure.
357	#[cfg(feature = "quiche")]
358	pub(crate) fn refresh(&self) -> Vec<CertificateDer<'static>> {
359		self.refresh_with(|| self.load())
360	}
361
362	#[cfg(feature = "quiche")]
363	fn refresh_with(
364		&self,
365		load: impl FnOnce() -> Result<Vec<CertificateDer<'static>>>,
366	) -> Vec<CertificateDer<'static>> {
367		let mut current = self.current.write().unwrap_or_else(std::sync::PoisonError::into_inner);
368		match load().and_then(|roots| {
369			root_store(&roots)?;
370			Ok(roots)
371		}) {
372			Ok(roots) => {
373				*current = roots.clone();
374				roots
375			}
376			Err(err) => {
377				tracing::warn!(%err, "failed to reload client root certificates; retaining previous roots");
378				current.clone()
379			}
380		}
381	}
382}
383
384#[cfg(feature = "watch")]
385struct ReloadState<T: ?Sized + Send + Sync + 'static> {
386	current: RwLock<Arc<T>>,
387	build: Box<dyn Fn() -> Result<Arc<T>> + Send + Sync>,
388	role: &'static str,
389}
390
391#[cfg(feature = "watch")]
392impl<T: ?Sized + Send + Sync + 'static> ReloadState<T> {
393	fn reload(&self) {
394		match (self.build)() {
395			Ok(next) => {
396				*self.current.write().unwrap_or_else(std::sync::PoisonError::into_inner) = next;
397				tracing::info!(role = self.role, "reloaded TLS root certificates");
398			}
399			Err(err) => {
400				tracing::warn!(%err, role = self.role, "failed to reload TLS root certificates; retaining previous roots");
401			}
402		}
403	}
404
405	fn current(&self) -> Arc<T> {
406		self.current
407			.read()
408			.unwrap_or_else(std::sync::PoisonError::into_inner)
409			.clone()
410	}
411}
412
413/// A verifier whose implementation is replaced after its root files change.
414#[cfg(feature = "watch")]
415struct Reloading<T: ?Sized + Send + Sync + 'static> {
416	state: Arc<ReloadState<T>>,
417	// Holds the OS watcher alive for exactly as long as the TLS config.
418	_watcher: Option<notify::RecommendedWatcher>,
419}
420
421#[cfg(feature = "watch")]
422impl<T: ?Sized + Send + Sync + 'static> Reloading<T> {
423	fn new(
424		paths: &[PathBuf],
425		initial: Arc<T>,
426		role: &'static str,
427		build: impl Fn() -> Result<Arc<T>> + Send + Sync + 'static,
428	) -> Self {
429		let state = Arc::new(ReloadState {
430			current: RwLock::new(initial),
431			build: Box::new(build),
432			role,
433		});
434
435		let reload = state.clone();
436		let watcher = match crate::watch::callback(paths, move || reload.reload()) {
437			Ok(watcher) => Some(watcher),
438			Err(err) => {
439				tracing::error!(%err, role, "failed to watch TLS root certificates; hot reload disabled");
440				None
441			}
442		};
443
444		Self {
445			state,
446			_watcher: watcher,
447		}
448	}
449
450	fn current(&self) -> Arc<T> {
451		self.state.current()
452	}
453}
454
455#[cfg(feature = "watch")]
456impl<T: ?Sized + Send + Sync + 'static> std::fmt::Debug for Reloading<T> {
457	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458		f.debug_struct("Reloading").field("role", &self.state.role).finish()
459	}
460}
461
462/// The resolved server-certificate verification policy.
463///
464/// Computed once by [Client::verification] and shared by every backend (the
465/// rustls-based quinn/noq via [Client::build], and quiche directly) so they
466/// agree on precedence, the system-roots default, and which flag combinations
467/// are valid.
468#[derive(Clone)]
469pub(crate) enum Verification {
470	/// No verification at all. Insecure; only via `--client-tls-disable-verify`.
471	Disabled,
472
473	/// Pin the leaf certificate by SHA-256. The CA chain is not consulted, so
474	/// this is mutually exclusive with any roots.
475	Fingerprints(Vec<[u8; 32]>),
476
477	/// Standard CA verification. When `system` is set the platform/default trust
478	/// store is trusted too; each backend resolves that its own way (the rustls
479	/// backends use the OS platform verifier, quiche loads the native roots).
480	/// `custom` are extra PEM roots trusted in addition.
481	Roots { custom: CustomRoots, system: bool },
482}
483
484impl Client {
485	/// Log a warning for each deprecated `--tls-*` flag in use. Called once from
486	/// [`Self::verification`], which every backend runs, so a deprecated flag warns once.
487	pub(crate) fn warn_deprecated(&self) {
488		if !self.deprecated.root.is_empty() {
489			tracing::warn!("--tls-root is deprecated; use --client-tls-root");
490		}
491		if self.deprecated.system_roots.is_some() {
492			tracing::warn!("--tls-system-roots is deprecated; use --client-tls-system-roots");
493		}
494		if !self.deprecated.fingerprint.is_empty() {
495			tracing::warn!("--tls-fingerprint is deprecated; use --client-tls-fingerprint");
496		}
497		if self.deprecated.disable_verify.is_some() {
498			tracing::warn!("--tls-disable-verify is deprecated; use --client-tls-disable-verify");
499		}
500	}
501
502	/// Roots from the canonical field plus the deprecated `--tls-root` spelling.
503	pub(crate) fn effective_root(&self) -> Vec<PathBuf> {
504		let mut root = self.root.clone();
505		root.extend(self.deprecated.root.iter().cloned());
506		root
507	}
508
509	/// Fingerprints from the canonical field plus the deprecated `--tls-fingerprint`.
510	pub(crate) fn effective_fingerprint(&self) -> Vec<String> {
511		let mut fp = self.fingerprint.clone();
512		fp.extend(self.deprecated.fingerprint.iter().cloned());
513		fp
514	}
515
516	/// `system_roots`, preferring the canonical flag over the deprecated alias.
517	pub(crate) fn effective_system_roots(&self) -> Option<bool> {
518		self.system_roots.or(self.deprecated.system_roots)
519	}
520
521	/// `disable_verify`, preferring the canonical flag over the deprecated alias.
522	pub(crate) fn effective_disable_verify(&self) -> Option<bool> {
523		self.disable_verify.or(self.deprecated.disable_verify)
524	}
525
526	/// Resolve the verification policy from the configured flags.
527	///
528	/// Precedence and rules (shared by all backends):
529	/// - `--client-tls-disable-verify` disables verification, and combining it with any
530	///   trust material is rejected rather than silently ignoring that material.
531	/// - `--client-tls-fingerprint` pins the leaf and bypasses the CA chain; combining
532	///   it with `--client-tls-root` or `--client-tls-system-roots` is rejected rather than
533	///   silently ignoring one of them.
534	/// - Otherwise, verify against the system roots (default) plus any custom
535	///   roots. The system roots are dropped once a custom root is given unless
536	///   `--client-tls-system-roots` re-enables them.
537	///
538	/// Every combination that would quietly drop one setting is an error. Silently
539	/// weakening trust is the worst outcome here: someone moving off
540	/// `disable_verify` by adding a fingerprint would otherwise still accept every
541	/// certificate, with the UI showing the pin as configured.
542	pub(crate) fn verification(&self) -> Result<Verification> {
543		self.warn_deprecated();
544
545		let fingerprints = self.fingerprints()?;
546		let roots = self.effective_root();
547		let system_roots = self.effective_system_roots();
548
549		if self.effective_disable_verify().unwrap_or_default() {
550			if !fingerprints.is_empty() || !roots.is_empty() || system_roots == Some(true) {
551				return Err(Error::DisableVerifyWithTrust);
552			}
553			return Ok(Verification::Disabled);
554		}
555
556		if !fingerprints.is_empty() {
557			if !roots.is_empty() || system_roots == Some(true) {
558				return Err(Error::FingerprintWithRoots);
559			}
560			return Ok(Verification::Fingerprints(fingerprints));
561		}
562
563		// Default to system roots only when no custom root is given, so passing a
564		// root replaces them unless the system roots are explicitly re-enabled.
565		let system = system_roots.unwrap_or(roots.is_empty());
566
567		let custom = CustomRoots::new(roots)?;
568
569		// WebPKI needs at least one trusted root to ever succeed, so fail fast
570		// instead of producing confusing handshake errors later. With system
571		// trust enabled the verifier supplies its own roots, so custom roots are
572		// optional.
573		if !system && custom.current().is_empty() {
574			return Err(Error::NoRoots);
575		}
576
577		Ok(Verification::Roots { custom, system })
578	}
579
580	/// Whether an insecure `http://` certificate-fingerprint bootstrap may be
581	/// honored for a connection.
582	///
583	/// Only when no stronger verification is configured: an explicit
584	/// `--client-tls-fingerprint` must never be weakened by an attacker-controlled
585	/// plaintext fetch, and there is nothing to bootstrap when verification is
586	/// disabled. With CA roots (the default), `http://` is the deliberate
587	/// per-connection way to pin a self-signed relay, so it is allowed.
588	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
589	pub(crate) fn allows_http_bootstrap(&self) -> bool {
590		self.effective_fingerprint().is_empty() && !self.effective_disable_verify().unwrap_or_default()
591	}
592
593	/// Parse the configured fingerprints into fixed-size SHA-256 digests.
594	fn fingerprints(&self) -> Result<Vec<[u8; 32]>> {
595		self.effective_fingerprint()
596			.iter()
597			.map(|fp| parse_fingerprint(fp))
598			.collect()
599	}
600
601	/// Build a [`rustls::ClientConfig`] from this configuration.
602	///
603	/// Resolves the verification policy, optionally attaches a client identity
604	/// for mTLS, and installs the matching verifier.
605	pub fn build(&self) -> Result<rustls::ClientConfig> {
606		let provider = crypto::provider();
607		let verification = self.verification()?;
608		let reloadable_roots = cfg!(feature = "watch")
609			&& matches!(&verification, Verification::Roots { custom, .. } if !custom.paths.is_empty());
610
611		// Allow TLS 1.2 in addition to 1.3 for WebSocket compatibility.
612		// QUIC always negotiates TLS 1.3 regardless of this setting.
613		let builder = rustls::ClientConfig::builder_with_provider(provider.clone())
614			.with_protocol_versions(&[&rustls::version::TLS13, &rustls::version::TLS12])?;
615
616		let verifier: Arc<dyn rustls::client::danger::ServerCertVerifier> = match verification {
617			Verification::Disabled => {
618				tracing::warn!(
619					"TLS server certificate verification is disabled; A man-in-the-middle attack is possible."
620				);
621				Arc::new(NoCertificateVerification(provider))
622			}
623			Verification::Fingerprints(fingerprints) => {
624				let fingerprints = fingerprints.into_iter().map(|fp| fp.to_vec()).collect();
625				Arc::new(FingerprintVerifier::new(provider, fingerprints))
626			}
627			Verification::Roots { custom, system } => Self::root_server_verifier(custom, system, provider)?,
628		};
629
630		let builder = builder.dangerous().with_custom_certificate_verifier(verifier);
631		let mut tls = self.with_client_auth(builder)?;
632
633		// A resumed session skips certificate verification. Disable resumption when
634		// roots can change underneath this config so removing a CA takes effect on
635		// every subsequent handshake.
636		if reloadable_roots {
637			tls.resumption = rustls::client::Resumption::disabled();
638		}
639
640		Ok(tls)
641	}
642
643	/// Build a reloadable verifier for custom roots and system/default trust.
644	///
645	/// Uses the OS-native platform verifier (Keychain/SecTrust, Windows
646	/// CryptoAPI, or the native store on Linux) everywhere it works, optionally
647	/// extended with `custom` PEM roots. Android's platform verifier needs JNI
648	/// setup (see [`init_android`]); until that has run we trust the bundled
649	/// Mozilla roots so verification still works out of the box.
650	fn root_server_verifier(
651		custom: CustomRoots,
652		system: bool,
653		provider: crypto::Provider,
654	) -> Result<Arc<dyn rustls::client::danger::ServerCertVerifier>> {
655		let initial = Self::build_root_server_verifier(&custom.current(), system, &provider)?;
656
657		#[cfg(feature = "watch")]
658		if !custom.paths.is_empty() {
659			let paths = custom.paths.clone();
660			let reload = custom.clone();
661			let reload_provider = provider.clone();
662			let verifier = ReloadingServerVerifier::new(&paths, initial, move || {
663				let roots = reload.load()?;
664				let verifier = Self::build_root_server_verifier(&roots, system, &reload_provider)?;
665				reload.replace(roots);
666				Ok(verifier)
667			});
668			return Ok(Arc::new(verifier));
669		}
670
671		Ok(initial)
672	}
673
674	fn build_root_server_verifier(
675		custom: &[CertificateDer<'static>],
676		system: bool,
677		provider: &crypto::Provider,
678	) -> Result<Arc<dyn rustls::client::danger::ServerCertVerifier>> {
679		if !system {
680			let roots = root_store(custom)?;
681			let verifier =
682				rustls::client::WebPkiServerVerifier::builder_with_provider(Arc::new(roots), provider.clone())
683					.build()
684					.map_err(Error::ServerVerifier)?;
685			return Ok(verifier);
686		}
687
688		// Android's platform verifier needs JNI init (see `init_android`) and,
689		// unlike the other platforms, can't be extended with custom roots. So use
690		// it only once initialized and with no custom roots; otherwise trust the
691		// bundled Mozilla roots (plus any custom roots) so verification still works.
692		#[cfg(target_os = "android")]
693		{
694			if ANDROID_INITIALIZED.load(std::sync::atomic::Ordering::Acquire) && custom.is_empty() {
695				let verifier = rustls_platform_verifier::Verifier::new(provider.clone())?;
696				return Ok(Arc::new(verifier));
697			}
698
699			let mut roots = rustls::RootCertStore::empty();
700			roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
701			for cert in custom {
702				roots.add(cert.clone()).map_err(Error::AddRoot)?;
703			}
704			let verifier =
705				rustls::client::WebPkiServerVerifier::builder_with_provider(Arc::new(roots), provider.clone())
706					.build()
707					.map_err(Error::ServerVerifier)?;
708			Ok(verifier)
709		}
710
711		#[cfg(not(target_os = "android"))]
712		{
713			let verifier = if custom.is_empty() {
714				rustls_platform_verifier::Verifier::new(provider.clone())?
715			} else {
716				rustls_platform_verifier::Verifier::new_with_extra_roots(custom.iter().cloned(), provider.clone())?
717			};
718			Ok(Arc::new(verifier))
719		}
720	}
721
722	/// Attach the optional mTLS client identity, finishing the rustls builder.
723	fn with_client_auth(
724		&self,
725		builder: rustls::ConfigBuilder<rustls::ClientConfig, rustls::client::WantsClientCert>,
726	) -> Result<rustls::ClientConfig> {
727		Ok(match (&self.cert, &self.key) {
728			(Some(cert_path), Some(key_path)) => {
729				let cert_pem = fs::read(cert_path).map_err(Error::ReadFile)?;
730				let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(&cert_pem)
731					.collect::<std::result::Result<_, _>>()
732					.map_err(Error::Read)?;
733				if chain.is_empty() {
734					return Err(Error::Empty);
735				}
736				let key_pem = fs::read(key_path).map_err(Error::ReadFile)?;
737				let key = PrivateKeyDer::from_pem_slice(&key_pem).map_err(Error::Key)?;
738				builder.with_client_auth_cert(chain, key).map_err(Error::ClientAuth)?
739			}
740			(None, None) => builder.with_no_client_auth(),
741			_ => return Err(Error::IncompleteClientAuth),
742		})
743	}
744}
745
746/// Build a [`rustls::RootCertStore`] from a list of custom PEM roots.
747fn root_store(custom: &[CertificateDer<'static>]) -> Result<rustls::RootCertStore> {
748	let mut roots = rustls::RootCertStore::empty();
749	for cert in custom {
750		roots.add(cert.clone()).map_err(Error::AddRoot)?;
751	}
752	Ok(roots)
753}
754
755/// Whether [`init_android`] has successfully wired up the platform verifier.
756#[cfg(target_os = "android")]
757static ANDROID_INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
758
759/// Initialize Android platform certificate verification.
760///
761/// On Android the OS trust store is only reachable through the JVM, so the
762/// platform verifier needs a JNI handle to the application `Context` before it
763/// can be used. Call this once at startup (e.g. from `JNI_OnLoad`) with an
764/// attached [`jni::Env`] for the calling thread and the application `Context`.
765/// The `moq-ffi` bindings call it automatically, so most consumers never touch
766/// this directly.
767///
768/// Until it succeeds, clients fall back to the bundled Mozilla roots, so a
769/// missing or failed init degrades to webpki verification rather than failing.
770#[cfg(target_os = "android")]
771pub fn init_android(env: &mut jni::Env, context: jni::objects::JObject) -> Result<()> {
772	rustls_platform_verifier::android::init_with_env(env, context).map_err(Error::AndroidInit)?;
773	ANDROID_INITIALIZED.store(true, std::sync::atomic::Ordering::Release);
774	Ok(())
775}
776
777// ── Server ──────────────────────────────────────────────────────────
778
779/// TLS configuration for the server.
780///
781/// Certificate and keys must currently be files on disk.
782/// Alternatively, you can generate a self-signed certificate given a list of hostnames.
783///
784/// In config files, each list field accepts either a single string or a TOML array.
785#[serde_with::serde_as]
786#[derive(clap::Args, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
787#[serde(deny_unknown_fields)]
788#[group(id = "tls-server")]
789#[non_exhaustive]
790pub struct Server {
791	/// Load the given certificate from disk.
792	#[arg(long = "tls-cert", id = "tls-cert", env = "MOQ_SERVER_TLS_CERT")]
793	#[serde(default, skip_serializing_if = "Vec::is_empty")]
794	#[serde_as(as = "serde_with::OneOrMany<_>")]
795	pub cert: Vec<PathBuf>,
796
797	/// Load the given key from disk.
798	#[arg(long = "tls-key", id = "tls-key", env = "MOQ_SERVER_TLS_KEY")]
799	#[serde(default, skip_serializing_if = "Vec::is_empty")]
800	#[serde_as(as = "serde_with::OneOrMany<_>")]
801	pub key: Vec<PathBuf>,
802
803	/// Or generate a new certificate and key with the given hostnames.
804	/// This won't be valid unless the client uses the fingerprint or disables verification.
805	#[arg(
806		long = "tls-generate",
807		id = "tls-generate",
808		value_delimiter = ',',
809		env = "MOQ_SERVER_TLS_GENERATE"
810	)]
811	#[serde(default, skip_serializing_if = "Vec::is_empty")]
812	#[serde_as(as = "serde_with::OneOrMany<_>")]
813	pub generate: Vec<String>,
814
815	/// PEM file(s) of root CAs for validating optional client certificates (mTLS).
816	///
817	/// When set, clients *may* present a certificate during the TLS handshake.
818	/// Valid presentations are reported via [`crate::Request::peer_identity`]
819	/// and can be used by the application to grant elevated access. Clients that
820	/// do not present a certificate are unaffected.
821	///
822	/// Plain-TLS listeners built via [`Self::server_config`] also use these roots
823	/// for optional mTLS. Root files are hot reloaded for new handshakes on the
824	/// rustls-based backends; quiche servers require a restart because their TLS
825	/// hook fixes client-auth roots when the listener is built.
826	#[arg(
827		long = "server-tls-root",
828		id = "server-tls-root",
829		value_delimiter = ',',
830		env = "MOQ_SERVER_TLS_ROOT"
831	)]
832	#[serde(default, skip_serializing_if = "Vec::is_empty")]
833	#[serde_as(as = "serde_with::OneOrMany<_>")]
834	pub root: Vec<PathBuf>,
835}
836
837impl Server {
838	/// Disable cached client authentication when client roots can reload.
839	#[cfg(feature = "watch")]
840	pub(crate) fn disable_resumption(&self, tls: &mut rustls::ServerConfig) {
841		if !self.root.is_empty() {
842			tls.session_storage = Arc::new(rustls::server::NoServerSessionStorage {});
843			tls.send_tls13_tickets = 0;
844		}
845	}
846
847	/// Load all configured root CAs into a [`rustls::RootCertStore`].
848	pub fn load_roots(&self) -> Result<rustls::RootCertStore> {
849		root_store(&read_roots(&self.root)?)
850	}
851
852	/// Build the optional-client-auth verifier, reloading configured roots in place.
853	#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
854	pub(crate) fn client_verifier(
855		&self,
856		provider: crypto::Provider,
857	) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>> {
858		let initial = Self::build_client_verifier(&self.root, &provider)?;
859
860		#[cfg(feature = "watch")]
861		{
862			let paths = self.root.clone();
863			let reload_paths = paths.clone();
864			let reload_provider = provider.clone();
865			let verifier = ReloadingClientVerifier::new(&paths, initial, move || {
866				Self::build_client_verifier(&reload_paths, &reload_provider)
867			});
868			Ok(Arc::new(verifier))
869		}
870
871		#[cfg(not(feature = "watch"))]
872		Ok(initial)
873	}
874
875	#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
876	fn build_client_verifier(
877		paths: &[PathBuf],
878		provider: &crypto::Provider,
879	) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>> {
880		let roots = root_store(&read_roots(paths)?)?;
881		rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider.clone())
882			.allow_unauthenticated()
883			.build()
884			.map_err(Error::ClientVerifier)
885	}
886
887	/// Build a [`rustls::ServerConfig`] for a plain-TLS (non-QUIC) server, e.g. an
888	/// RTMPS or HTTPS listener fronting the QUIC endpoint, reusing the QUIC
889	/// backend's certificate handling: on-disk `cert`/`key` pairs, `generate`
890	/// self-signed certs, and optional mTLS `root` client CAs.
891	///
892	/// `alpn` sets the advertised ALPN protocols (e.g.
893	/// `vec![b"h2".to_vec(), b"http/1.1".to_vec()]`); pass an empty list for a
894	/// protocol like RTMPS that doesn't use ALPN.
895	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
896	pub fn server_config(&self, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
897		server_config(self, alpn)
898	}
899}
900
901/// Build a [`rustls::ServerConfig`] from a [`Server`] for a plain-TLS listener.
902#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
903fn server_config(config: &Server, alpn: Vec<Vec<u8>>) -> Result<Arc<rustls::ServerConfig>> {
904	let provider = crypto::provider();
905
906	let certs = ServeCerts::new(provider.clone());
907	certs.load_certs(config)?;
908	let certs = Arc::new(certs);
909
910	// TCP can negotiate TLS 1.2 as well as 1.3, unlike QUIC which is 1.3-only.
911	let builder =
912		rustls::ServerConfig::builder_with_provider(provider.clone()).with_safe_default_protocol_versions()?;
913
914	let mut tls = if config.root.is_empty() {
915		builder.with_no_client_auth().with_cert_resolver(certs)
916	} else {
917		let verifier = config.client_verifier(provider)?;
918		builder.with_client_cert_verifier(verifier).with_cert_resolver(certs)
919	};
920
921	tls.alpn_protocols = alpn;
922	config.disable_resumption(&mut tls);
923	Ok(Arc::new(tls))
924}
925
926/// A peer's validated client-certificate chain from the mTLS handshake.
927///
928/// Returned by [`crate::Request::peer_identity`] when the peer presented a
929/// certificate that chained to a configured [`Server::root`]. Owns the chain
930/// (leaf first) so callers can inspect it, e.g. [`expiry`](Self::expiry),
931/// without re-parsing the type-erased QUIC identity.
932#[derive(Clone)]
933pub struct PeerIdentity {
934	chain: Vec<CertificateDer<'static>>,
935}
936
937impl PeerIdentity {
938	/// Wrap the type-erased identity from `quinn::Connection::peer_identity`.
939	/// Returns `None` if the peer presented no certificate or the identity is
940	/// not a certificate chain.
941	#[cfg(any(feature = "quinn", feature = "noq"))]
942	pub(crate) fn from_any(identity: Option<Box<dyn std::any::Any>>) -> Option<Self> {
943		let chain = identity?.downcast::<Vec<CertificateDer<'static>>>().ok()?;
944		Some(Self { chain: *chain })
945	}
946
947	/// Wrap a certificate chain already exposed by a QUIC backend.
948	#[cfg(feature = "quiche")]
949	pub(crate) fn from_chain(chain: Vec<CertificateDer<'static>>) -> Self {
950		Self { chain }
951	}
952
953	/// The validated certificate chain, leaf first.
954	///
955	/// Exposes [`rustls::pki_types::CertificateDer`] directly (already part of
956	/// this crate's public API via the `rustls` re-export), so a major `rustls`
957	/// bump is a breaking change for consumers of this method.
958	pub fn chain(&self) -> &[CertificateDer<'static>] {
959		&self.chain
960	}
961
962	/// The leaf certificate's `notAfter`, if it parses. A `notAfter` before the
963	/// Unix epoch is reported as `None`.
964	pub fn expiry(&self) -> Option<std::time::SystemTime> {
965		use std::time::{Duration, UNIX_EPOCH};
966
967		let leaf = self.chain.first()?;
968		let (_, cert) = x509_parser::parse_x509_certificate(leaf).ok()?;
969		let secs = u64::try_from(cert.validity().not_after.timestamp()).ok()?;
970		Some(UNIX_EPOCH + Duration::from_secs(secs))
971	}
972}
973
974/// The certificates a server is currently serving.
975///
976/// Only a QUIC backend serves TLS of its own, so nothing else populates this.
977#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
978#[derive(Debug, Default)]
979pub(crate) struct Info {
980	pub(crate) certs: Vec<Arc<rustls::sign::CertifiedKey>>,
981	pub(crate) fingerprints: Vec<String>,
982}
983
984/// A live handle to the certificates a [`crate::Server`] is serving.
985///
986/// Cheap to clone, and every read reflects the latest hot reload of the files on
987/// disk, so a caller can build one at startup and hold it for the process
988/// lifetime. Obtained from [`crate::Server::certificates`].
989#[derive(Clone, Debug)]
990pub struct Certificates {
991	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
992	info: Arc<RwLock<Info>>,
993}
994
995impl Certificates {
996	#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
997	pub(crate) fn new(info: Arc<RwLock<Info>>) -> Self {
998		Self { info }
999	}
1000
1001	/// An empty set, used when no TLS-bearing backend is configured.
1002	pub(crate) fn empty() -> Self {
1003		Self {
1004			#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
1005			info: Arc::new(RwLock::new(Info::default())),
1006		}
1007	}
1008
1009	/// The SHA-256 fingerprints of the certificates being served right now, hex
1010	/// encoded, one per certificate and in configuration order.
1011	///
1012	/// Empty when the server has no TLS-bearing backend. Re-read this per use
1013	/// rather than caching it: a cert rotation on disk changes the values.
1014	pub fn fingerprints(&self) -> Vec<String> {
1015		#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
1016		{
1017			// A panicking writer can't leave the cert list half-updated (it is
1018			// replaced wholesale), so a poisoned lock is still safe to read.
1019			let info = self.info.read().unwrap_or_else(std::sync::PoisonError::into_inner);
1020			info.fingerprints.clone()
1021		}
1022		#[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche")))]
1023		Vec::new()
1024	}
1025}
1026
1027/// Delegates each server-certificate check to the latest valid root verifier.
1028#[cfg(feature = "watch")]
1029#[derive(Debug)]
1030struct ReloadingServerVerifier {
1031	inner: Reloading<dyn rustls::client::danger::ServerCertVerifier>,
1032}
1033
1034#[cfg(feature = "watch")]
1035impl ReloadingServerVerifier {
1036	fn new(
1037		paths: &[PathBuf],
1038		initial: Arc<dyn rustls::client::danger::ServerCertVerifier>,
1039		build: impl Fn() -> Result<Arc<dyn rustls::client::danger::ServerCertVerifier>> + Send + Sync + 'static,
1040	) -> Self {
1041		Self {
1042			inner: Reloading::new(paths, initial, "client", build),
1043		}
1044	}
1045}
1046
1047#[cfg(feature = "watch")]
1048impl rustls::client::danger::ServerCertVerifier for ReloadingServerVerifier {
1049	fn verify_server_cert(
1050		&self,
1051		end_entity: &CertificateDer<'_>,
1052		intermediates: &[CertificateDer<'_>],
1053		server_name: &ServerName<'_>,
1054		ocsp_response: &[u8],
1055		now: UnixTime,
1056	) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
1057		self.inner
1058			.current()
1059			.verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
1060	}
1061
1062	fn verify_tls12_signature(
1063		&self,
1064		message: &[u8],
1065		cert: &CertificateDer<'_>,
1066		dss: &rustls::DigitallySignedStruct,
1067	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1068		self.inner.current().verify_tls12_signature(message, cert, dss)
1069	}
1070
1071	fn verify_tls13_signature(
1072		&self,
1073		message: &[u8],
1074		cert: &CertificateDer<'_>,
1075		dss: &rustls::DigitallySignedStruct,
1076	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1077		self.inner.current().verify_tls13_signature(message, cert, dss)
1078	}
1079
1080	fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
1081		self.inner.current().supported_verify_schemes()
1082	}
1083
1084	fn requires_raw_public_keys(&self) -> bool {
1085		self.inner.current().requires_raw_public_keys()
1086	}
1087}
1088
1089/// Delegates each client-certificate check to the latest valid root verifier.
1090#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1091#[derive(Debug)]
1092struct ReloadingClientVerifier {
1093	inner: Reloading<dyn rustls::server::danger::ClientCertVerifier>,
1094}
1095
1096#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1097impl ReloadingClientVerifier {
1098	fn new(
1099		paths: &[PathBuf],
1100		initial: Arc<dyn rustls::server::danger::ClientCertVerifier>,
1101		build: impl Fn() -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>> + Send + Sync + 'static,
1102	) -> Self {
1103		Self {
1104			inner: Reloading::new(paths, initial, "server", build),
1105		}
1106	}
1107}
1108
1109#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1110impl rustls::server::danger::ClientCertVerifier for ReloadingClientVerifier {
1111	fn offer_client_auth(&self) -> bool {
1112		self.inner.current().offer_client_auth()
1113	}
1114
1115	fn client_auth_mandatory(&self) -> bool {
1116		self.inner.current().client_auth_mandatory()
1117	}
1118
1119	fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
1120		// A live verifier cannot return a borrowed slice from a replaceable value.
1121		// Empty hints ask clients to offer any configured identity, which rustls
1122		// then verifies against the current roots.
1123		&[]
1124	}
1125
1126	fn verify_client_cert(
1127		&self,
1128		end_entity: &CertificateDer<'_>,
1129		intermediates: &[CertificateDer<'_>],
1130		now: UnixTime,
1131	) -> std::result::Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
1132		self.inner.current().verify_client_cert(end_entity, intermediates, now)
1133	}
1134
1135	fn verify_tls12_signature(
1136		&self,
1137		message: &[u8],
1138		cert: &CertificateDer<'_>,
1139		dss: &rustls::DigitallySignedStruct,
1140	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1141		self.inner.current().verify_tls12_signature(message, cert, dss)
1142	}
1143
1144	fn verify_tls13_signature(
1145		&self,
1146		message: &[u8],
1147		cert: &CertificateDer<'_>,
1148		dss: &rustls::DigitallySignedStruct,
1149	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1150		self.inner.current().verify_tls13_signature(message, cert, dss)
1151	}
1152
1153	fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
1154		self.inner.current().supported_verify_schemes()
1155	}
1156
1157	fn requires_raw_public_keys(&self) -> bool {
1158		self.inner.current().requires_raw_public_keys()
1159	}
1160}
1161
1162// ── NoCertificateVerification ───────────────────────────────────────
1163
1164#[derive(Debug)]
1165struct NoCertificateVerification(crypto::Provider);
1166
1167impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
1168	fn verify_server_cert(
1169		&self,
1170		_end_entity: &CertificateDer<'_>,
1171		_intermediates: &[CertificateDer<'_>],
1172		_server_name: &ServerName<'_>,
1173		_ocsp: &[u8],
1174		_now: UnixTime,
1175	) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
1176		Ok(rustls::client::danger::ServerCertVerified::assertion())
1177	}
1178
1179	fn verify_tls12_signature(
1180		&self,
1181		message: &[u8],
1182		cert: &CertificateDer<'_>,
1183		dss: &rustls::DigitallySignedStruct,
1184	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1185		rustls::crypto::verify_tls12_signature(message, cert, dss, &self.0.signature_verification_algorithms)
1186	}
1187
1188	fn verify_tls13_signature(
1189		&self,
1190		message: &[u8],
1191		cert: &CertificateDer<'_>,
1192		dss: &rustls::DigitallySignedStruct,
1193	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1194		rustls::crypto::verify_tls13_signature(message, cert, dss, &self.0.signature_verification_algorithms)
1195	}
1196
1197	fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
1198		self.0.signature_verification_algorithms.supported_schemes()
1199	}
1200}
1201
1202// ── FingerprintVerifier ─────────────────────────────────────────────
1203
1204#[derive(Debug)]
1205pub(crate) struct FingerprintVerifier {
1206	provider: crypto::Provider,
1207	fingerprints: Vec<Vec<u8>>,
1208}
1209
1210impl FingerprintVerifier {
1211	pub fn new(provider: crypto::Provider, fingerprints: Vec<Vec<u8>>) -> Self {
1212		Self { provider, fingerprints }
1213	}
1214}
1215
1216impl rustls::client::danger::ServerCertVerifier for FingerprintVerifier {
1217	fn verify_server_cert(
1218		&self,
1219		end_entity: &CertificateDer<'_>,
1220		_intermediates: &[CertificateDer<'_>],
1221		_server_name: &ServerName<'_>,
1222		_ocsp: &[u8],
1223		_now: UnixTime,
1224	) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
1225		let fingerprint = crypto::sha256(&self.provider, end_entity);
1226		if self.fingerprints.iter().any(|fp| fingerprint.as_ref() == fp.as_slice()) {
1227			Ok(rustls::client::danger::ServerCertVerified::assertion())
1228		} else {
1229			Err(rustls::Error::General("fingerprint mismatch".into()))
1230		}
1231	}
1232
1233	fn verify_tls12_signature(
1234		&self,
1235		message: &[u8],
1236		cert: &CertificateDer<'_>,
1237		dss: &rustls::DigitallySignedStruct,
1238	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1239		rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
1240	}
1241
1242	fn verify_tls13_signature(
1243		&self,
1244		message: &[u8],
1245		cert: &CertificateDer<'_>,
1246		dss: &rustls::DigitallySignedStruct,
1247	) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
1248		rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
1249	}
1250
1251	fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
1252		self.provider.signature_verification_algorithms.supported_schemes()
1253	}
1254}
1255
1256#[cfg(test)]
1257#[cfg(all(any(feature = "quinn", feature = "noq", feature = "quiche"), feature = "aws-lc-rs"))]
1258mod tests {
1259	/// Disabling verification cannot be combined with trust material that it would
1260	/// otherwise ignore.
1261	#[test]
1262	fn disable_verify_rejects_trust_material() {
1263		let insecure = Client {
1264			disable_verify: Some(true),
1265			..Default::default()
1266		};
1267		assert!(matches!(insecure.verification(), Ok(Verification::Disabled)));
1268
1269		let with_fingerprint = Client {
1270			disable_verify: Some(true),
1271			fingerprint: vec!["ab".repeat(32)],
1272			..Default::default()
1273		};
1274		assert!(matches!(
1275			with_fingerprint.verification(),
1276			Err(Error::DisableVerifyWithTrust)
1277		));
1278
1279		let with_root = Client {
1280			disable_verify: Some(true),
1281			root: vec!["/tmp/root.pem".into()],
1282			..Default::default()
1283		};
1284		assert!(matches!(with_root.verification(), Err(Error::DisableVerifyWithTrust)));
1285
1286		let with_system_roots = Client {
1287			disable_verify: Some(true),
1288			system_roots: Some(true),
1289			..Default::default()
1290		};
1291		assert!(matches!(
1292			with_system_roots.verification(),
1293			Err(Error::DisableVerifyWithTrust)
1294		));
1295
1296		let without_system_roots = Client {
1297			disable_verify: Some(true),
1298			system_roots: Some(false),
1299			..Default::default()
1300		};
1301		assert!(matches!(
1302			without_system_roots.verification(),
1303			Ok(Verification::Disabled)
1304		));
1305	}
1306
1307	use super::*;
1308	use rustls::client::danger::ServerCertVerifier;
1309	use rustls::pki_types::ServerName;
1310	#[cfg(feature = "watch")]
1311	use rustls::server::danger::ClientCertVerifier;
1312
1313	fn self_signed() -> CertificateDer<'static> {
1314		let key = rcgen::KeyPair::generate().unwrap();
1315		let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
1316		params.self_signed(&key).unwrap().into()
1317	}
1318
1319	#[cfg(any(feature = "quinn", feature = "noq"))]
1320	#[test]
1321	fn peer_identity_expiry_reads_not_after() {
1322		// notAfter at a whole second so the round-trip is exact.
1323		let not_after = ::time::OffsetDateTime::from_unix_timestamp(2_000_000_000).unwrap();
1324
1325		let key = rcgen::KeyPair::generate().unwrap();
1326		let mut params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
1327		params.not_after = not_after;
1328		let cert: CertificateDer<'static> = params.self_signed(&key).unwrap().into();
1329
1330		// quinn/noq hand back the chain as a boxed Vec<CertificateDer>.
1331		let identity: Box<dyn std::any::Any> = Box::new(vec![cert]);
1332		let parsed = PeerIdentity::from_any(Some(identity)).expect("chain parsed");
1333		let expiry = parsed.expiry().expect("expiry parsed");
1334		assert_eq!(
1335			expiry.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
1336			2_000_000_000
1337		);
1338	}
1339
1340	#[cfg(any(feature = "quinn", feature = "noq"))]
1341	#[test]
1342	fn peer_identity_none_without_chain() {
1343		assert!(PeerIdentity::from_any(None).is_none());
1344		// A wrong downcast type (not a cert chain) yields None rather than panicking.
1345		let bogus: Box<dyn std::any::Any> = Box::new(42u32);
1346		assert!(PeerIdentity::from_any(Some(bogus)).is_none());
1347	}
1348
1349	#[test]
1350	fn fingerprint_verifier_matches_and_rejects() {
1351		let provider = crypto::provider();
1352		let cert = self_signed();
1353		let fingerprint = crypto::sha256(&provider, cert.as_ref()).as_ref().to_vec();
1354
1355		let name = ServerName::try_from("localhost").unwrap();
1356		let now = UnixTime::now();
1357
1358		let verifier = FingerprintVerifier::new(provider.clone(), vec![fingerprint]);
1359		assert!(verifier.verify_server_cert(&cert, &[], &name, &[], now).is_ok());
1360
1361		// A different leaf certificate must not satisfy the pin.
1362		let other = self_signed();
1363		assert!(verifier.verify_server_cert(&other, &[], &name, &[], now).is_err());
1364	}
1365
1366	#[test]
1367	fn build_installs_fingerprint_verifier() {
1368		let cert = self_signed();
1369		let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1370
1371		// A bogus hash still builds; verification happens at handshake time.
1372		let config = Client {
1373			fingerprint: vec![fingerprint],
1374			..Default::default()
1375		};
1376		assert!(config.build().is_ok());
1377	}
1378
1379	#[test]
1380	fn build_rejects_invalid_fingerprint_hex() {
1381		let config = Client {
1382			fingerprint: vec!["not-hex".to_string()],
1383			..Default::default()
1384		};
1385		assert!(matches!(config.build(), Err(Error::Fingerprint(_))));
1386	}
1387
1388	#[test]
1389	fn build_rejects_wrong_length_fingerprint() {
1390		// Valid hex, but only 2 bytes instead of 32.
1391		let config = Client {
1392			fingerprint: vec!["abcd".to_string()],
1393			..Default::default()
1394		};
1395		assert!(matches!(config.build(), Err(Error::FingerprintLength(2))));
1396	}
1397
1398	#[test]
1399	fn build_rejects_no_roots() {
1400		// System roots disabled with no custom root and no alternate verifier:
1401		// nothing could ever verify, so reject up front.
1402		let config = Client {
1403			system_roots: Some(false),
1404			..Default::default()
1405		};
1406		assert!(matches!(config.build(), Err(Error::NoRoots)));
1407	}
1408
1409	#[test]
1410	fn build_allows_no_roots_when_verification_overridden() {
1411		// disable_verify swaps in its own verifier, so an empty store is fine.
1412		let config = Client {
1413			system_roots: Some(false),
1414			disable_verify: Some(true),
1415			..Default::default()
1416		};
1417		assert!(config.build().is_ok());
1418
1419		// Same for fingerprint pinning.
1420		let cert = self_signed();
1421		let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1422		let config = Client {
1423			system_roots: Some(false),
1424			fingerprint: vec![fingerprint],
1425			..Default::default()
1426		};
1427		assert!(config.build().is_ok());
1428	}
1429
1430	#[test]
1431	fn build_rejects_fingerprint_with_roots() {
1432		let cert = self_signed();
1433		let fingerprint = hex::encode(crypto::sha256(&crypto::provider(), cert.as_ref()));
1434
1435		// Fingerprint pinning bypasses the CA chain, so combining it with roots
1436		// is rejected rather than silently ignoring one of them.
1437		let with_system = Client {
1438			fingerprint: vec![fingerprint.clone()],
1439			system_roots: Some(true),
1440			..Default::default()
1441		};
1442		assert!(matches!(with_system.build(), Err(Error::FingerprintWithRoots)));
1443
1444		// The conflict is detected before any root file is read, so the path
1445		// need not exist.
1446		let with_custom = Client {
1447			fingerprint: vec![fingerprint],
1448			root: vec![PathBuf::from("/does-not-exist.pem")],
1449			..Default::default()
1450		};
1451		assert!(matches!(with_custom.build(), Err(Error::FingerprintWithRoots)));
1452	}
1453
1454	/// Write a self-signed cert to a temp PEM file, returning the keep-alive
1455	/// handle alongside its path.
1456	fn self_signed_root() -> (tempfile::NamedTempFile, PathBuf) {
1457		use std::io::Write;
1458		let key = rcgen::KeyPair::generate().unwrap();
1459		let params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
1460		let cert = params.self_signed(&key).unwrap();
1461		let mut file = tempfile::NamedTempFile::new().unwrap();
1462		file.write_all(cert.pem().as_bytes()).unwrap();
1463		let path = file.path().to_path_buf();
1464		(file, path)
1465	}
1466
1467	#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1468	fn signed_certificates() -> (
1469		String,
1470		CertificateDer<'static>,
1471		PrivateKeyDer<'static>,
1472		CertificateDer<'static>,
1473		PrivateKeyDer<'static>,
1474	) {
1475		use rcgen::{BasicConstraints, ExtendedKeyUsagePurpose, IsCa, Issuer};
1476
1477		let ca_key = rcgen::KeyPair::generate().unwrap();
1478		let mut ca_params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
1479		ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
1480		let ca = ca_params.self_signed(&ca_key).unwrap();
1481		let issuer = Issuer::from_params(&ca_params, &ca_key);
1482
1483		let server_key = rcgen::KeyPair::generate().unwrap();
1484		let server_key_der = PrivatePkcs8KeyDer::from(server_key.serialize_der());
1485		let mut server_params = rcgen::CertificateParams::new(vec!["localhost".to_string()]).unwrap();
1486		server_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
1487		let server = server_params.signed_by(&server_key, &issuer).unwrap();
1488
1489		let client_key = rcgen::KeyPair::generate().unwrap();
1490		let client_key_der = PrivatePkcs8KeyDer::from(client_key.serialize_der());
1491		let mut client_params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
1492		client_params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
1493		let client = client_params.signed_by(&client_key, &issuer).unwrap();
1494
1495		(
1496			ca.pem(),
1497			server.into(),
1498			server_key_der.into(),
1499			client.into(),
1500			client_key_der.into(),
1501		)
1502	}
1503
1504	#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1505	fn handshake_kinds(
1506		client: Arc<rustls::ClientConfig>,
1507		server: Arc<rustls::ServerConfig>,
1508	) -> std::result::Result<(rustls::HandshakeKind, rustls::HandshakeKind), rustls::Error> {
1509		let name = ServerName::try_from("localhost").unwrap();
1510		let mut client = rustls::ClientConnection::new(client, name).unwrap();
1511		let mut server = rustls::ServerConnection::new(server).unwrap();
1512
1513		for _ in 0..100 {
1514			let mut client_data = Vec::new();
1515			client.write_tls(&mut client_data).unwrap();
1516			if !client_data.is_empty() {
1517				server.read_tls(&mut client_data.as_slice()).unwrap();
1518				server.process_new_packets()?;
1519			}
1520
1521			let mut server_data = Vec::new();
1522			server.write_tls(&mut server_data).unwrap();
1523			if !server_data.is_empty() {
1524				client.read_tls(&mut server_data.as_slice()).unwrap();
1525				client.process_new_packets()?;
1526			}
1527
1528			if !client.is_handshaking() && !server.is_handshaking() && !client.wants_write() && !server.wants_write() {
1529				return Ok((client.handshake_kind().unwrap(), server.handshake_kind().unwrap()));
1530			}
1531		}
1532
1533		panic!("TLS handshake did not settle");
1534	}
1535
1536	#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1537	#[test]
1538	fn reloadable_roots_disable_session_resumption() {
1539		use std::io::Write;
1540
1541		let (ca, server_cert, server_key, _, _) = signed_certificates();
1542		let mut root_file = tempfile::NamedTempFile::new().unwrap();
1543		root_file.write_all(ca.as_bytes()).unwrap();
1544		let roots = read_roots(&[root_file.path().to_path_buf()]).unwrap();
1545		let provider = crypto::provider();
1546
1547		let control = rustls::ClientConfig::builder_with_provider(provider.clone())
1548			.with_safe_default_protocol_versions()
1549			.unwrap()
1550			.with_root_certificates(root_store(&roots).unwrap())
1551			.with_no_client_auth();
1552		let reloadable = Client {
1553			root: vec![root_file.path().to_path_buf()],
1554			..Default::default()
1555		}
1556		.build()
1557		.unwrap();
1558		let server = rustls::ServerConfig::builder_with_provider(provider)
1559			.with_safe_default_protocol_versions()
1560			.unwrap()
1561			.with_no_client_auth()
1562			.with_single_cert(vec![server_cert], server_key)
1563			.unwrap();
1564		let server = Arc::new(server);
1565
1566		let control = Arc::new(control);
1567		assert_eq!(
1568			handshake_kinds(control.clone(), server.clone()).unwrap().0,
1569			rustls::HandshakeKind::Full
1570		);
1571		assert_eq!(
1572			handshake_kinds(control, server.clone()).unwrap().0,
1573			rustls::HandshakeKind::Resumed
1574		);
1575
1576		let reloadable = Arc::new(reloadable);
1577		assert_eq!(
1578			handshake_kinds(reloadable.clone(), server.clone()).unwrap().0,
1579			rustls::HandshakeKind::Full
1580		);
1581		assert_eq!(
1582			handshake_kinds(reloadable, server).unwrap().0,
1583			rustls::HandshakeKind::Full
1584		);
1585	}
1586
1587	#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1588	#[test]
1589	fn reloadable_client_roots_disable_server_resumption() {
1590		use std::io::Write;
1591
1592		let (ca_a, server_cert, server_key, client_cert, client_key) = signed_certificates();
1593		let (ca_b, _, _, _, _) = signed_certificates();
1594		let mut root_file = tempfile::NamedTempFile::new().unwrap();
1595		root_file.write_all(ca_a.as_bytes()).unwrap();
1596		let paths = vec![root_file.path().to_path_buf()];
1597		let provider = crypto::provider();
1598
1599		let build_client = |cert, key| {
1600			rustls::ClientConfig::builder_with_provider(provider.clone())
1601				.with_safe_default_protocol_versions()
1602				.unwrap()
1603				.dangerous()
1604				.with_custom_certificate_verifier(Arc::new(NoCertificateVerification(provider.clone())))
1605				.with_client_auth_cert(vec![cert], key)
1606				.unwrap()
1607		};
1608		let control_client = Arc::new(build_client(client_cert.clone(), client_key.clone_key()));
1609		let reloadable_client = Arc::new(build_client(client_cert, client_key));
1610
1611		let verifier = Server::build_client_verifier(&paths, &provider).unwrap();
1612		let control = rustls::ServerConfig::builder_with_provider(provider.clone())
1613			.with_safe_default_protocol_versions()
1614			.unwrap()
1615			.with_client_cert_verifier(verifier)
1616			.with_single_cert(vec![server_cert.clone()], server_key.clone_key())
1617			.unwrap();
1618
1619		let initial = Server::build_client_verifier(&paths, &provider).unwrap();
1620		let reload_paths = paths.clone();
1621		let reload_provider = provider.clone();
1622		let verifier = Arc::new(ReloadingClientVerifier::new(&paths, initial, move || {
1623			Server::build_client_verifier(&reload_paths, &reload_provider)
1624		}));
1625		let reload = verifier.inner.state.clone();
1626		let mut reloadable = rustls::ServerConfig::builder_with_provider(provider)
1627			.with_safe_default_protocol_versions()
1628			.unwrap()
1629			.with_client_cert_verifier(verifier)
1630			.with_single_cert(vec![server_cert], server_key)
1631			.unwrap();
1632		Server {
1633			root: paths,
1634			..Default::default()
1635		}
1636		.disable_resumption(&mut reloadable);
1637
1638		let control = Arc::new(control);
1639		assert_eq!(
1640			handshake_kinds(control_client.clone(), control.clone()).unwrap().1,
1641			rustls::HandshakeKind::Full
1642		);
1643		assert_eq!(
1644			handshake_kinds(control_client, control).unwrap().1,
1645			rustls::HandshakeKind::Resumed
1646		);
1647
1648		let reloadable = Arc::new(reloadable);
1649		assert_eq!(
1650			handshake_kinds(reloadable_client.clone(), reloadable.clone())
1651				.unwrap()
1652				.1,
1653			rustls::HandshakeKind::Full
1654		);
1655
1656		std::fs::write(root_file.path(), ca_b).unwrap();
1657		reload.reload();
1658		assert!(handshake_kinds(reloadable_client, reloadable).is_err());
1659	}
1660
1661	#[cfg(all(feature = "watch", any(feature = "quinn", feature = "noq", feature = "quiche")))]
1662	#[test]
1663	fn custom_roots_reload_for_new_client_and_server_handshakes() {
1664		use std::io::Write;
1665
1666		let (ca_a, server_a, _, client_a, _) = signed_certificates();
1667		let (ca_b, server_b, _, client_b, _) = signed_certificates();
1668		let mut root_file = tempfile::NamedTempFile::new().unwrap();
1669		root_file.write_all(ca_a.as_bytes()).unwrap();
1670		let paths = vec![root_file.path().to_path_buf()];
1671		let provider = crypto::provider();
1672
1673		let custom = CustomRoots::new(paths.clone()).unwrap();
1674		let initial = Client::build_root_server_verifier(&custom.current(), false, &provider).unwrap();
1675		let reload_custom = custom.clone();
1676		let reload_provider = provider.clone();
1677		let server_verifier = ReloadingServerVerifier::new(&paths, initial, move || {
1678			let roots = reload_custom.load()?;
1679			let verifier = Client::build_root_server_verifier(&roots, false, &reload_provider)?;
1680			reload_custom.replace(roots);
1681			Ok(verifier)
1682		});
1683
1684		let initial = Server::build_client_verifier(&paths, &provider).unwrap();
1685		let reload_paths = paths.clone();
1686		let reload_provider = provider.clone();
1687		let client_verifier = ReloadingClientVerifier::new(&paths, initial, move || {
1688			Server::build_client_verifier(&reload_paths, &reload_provider)
1689		});
1690
1691		let name = ServerName::try_from("localhost").unwrap();
1692		let now = UnixTime::now();
1693		assert!(
1694			server_verifier
1695				.verify_server_cert(&server_a, &[], &name, &[], now)
1696				.is_ok()
1697		);
1698		assert!(
1699			server_verifier
1700				.verify_server_cert(&server_b, &[], &name, &[], now)
1701				.is_err()
1702		);
1703		assert!(client_verifier.verify_client_cert(&client_a, &[], now).is_ok());
1704		assert!(client_verifier.verify_client_cert(&client_b, &[], now).is_err());
1705
1706		std::fs::write(root_file.path(), ca_b).unwrap();
1707		server_verifier.inner.state.reload();
1708		client_verifier.inner.state.reload();
1709
1710		assert!(
1711			server_verifier
1712				.verify_server_cert(&server_a, &[], &name, &[], now)
1713				.is_err()
1714		);
1715		assert!(client_verifier.verify_client_cert(&client_a, &[], now).is_err());
1716
1717		// A malformed replacement must not erase the last valid verifier.
1718		std::fs::write(root_file.path(), "not a PEM certificate").unwrap();
1719		server_verifier.inner.state.reload();
1720		client_verifier.inner.state.reload();
1721		assert!(
1722			server_verifier
1723				.verify_server_cert(&server_b, &[], &name, &[], now)
1724				.is_ok()
1725		);
1726		assert!(client_verifier.verify_client_cert(&client_b, &[], now).is_ok());
1727	}
1728
1729	#[cfg(all(feature = "quiche", feature = "watch"))]
1730	#[test]
1731	fn custom_root_refresh_retains_last_valid_bundle() {
1732		use std::io::Write;
1733
1734		let (ca_a, _, _, _, _) = signed_certificates();
1735		let (ca_b, _, _, _, _) = signed_certificates();
1736		let mut root_file = tempfile::NamedTempFile::new().unwrap();
1737		root_file.write_all(ca_a.as_bytes()).unwrap();
1738		let roots = CustomRoots::new(vec![root_file.path().to_path_buf()]).unwrap();
1739		let initial = roots.current();
1740
1741		std::fs::write(root_file.path(), "not a PEM certificate").unwrap();
1742		assert_eq!(roots.refresh(), initial);
1743
1744		std::fs::write(
1745			root_file.path(),
1746			"-----BEGIN CERTIFICATE-----\nAQID\n-----END CERTIFICATE-----\n",
1747		)
1748		.unwrap();
1749		assert_eq!(roots.refresh(), initial);
1750
1751		std::fs::write(root_file.path(), ca_b).unwrap();
1752		let rotated = roots.refresh();
1753		assert_ne!(rotated, initial);
1754		assert_eq!(roots.current(), rotated);
1755	}
1756
1757	#[cfg(all(feature = "quiche", feature = "watch"))]
1758	#[test]
1759	fn custom_root_refresh_serializes_cache_updates() {
1760		let (ca_a, _, _, _, _) = signed_certificates();
1761		let (ca_b, _, _, _, _) = signed_certificates();
1762		let (ca_c, _, _, _, _) = signed_certificates();
1763		let parse = |pem: &str| {
1764			CertificateDer::pem_slice_iter(pem.as_bytes())
1765				.collect::<std::result::Result<Vec<_>, _>>()
1766				.unwrap()
1767		};
1768		let initial = parse(&ca_a);
1769		let bundle_b = parse(&ca_b);
1770		let bundle_c = parse(&ca_c);
1771		let roots = CustomRoots {
1772			paths: Vec::new(),
1773			current: Arc::new(RwLock::new(initial)),
1774		};
1775
1776		let (first_loaded_tx, first_loaded_rx) = std::sync::mpsc::sync_channel(0);
1777		let (release_first_tx, release_first_rx) = std::sync::mpsc::sync_channel(0);
1778		let first_roots = roots.clone();
1779		let first = std::thread::spawn(move || {
1780			first_roots.refresh_with(|| {
1781				first_loaded_tx.send(()).unwrap();
1782				release_first_rx.recv().unwrap();
1783				Ok(bundle_b)
1784			})
1785		});
1786		first_loaded_rx.recv().unwrap();
1787
1788		let (second_ready_tx, second_ready_rx) = std::sync::mpsc::sync_channel(0);
1789		let (second_loaded_tx, second_loaded_rx) = std::sync::mpsc::channel();
1790		let second_roots = roots.clone();
1791		let expected = bundle_c.clone();
1792		let second = std::thread::spawn(move || {
1793			second_ready_tx.send(()).unwrap();
1794			second_roots.refresh_with(|| {
1795				second_loaded_tx.send(()).unwrap();
1796				Ok(bundle_c)
1797			})
1798		});
1799		second_ready_rx.recv().unwrap();
1800		let overlapped = second_loaded_rx
1801			.recv_timeout(std::time::Duration::from_millis(100))
1802			.is_ok();
1803
1804		release_first_tx.send(()).unwrap();
1805		first.join().unwrap();
1806		second.join().unwrap();
1807		assert!(!overlapped, "root cache refresh transactions must not overlap");
1808		assert_eq!(roots.current(), expected);
1809	}
1810
1811	#[test]
1812	fn build_uses_platform_verifier_by_default() {
1813		// No custom roots, system trust on: resolves to the OS platform verifier
1814		// (bundled Mozilla roots on Android) and must build cleanly everywhere.
1815		assert!(Client::default().build().is_ok());
1816	}
1817
1818	#[test]
1819	fn build_with_custom_roots_only() {
1820		// A custom root with system trust left at its default disables the system
1821		// roots, verifying against the custom PEM alone.
1822		let (_keep, path) = self_signed_root();
1823		let config = Client {
1824			root: vec![path],
1825			..Default::default()
1826		};
1827		assert!(config.build().is_ok());
1828	}
1829
1830	#[test]
1831	fn build_with_custom_and_system_roots() {
1832		// Custom roots layered on top of system trust: exercises the platform
1833		// verifier's extra-roots path (or the bundled roots plus custom on Android).
1834		let (_keep, path) = self_signed_root();
1835		let config = Client {
1836			root: vec![path],
1837			system_roots: Some(true),
1838			..Default::default()
1839		};
1840		assert!(config.build().is_ok());
1841	}
1842}
1843
1844// ── ServeCerts ──────────────────────────────────────────────────────
1845
1846#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1847#[derive(Debug)]
1848pub(crate) struct ServeCerts {
1849	pub info: Arc<RwLock<Info>>,
1850	provider: crypto::Provider,
1851}
1852
1853#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1854impl ServeCerts {
1855	pub fn new(provider: crypto::Provider) -> Self {
1856		Self {
1857			info: Arc::new(RwLock::new(Info::default())),
1858			provider,
1859		}
1860	}
1861
1862	pub fn load_certs(&self, config: &Server) -> Result<()> {
1863		if config.cert.len() != config.key.len() {
1864			return Err(Error::CertKeyCountMismatch);
1865		}
1866		if config.cert.is_empty() && config.generate.is_empty() {
1867			return Err(Error::NoCertSource);
1868		}
1869
1870		let mut certs = Vec::new();
1871
1872		// Load the certificate and key files based on their index.
1873		for (cert, key) in config.cert.iter().zip(config.key.iter()) {
1874			certs.push(Arc::new(self.load(cert, key)?));
1875		}
1876
1877		// Generate a new certificate if requested.
1878		if !config.generate.is_empty() {
1879			certs.push(Arc::new(self.generate(&config.generate)?));
1880		}
1881
1882		self.set_certs(certs);
1883		Ok(())
1884	}
1885
1886	// Load a certificate and corresponding key from a file, but don't add it to the certs
1887	fn load(&self, chain_path: &Path, key_path: &Path) -> Result<rustls::sign::CertifiedKey> {
1888		let chain = read_certs(chain_path)?;
1889		if chain.is_empty() {
1890			return Err(Error::Empty);
1891		}
1892
1893		// Read the PEM private key
1894		let key = PrivateKeyDer::from_pem_file(key_path).map_err(Error::Key)?;
1895		let key = self.provider.key_provider.load_private_key(key)?;
1896
1897		let certified_key = rustls::sign::CertifiedKey::new(chain, key);
1898
1899		certified_key.keys_match().map_err(|source| Error::KeyMismatch {
1900			key: key_path.to_path_buf(),
1901			cert: chain_path.to_path_buf(),
1902			source,
1903		})?;
1904
1905		Ok(certified_key)
1906	}
1907
1908	#[cfg(any(feature = "aws-lc-rs", feature = "ring"))]
1909	fn generate(&self, hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1910		let key_pair = rcgen::KeyPair::generate()?;
1911
1912		let mut params = rcgen::CertificateParams::new(hostnames)?;
1913
1914		// Make the certificate valid for two weeks, starting yesterday (in case of clock drift).
1915		// WebTransport certificates MUST be valid for two weeks at most.
1916		params.not_before = ::time::OffsetDateTime::now_utc() - ::time::Duration::days(1);
1917		params.not_after = params.not_before + ::time::Duration::days(14);
1918
1919		// Generate the certificate
1920		let cert = params.self_signed(&key_pair)?;
1921
1922		// Convert the rcgen type to the rustls type.
1923		let key_der = key_pair.serialized_der().to_vec();
1924		let key_der = PrivatePkcs8KeyDer::from(key_der);
1925		let key = self.provider.key_provider.load_private_key(key_der.into())?;
1926
1927		// Create a rustls::sign::CertifiedKey
1928		Ok(rustls::sign::CertifiedKey::new(vec![cert.into()], key))
1929	}
1930
1931	#[cfg(not(any(feature = "aws-lc-rs", feature = "ring")))]
1932	fn generate(&self, _hostnames: &[String]) -> Result<rustls::sign::CertifiedKey> {
1933		Err(Error::NoCryptoProvider)
1934	}
1935
1936	// Replace the certificates
1937	pub fn set_certs(&self, certs: Vec<Arc<rustls::sign::CertifiedKey>>) {
1938		let fingerprints = certs
1939			.iter()
1940			.map(|ck| {
1941				let fingerprint = crate::crypto::sha256(&self.provider, ck.cert[0].as_ref());
1942				hex::encode(fingerprint)
1943			})
1944			.collect();
1945
1946		let mut info = self.info.write().expect("info write lock poisoned");
1947		info.certs = certs;
1948		info.fingerprints = fingerprints;
1949	}
1950
1951	// Return the best certificate for the given ClientHello.
1952	fn best_certificate(
1953		&self,
1954		client_hello: &rustls::server::ClientHello<'_>,
1955	) -> Option<Arc<rustls::sign::CertifiedKey>> {
1956		let server_name = client_hello.server_name()?;
1957		let dns_name = rustls::pki_types::ServerName::try_from(server_name).ok()?;
1958
1959		for ck in self.info.read().expect("info read lock poisoned").certs.iter() {
1960			let leaf: webpki::EndEntityCert = ck
1961				.end_entity_cert()
1962				.expect("missing certificate")
1963				.try_into()
1964				.expect("failed to parse certificate");
1965
1966			if leaf.verify_is_valid_for_subject_name(&dns_name).is_ok() {
1967				return Some(ck.clone());
1968			}
1969		}
1970
1971		None
1972	}
1973}
1974
1975#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche"))]
1976impl rustls::server::ResolvesServerCert for ServeCerts {
1977	fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<rustls::sign::CertifiedKey>> {
1978		if let Some(cert) = self.best_certificate(&client_hello) {
1979			return Some(cert);
1980		}
1981
1982		// If this happens, it means the client was trying to connect to an unknown hostname.
1983		// We do our best and return the first certificate.
1984		tracing::warn!(server_name = ?client_hello.server_name(), "no SNI certificate found");
1985
1986		self.info
1987			.read()
1988			.expect("info read lock poisoned")
1989			.certs
1990			.first()
1991			.cloned()
1992	}
1993}
1994
1995// ── reload_certs ────────────────────────────────────────────────────
1996
1997/// Watch the on-disk cert/key files and reload them whenever they change.
1998///
1999/// Reacting to the filesystem means cert-manager, Kubernetes secret mounts, and
2000/// `mv`-into-place rotate certs with no external signal. Returns immediately when
2001/// only generated certs are configured: there's nothing on disk to watch.
2002#[cfg(any(feature = "quinn", feature = "noq"))]
2003pub(crate) async fn reload_certs(certs: Arc<ServeCerts>, tls_config: Server) {
2004	let paths: Vec<PathBuf> = tls_config.cert.iter().chain(tls_config.key.iter()).cloned().collect();
2005	if paths.is_empty() {
2006		return;
2007	}
2008
2009	let mut watcher = match crate::watch::FileWatcher::new(&paths) {
2010		Ok(watcher) => watcher,
2011		Err(err) => {
2012			tracing::error!(%err, "failed to watch certificate files; hot reload disabled");
2013			return;
2014		}
2015	};
2016
2017	loop {
2018		watcher.changed().await;
2019		tracing::info!("reloading server certificates");
2020
2021		if let Err(err) = certs.load_certs(&tls_config) {
2022			tracing::warn!(%err, "failed to reload server certificates");
2023		}
2024	}
2025}