1use std::path::PathBuf;
15use std::sync::Arc;
16use std::time::Duration;
17
18use arc_swap::ArcSwap;
19use hyper::body::{Body, Incoming};
20use hyper::rt::{Read, Write};
21use hyper::service::Service;
22use hyper::{Request, Response};
23use hyper_util::rt::TokioExecutor;
24use hyper_util::server::conn::auto;
25use notify::{Event, EventKind, RecursiveMode, Watcher};
26use rustls::pki_types::{CertificateDer, PrivateKeyDer};
27use rustls::server::{ClientHello, ResolvesServerCert};
28use rustls::sign::CertifiedKey;
29use rustls::ServerConfig;
30use tokio::sync::mpsc;
31use tokio_rustls::TlsAcceptor;
32use tracing::{error, info, warn};
33
34use crate::config::TlsConfig;
35use crate::stream::sni::SniPattern;
36
37#[derive(Debug)]
41struct SniCertResolver {
42 certs: Vec<(SniPattern, Arc<CertifiedKey>)>,
43 default: Arc<CertifiedKey>,
44}
45
46impl ResolvesServerCert for SniCertResolver {
47 fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
48 if let Some(name) = client_hello.server_name() {
49 for (pattern, ck) in &self.certs {
50 if pattern.matches(name) {
51 return Some(ck.clone());
52 }
53 }
54 }
55 Some(self.default.clone())
56 }
57}
58
59fn certified_key(
62 chain: Vec<CertificateDer<'static>>,
63 key: PrivateKeyDer<'static>,
64 provider: &rustls::crypto::CryptoProvider,
65) -> Result<CertifiedKey, TlsError> {
66 CertifiedKey::from_der(chain, key, provider).map_err(|e| TlsError::RustlsConfig(e.to_string()))
67}
68
69pub type SharedTlsConfig = Arc<ArcSwap<ServerConfig>>;
72
73#[derive(Debug, thiserror::Error)]
77pub enum TlsError {
78 #[error("failed to read TLS certificate '{0}': {1}")]
79 CertRead(String, String),
80 #[error("TLS certificate file '{0}' contained no certificates")]
81 NoCerts(String),
82 #[error("failed to read TLS private key '{0}': {1}")]
83 KeyRead(String, String),
84 #[error("TLS private key file '{0}' contained no private key")]
85 NoKey(String),
86 #[error("unsupported TLS min_version '{0}' (expected \"1.2\" or \"1.3\")")]
87 BadMinVersion(String),
88 #[error("failed to build TLS server config: {0}")]
89 RustlsConfig(String),
90 #[error("failed to read client-CA bundle '{0}': {1}")]
91 ClientCaRead(String, String),
92 #[error("client-CA bundle '{0}' contained no certificates")]
93 NoClientCaCerts(String),
94 #[error("failed to build client certificate verifier: {0}")]
95 ClientVerifier(String),
96}
97
98pub fn install_crypto_provider() {
106 static INSTALL_PROVIDER: std::sync::Once = std::sync::Once::new();
107 INSTALL_PROVIDER.call_once(|| {
108 let _ = rustls::crypto::ring::default_provider().install_default();
109 });
110}
111
112pub fn load_cert_chain(path: &str) -> Result<Vec<CertificateDer<'static>>, TlsError> {
115 let data =
116 std::fs::read(path).map_err(|e| TlsError::CertRead(path.to_string(), e.to_string()))?;
117 let mut reader = std::io::BufReader::new(&data[..]);
118 let certs = rustls_pemfile::certs(&mut reader)
119 .collect::<Result<Vec<_>, _>>()
120 .map_err(|e| TlsError::CertRead(path.to_string(), e.to_string()))?;
121 if certs.is_empty() {
122 return Err(TlsError::NoCerts(path.to_string()));
123 }
124 Ok(certs)
125}
126
127pub fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>, TlsError> {
130 let data =
131 std::fs::read(path).map_err(|e| TlsError::KeyRead(path.to_string(), e.to_string()))?;
132 let mut reader = std::io::BufReader::new(&data[..]);
133 rustls_pemfile::private_key(&mut reader)
134 .map_err(|e| TlsError::KeyRead(path.to_string(), e.to_string()))?
135 .ok_or_else(|| TlsError::NoKey(path.to_string()))
136}
137
138fn load_client_ca_roots(path: &str) -> Result<rustls::RootCertStore, TlsError> {
142 let data =
143 std::fs::read(path).map_err(|e| TlsError::ClientCaRead(path.to_string(), e.to_string()))?;
144 let mut reader = std::io::BufReader::new(&data[..]);
145 let certs = rustls_pemfile::certs(&mut reader)
146 .collect::<Result<Vec<_>, _>>()
147 .map_err(|e| TlsError::ClientCaRead(path.to_string(), e.to_string()))?;
148 let mut roots = rustls::RootCertStore::empty();
149 let (added, _skipped) = roots.add_parsable_certificates(certs);
150 if added == 0 {
151 return Err(TlsError::NoClientCaCerts(path.to_string()));
152 }
153 Ok(roots)
154}
155
156pub fn build_server_config(
161 tls: &TlsConfig,
162 http2_enabled: bool,
163) -> Result<Arc<ServerConfig>, TlsError> {
164 install_crypto_provider();
165
166 let versions: &[&'static rustls::SupportedProtocolVersion] = match tls.min_version.as_str() {
167 "1.2" => &[&rustls::version::TLS13, &rustls::version::TLS12],
168 "1.3" => &[&rustls::version::TLS13],
169 other => return Err(TlsError::BadMinVersion(other.to_string())),
170 };
171
172 let chain = load_cert_chain(&tls.cert_path)?;
173 let key = load_private_key(&tls.key_path)?;
174
175 let provider = Arc::new(rustls::crypto::ring::default_provider());
176 let builder = ServerConfig::builder_with_provider(provider.clone())
177 .with_protocol_versions(versions)
178 .map_err(|e| TlsError::RustlsConfig(e.to_string()))?;
179
180 let builder = match &tls.client_ca_path {
183 Some(ca_path) => {
184 let roots = load_client_ca_roots(ca_path)?;
185 let vbuilder = rustls::server::WebPkiClientVerifier::builder_with_provider(
186 Arc::new(roots),
187 provider.clone(),
188 );
189 let vbuilder = if tls.client_cert_required {
190 vbuilder
191 } else {
192 vbuilder.allow_unauthenticated()
193 };
194 let verifier = vbuilder
195 .build()
196 .map_err(|e| TlsError::ClientVerifier(e.to_string()))?;
197 builder.with_client_cert_verifier(verifier)
198 }
199 None => builder.with_no_client_auth(),
200 };
201
202 let mut config = if tls.sni_certs.is_empty() {
205 builder
206 .with_single_cert(chain, key)
207 .map_err(|e| TlsError::RustlsConfig(e.to_string()))?
208 } else {
209 let default = Arc::new(certified_key(chain, key, &provider)?);
210 let mut certs = Vec::with_capacity(tls.sni_certs.len());
211 for sc in &tls.sni_certs {
212 let c = load_cert_chain(&sc.cert_path)?;
213 let k = load_private_key(&sc.key_path)?;
214 certs.push((
215 SniPattern::parse(&sc.server_name),
216 Arc::new(certified_key(c, k, &provider)?),
217 ));
218 }
219 builder.with_cert_resolver(Arc::new(SniCertResolver { certs, default }))
220 };
221
222 config.alpn_protocols = if http2_enabled {
223 vec![b"h2".to_vec(), b"http/1.1".to_vec()]
225 } else {
226 vec![b"http/1.1".to_vec()]
227 };
228
229 Ok(Arc::new(config))
230}
231
232#[allow(dead_code)]
238pub fn build_acceptor(tls: &TlsConfig, http2_enabled: bool) -> Result<TlsAcceptor, TlsError> {
239 Ok(TlsAcceptor::from(build_server_config(tls, http2_enabled)?))
240}
241
242pub fn build_reloadable(tls: &TlsConfig, http2_enabled: bool) -> Result<SharedTlsConfig, TlsError> {
246 Ok(Arc::new(ArcSwap::new(build_server_config(
247 tls,
248 http2_enabled,
249 )?)))
250}
251
252pub fn current_acceptor(shared: &SharedTlsConfig) -> TlsAcceptor {
255 TlsAcceptor::from(shared.load_full())
256}
257
258#[derive(Debug, Clone, PartialEq)]
260pub struct ClientCertIdentity {
261 pub fingerprint: String,
263 pub subject_cn: Option<String>,
265 pub san_dns: Vec<String>,
267}
268
269pub fn client_cert_identity<IO>(
273 stream: &tokio_rustls::server::TlsStream<IO>,
274) -> Option<ClientCertIdentity> {
275 let leaf = stream.get_ref().1.peer_certificates()?.first()?;
276 let digest = ring::digest::digest(&ring::digest::SHA256, leaf.as_ref());
277 let fingerprint = digest
278 .as_ref()
279 .iter()
280 .map(|b| format!("{:02x}", b))
281 .collect();
282 let (subject_cn, san_dns) = parse_client_identity(leaf.as_ref());
283 Some(ClientCertIdentity {
284 fingerprint,
285 subject_cn,
286 san_dns,
287 })
288}
289
290fn parse_client_identity(der: &[u8]) -> (Option<String>, Vec<String>) {
293 use x509_parser::prelude::*;
294 let cert = match X509Certificate::from_der(der) {
295 Ok((_rem, cert)) => cert,
296 Err(_) => return (None, Vec::new()),
297 };
298 let subject_cn = cert
299 .subject()
300 .iter_common_name()
301 .filter_map(|a| a.as_str().ok())
302 .next()
303 .map(String::from);
304 let san_dns = match cert.subject_alternative_name() {
305 Ok(Some(ext)) => ext
306 .value
307 .general_names
308 .iter()
309 .filter_map(|gn| match gn {
310 GeneralName::DNSName(name) => Some((*name).to_string()),
311 _ => None,
312 })
313 .collect(),
314 _ => Vec::new(),
315 };
316 (subject_cn, san_dns)
317}
318
319pub fn spawn_cert_watcher(
329 tls: TlsConfig,
330 http2_enabled: bool,
331 shared: SharedTlsConfig,
332 label: &'static str,
333) {
334 let (tx, mut rx) = mpsc::channel::<()>(1);
335
336 let mut paths: Vec<&String> = vec![&tls.cert_path, &tls.key_path];
339 for sc in &tls.sni_certs {
340 paths.push(&sc.cert_path);
341 paths.push(&sc.key_path);
342 }
343 let mut dirs: Vec<PathBuf> = Vec::new();
344 for path in paths {
345 let dir = PathBuf::from(path)
346 .parent()
347 .map(PathBuf::from)
348 .unwrap_or_else(|| PathBuf::from("."));
349 if !dirs.contains(&dir) {
350 dirs.push(dir);
351 }
352 }
353
354 std::thread::spawn(move || {
355 let mut watcher =
356 match notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
357 if let Ok(event) = res {
358 if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) {
359 let _ = tx.blocking_send(());
360 }
361 }
362 }) {
363 Ok(w) => w,
364 Err(e) => {
365 error!("{} cert watcher failed to start: {}", label, e);
366 return;
367 }
368 };
369
370 for dir in &dirs {
371 if let Err(e) = watcher.watch(dir, RecursiveMode::Recursive) {
372 error!("{} cert watcher failed to watch {:?}: {}", label, dir, e);
373 }
374 }
375 info!("{} TLS certificate watcher started on {:?}", label, dirs);
376
377 loop {
379 std::thread::sleep(Duration::from_secs(3600));
380 }
381 });
382
383 tokio::spawn(async move {
384 loop {
385 if rx.recv().await.is_none() {
386 break;
387 }
388 tokio::time::sleep(Duration::from_millis(500)).await;
390 while rx.try_recv().is_ok() {}
391
392 match build_server_config(&tls, http2_enabled) {
393 Ok(config) => {
394 shared.store(config);
395 info!("{} TLS certificate reloaded", label);
396 }
397 Err(e) => warn!(
398 "{} TLS certificate reload failed (keeping current): {}",
399 label, e
400 ),
401 }
402 }
403 });
404}
405
406#[allow(dead_code)]
425pub async fn serve_connection<I, S, B>(io: I, service: S, http2_enabled: bool)
426where
427 I: Read + Write + Unpin + Send + 'static,
428 S: Service<Request<Incoming>, Response = Response<B>> + Send + 'static,
429 S::Future: Send + 'static,
430 S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
431 B: Body + Send + 'static,
432 B::Data: Send,
433 B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
434{
435 if let Err(err) = build_connection(io, service, http2_enabled).await {
436 error!("Connection error: {}", err);
437 }
438}
439
440pub fn build_connection<I, S, B>(
451 io: I,
452 service: S,
453 http2_enabled: bool,
454) -> auto::UpgradeableConnection<'static, I, S, TokioExecutor>
455where
456 I: Read + Write + Unpin + Send + 'static,
457 S: Service<Request<Incoming>, Response = Response<B>> + Send + 'static,
458 S::Future: Send + 'static,
459 S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
460 B: Body + Send + 'static,
461 B::Data: Send,
462 B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
463{
464 if http2_enabled {
465 auto::Builder::new(TokioExecutor::new())
466 .http2()
467 .enable_connect_protocol()
468 .serve_connection_with_upgrades(io, service)
469 .into_owned()
470 } else {
471 auto::Builder::new(TokioExecutor::new())
472 .http1_only()
473 .serve_connection_with_upgrades(io, service)
474 .into_owned()
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::config::TlsConfig;
482
483 fn self_signed(
486 tag: &str,
487 min_version: &str,
488 ) -> (TlsConfig, std::path::PathBuf, std::path::PathBuf) {
489 let certified = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
490 let dir = std::env::temp_dir();
491 let pid = std::process::id();
492 let cert_path = dir.join(format!("featherbit_{}_{}.crt", tag, pid));
493 let key_path = dir.join(format!("featherbit_{}_{}.key", tag, pid));
494 std::fs::write(&cert_path, certified.cert.pem()).unwrap();
495 std::fs::write(&key_path, certified.key_pair.serialize_pem()).unwrap();
496 let tls = TlsConfig {
497 cert_path: cert_path.to_string_lossy().into_owned(),
498 key_path: key_path.to_string_lossy().into_owned(),
499 min_version: min_version.to_string(),
500 client_ca_path: None,
501 client_cert_required: true,
502 sni_certs: Vec::new(),
503 };
504 (tls, cert_path, key_path)
505 }
506
507 #[test]
508 fn test_install_crypto_provider_idempotent() {
509 install_crypto_provider();
510 install_crypto_provider();
511 }
512
513 #[test]
514 fn test_load_cert_and_key() {
515 let (tls, cert, key) = self_signed("load", "1.2");
516 assert!(!load_cert_chain(&tls.cert_path).unwrap().is_empty());
517 load_private_key(&tls.key_path).unwrap();
518 let _ = std::fs::remove_file(cert);
519 let _ = std::fs::remove_file(key);
520 }
521
522 #[test]
523 fn test_load_cert_missing_file() {
524 let err = load_cert_chain("does-not-exist.pem").unwrap_err();
525 assert!(matches!(err, TlsError::CertRead(_, _)));
526 }
527
528 #[test]
529 fn test_load_key_no_key_in_pem() {
530 let (tls, cert, key) = self_signed("nokey", "1.2");
532 let err = load_private_key(&tls.cert_path).unwrap_err();
533 assert!(matches!(err, TlsError::NoKey(_)));
534 let _ = std::fs::remove_file(cert);
535 let _ = std::fs::remove_file(key);
536 }
537
538 #[test]
539 fn test_alpn_reflects_http2_flag() {
540 let (tls, cert, key) = self_signed("alpn", "1.2");
541
542 let with_h2 = build_server_config(&tls, true).unwrap();
543 assert_eq!(
544 with_h2.alpn_protocols,
545 vec![b"h2".to_vec(), b"http/1.1".to_vec()]
546 );
547
548 let without_h2 = build_server_config(&tls, false).unwrap();
549 assert_eq!(without_h2.alpn_protocols, vec![b"http/1.1".to_vec()]);
550
551 let _ = std::fs::remove_file(cert);
552 let _ = std::fs::remove_file(key);
553 }
554
555 #[test]
556 fn test_min_version_1_3_ok_and_bad_rejected() {
557 let (mut tls, cert, key) = self_signed("minver", "1.3");
558 build_server_config(&tls, true).unwrap();
559
560 tls.min_version = "sslv3".to_string();
561 let err = build_server_config(&tls, true).unwrap_err();
562 assert!(matches!(err, TlsError::BadMinVersion(v) if v == "sslv3"));
563
564 let _ = std::fs::remove_file(cert);
565 let _ = std::fs::remove_file(key);
566 }
567
568 #[tokio::test]
572 async fn test_tls_round_trip() {
573 use bytes::Bytes;
574 use http_body_util::Full;
575 use hyper::service::service_fn;
576 use hyper_util::rt::TokioIo;
577 use tokio::net::TcpListener;
578
579 let (tls, cert, key) = self_signed("rt", "1.2");
580 let acceptor = build_acceptor(&tls, true).unwrap();
581
582 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
583 let addr = listener.local_addr().unwrap();
584
585 tokio::spawn(async move {
586 let (stream, _) = listener.accept().await.unwrap();
587 let tls_stream = match acceptor.accept(stream).await {
588 Ok(s) => s,
589 Err(_) => return,
590 };
591 let service = service_fn(|_req| async {
592 Ok::<_, hyper::Error>(Response::new(Full::new(Bytes::from_static(b"ok"))))
593 });
594 serve_connection(TokioIo::new(tls_stream), service, true).await;
595 });
596
597 let client = reqwest::Client::builder()
598 .danger_accept_invalid_certs(true)
599 .use_rustls_tls() .build()
601 .unwrap();
602 let resp = client
603 .get(format!("https://{}/", addr))
604 .send()
605 .await
606 .unwrap();
607 assert_eq!(resp.status(), 200);
608 assert_eq!(resp.version(), reqwest::Version::HTTP_2);
610 assert_eq!(resp.text().await.unwrap(), "ok");
611
612 let _ = std::fs::remove_file(cert);
613 let _ = std::fs::remove_file(key);
614 }
615
616 fn write_fresh_cert(cert_path: &std::path::Path, key_path: &std::path::Path) {
619 let certified = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
620 std::fs::write(cert_path, certified.cert.pem()).unwrap();
621 std::fs::write(key_path, certified.key_pair.serialize_pem()).unwrap();
622 }
623
624 #[derive(Debug)]
627 struct CapturingVerifier {
628 captured: std::sync::Arc<std::sync::Mutex<Option<Vec<u8>>>>,
629 provider: rustls::crypto::CryptoProvider,
630 }
631
632 impl rustls::client::danger::ServerCertVerifier for CapturingVerifier {
633 fn verify_server_cert(
634 &self,
635 end_entity: &rustls::pki_types::CertificateDer<'_>,
636 _intermediates: &[rustls::pki_types::CertificateDer<'_>],
637 _server_name: &rustls::pki_types::ServerName<'_>,
638 _ocsp_response: &[u8],
639 _now: rustls::pki_types::UnixTime,
640 ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
641 *self.captured.lock().unwrap() = Some(end_entity.as_ref().to_vec());
642 Ok(rustls::client::danger::ServerCertVerified::assertion())
643 }
644 fn verify_tls12_signature(
645 &self,
646 message: &[u8],
647 cert: &rustls::pki_types::CertificateDer<'_>,
648 dss: &rustls::DigitallySignedStruct,
649 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
650 rustls::crypto::verify_tls12_signature(
651 message,
652 cert,
653 dss,
654 &self.provider.signature_verification_algorithms,
655 )
656 }
657 fn verify_tls13_signature(
658 &self,
659 message: &[u8],
660 cert: &rustls::pki_types::CertificateDer<'_>,
661 dss: &rustls::DigitallySignedStruct,
662 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
663 rustls::crypto::verify_tls13_signature(
664 message,
665 cert,
666 dss,
667 &self.provider.signature_verification_algorithms,
668 )
669 }
670 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
671 self.provider
672 .signature_verification_algorithms
673 .supported_schemes()
674 }
675 }
676
677 async fn spawn_reload_server(shared: SharedTlsConfig) -> std::net::SocketAddr {
680 use tokio::net::TcpListener;
681 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
682 let addr = listener.local_addr().unwrap();
683 tokio::spawn(async move {
684 while let Ok((stream, _)) = listener.accept().await {
685 let acceptor = current_acceptor(&shared);
686 tokio::spawn(async move {
687 let _ = acceptor.accept(stream).await; });
689 }
690 });
691 addr
692 }
693
694 async fn served_leaf_cert(addr: std::net::SocketAddr) -> Vec<u8> {
696 served_leaf_cert_sni(addr, "localhost").await
697 }
698
699 async fn served_leaf_cert_sni(addr: std::net::SocketAddr, sni: &str) -> Vec<u8> {
701 use tokio::net::TcpStream;
702 install_crypto_provider();
703 let captured = std::sync::Arc::new(std::sync::Mutex::new(None));
704 let verifier = std::sync::Arc::new(CapturingVerifier {
705 captured: captured.clone(),
706 provider: rustls::crypto::ring::default_provider(),
707 });
708 let config = rustls::ClientConfig::builder()
709 .dangerous()
710 .with_custom_certificate_verifier(verifier)
711 .with_no_client_auth();
712 let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
713 let tcp = TcpStream::connect(addr).await.unwrap();
714 let name = rustls::pki_types::ServerName::try_from(sni.to_string()).unwrap();
715 let _ = connector.connect(name, tcp).await.unwrap();
716 let leaf = captured.lock().unwrap().clone();
717 leaf.expect("no server certificate captured")
718 }
719
720 #[tokio::test]
721 async fn test_cert_hot_reload_swaps_served_cert() {
722 let dir = std::env::temp_dir();
723 let pid = std::process::id();
724 let cert = dir.join(format!("featherbit_reload_{}.crt", pid));
725 let key = dir.join(format!("featherbit_reload_{}.key", pid));
726 write_fresh_cert(&cert, &key);
727 let tls = TlsConfig {
728 cert_path: cert.to_string_lossy().into_owned(),
729 key_path: key.to_string_lossy().into_owned(),
730 min_version: "1.2".to_string(),
731 client_ca_path: None,
732 client_cert_required: true,
733 sni_certs: Vec::new(),
734 };
735
736 let shared = build_reloadable(&tls, false).unwrap();
737 let addr = spawn_reload_server(shared.clone()).await;
738
739 let leaf_a = served_leaf_cert(addr).await;
740
741 write_fresh_cert(&cert, &key);
744 shared.store(build_server_config(&tls, false).unwrap());
745
746 let leaf_b = served_leaf_cert(addr).await;
747
748 assert_ne!(
749 leaf_a, leaf_b,
750 "served certificate should change after reload"
751 );
752
753 let _ = std::fs::remove_file(&cert);
754 let _ = std::fs::remove_file(&key);
755 }
756
757 #[tokio::test]
758 async fn test_cert_hot_reload_via_file_watcher() {
759 let dir = std::env::temp_dir();
760 let pid = std::process::id();
761 let cert = dir.join(format!("featherbit_watch_{}.crt", pid));
762 let key = dir.join(format!("featherbit_watch_{}.key", pid));
763 write_fresh_cert(&cert, &key);
764 let tls = TlsConfig {
765 cert_path: cert.to_string_lossy().into_owned(),
766 key_path: key.to_string_lossy().into_owned(),
767 min_version: "1.2".to_string(),
768 client_ca_path: None,
769 client_cert_required: true,
770 sni_certs: Vec::new(),
771 };
772
773 let shared = build_reloadable(&tls, false).unwrap();
774 spawn_cert_watcher(tls.clone(), false, shared.clone(), "test");
775 let addr = spawn_reload_server(shared.clone()).await;
776
777 let leaf_a = served_leaf_cert(addr).await;
778
779 write_fresh_cert(&cert, &key);
782
783 let mut leaf_b = leaf_a.clone();
784 for _ in 0..50 {
785 tokio::time::sleep(Duration::from_millis(200)).await;
786 leaf_b = served_leaf_cert(addr).await;
787 if leaf_b != leaf_a {
788 break;
789 }
790 }
791 assert_ne!(
792 leaf_a, leaf_b,
793 "file watcher should hot-reload the certificate"
794 );
795
796 let _ = std::fs::remove_file(&cert);
797 let _ = std::fs::remove_file(&key);
798 }
799
800 fn gen_ca() -> (rcgen::Certificate, rcgen::KeyPair) {
803 let mut params = rcgen::CertificateParams::new(Vec::<String>::new()).unwrap();
804 params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
805 let key = rcgen::KeyPair::generate().unwrap();
806 let cert = params.self_signed(&key).unwrap();
807 (cert, key)
808 }
809
810 fn gen_signed(
811 cn: &str,
812 ca_cert: &rcgen::Certificate,
813 ca_key: &rcgen::KeyPair,
814 ) -> (rcgen::Certificate, rcgen::KeyPair) {
815 let mut params = rcgen::CertificateParams::new(vec![cn.to_string()]).unwrap();
817 params
818 .distinguished_name
819 .push(rcgen::DnType::CommonName, cn);
820 let key = rcgen::KeyPair::generate().unwrap();
821 let cert = params.signed_by(&key, ca_cert, ca_key).unwrap();
822 (cert, key)
823 }
824
825 fn sha256_hex(bytes: &[u8]) -> String {
826 ring::digest::digest(&ring::digest::SHA256, bytes)
827 .as_ref()
828 .iter()
829 .map(|b| format!("{:02x}", b))
830 .collect()
831 }
832
833 fn mtls_config(
837 tag: &str,
838 required: bool,
839 ) -> (
840 TlsConfig,
841 rcgen::Certificate,
842 rcgen::KeyPair,
843 Vec<std::path::PathBuf>,
844 ) {
845 let (ca_cert, ca_key) = gen_ca();
846 let server = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
847 let dir = std::env::temp_dir();
848 let pid = std::process::id();
849 let scert = dir.join(format!("fb_mtls_{}_{}.crt", tag, pid));
850 let skey = dir.join(format!("fb_mtls_{}_{}.key", tag, pid));
851 let ca = dir.join(format!("fb_mtls_{}_{}_ca.crt", tag, pid));
852 std::fs::write(&scert, server.cert.pem()).unwrap();
853 std::fs::write(&skey, server.key_pair.serialize_pem()).unwrap();
854 std::fs::write(&ca, ca_cert.pem()).unwrap();
855 let tls = TlsConfig {
856 cert_path: scert.to_string_lossy().into_owned(),
857 key_path: skey.to_string_lossy().into_owned(),
858 min_version: "1.2".to_string(),
859 client_ca_path: Some(ca.to_string_lossy().into_owned()),
860 client_cert_required: required,
861 sni_certs: Vec::new(),
862 };
863 (tls, ca_cert, ca_key, vec![scert, skey, ca])
864 }
865
866 async fn spawn_mtls_server(
869 tls: &TlsConfig,
870 ) -> (
871 std::net::SocketAddr,
872 std::sync::Arc<std::sync::Mutex<Option<ClientCertIdentity>>>,
873 ) {
874 use tokio::net::TcpListener;
875 let acceptor = build_acceptor(tls, false).unwrap();
876 let captured = std::sync::Arc::new(std::sync::Mutex::new(None));
877 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
878 let addr = listener.local_addr().unwrap();
879 let cap = captured.clone();
880 tokio::spawn(async move {
881 use tokio::io::AsyncWriteExt;
882 while let Ok((stream, _)) = listener.accept().await {
883 let acceptor = acceptor.clone();
884 let cap = cap.clone();
885 tokio::spawn(async move {
886 if let Ok(mut tls_stream) = acceptor.accept(stream).await {
887 *cap.lock().unwrap() = client_cert_identity(&tls_stream);
888 let _ = tls_stream.write_all(b"1").await;
891 let _ = tls_stream.flush().await;
892 tokio::time::sleep(Duration::from_millis(50)).await;
893 }
894 });
895 }
896 });
897 (addr, captured)
898 }
899
900 async fn mtls_client_connects(
907 addr: std::net::SocketAddr,
908 client: Option<(
909 Vec<rustls::pki_types::CertificateDer<'static>>,
910 rustls::pki_types::PrivateKeyDer<'static>,
911 )>,
912 ) -> bool {
913 use tokio::io::AsyncReadExt;
914 use tokio::net::TcpStream;
915 install_crypto_provider();
916 let verifier = std::sync::Arc::new(CapturingVerifier {
917 captured: std::sync::Arc::new(std::sync::Mutex::new(None)),
918 provider: rustls::crypto::ring::default_provider(),
919 });
920 let builder = rustls::ClientConfig::builder()
921 .dangerous()
922 .with_custom_certificate_verifier(verifier);
923 let config = match client {
924 Some((chain, key)) => builder.with_client_auth_cert(chain, key).unwrap(),
925 None => builder.with_no_client_auth(),
926 };
927 let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
928 let tcp = TcpStream::connect(addr).await.unwrap();
929 let name = rustls::pki_types::ServerName::try_from("localhost").unwrap();
930 let mut tls = match connector.connect(name, tcp).await {
931 Ok(t) => t,
932 Err(_) => return false,
933 };
934 let mut buf = [0u8; 1];
935 tls.read(&mut buf).await.is_ok()
938 }
939
940 fn client_material(
941 cert: &rcgen::Certificate,
942 key: &rcgen::KeyPair,
943 ) -> (
944 Vec<rustls::pki_types::CertificateDer<'static>>,
945 rustls::pki_types::PrivateKeyDer<'static>,
946 ) {
947 let chain = vec![cert.der().clone()];
948 let key_der = rustls::pki_types::PrivateKeyDer::Pkcs8(key.serialize_der().into());
949 (chain, key_der)
950 }
951
952 #[test]
953 fn test_parse_client_identity() {
954 let mut params = rcgen::CertificateParams::new(vec![
955 "a.example.com".to_string(),
956 "b.example.com".to_string(),
957 ])
958 .unwrap();
959 params
960 .distinguished_name
961 .push(rcgen::DnType::CommonName, "svc-a");
962 let key = rcgen::KeyPair::generate().unwrap();
963 let cert = params.self_signed(&key).unwrap();
964
965 let (cn, san) = parse_client_identity(cert.der().as_ref());
966 assert_eq!(cn.as_deref(), Some("svc-a"));
967 assert_eq!(
968 san,
969 vec!["a.example.com".to_string(), "b.example.com".to_string()]
970 );
971
972 let (cn, san) = parse_client_identity(&[0xff; 16]);
974 assert_eq!(cn, None);
975 assert!(san.is_empty());
976 }
977
978 #[test]
979 fn test_mtls_config_builds_and_rejects_empty_ca() {
980 let (tls, _ca_cert, _ca_key, paths) = mtls_config("cfg", true);
981 build_server_config(&tls, false).unwrap();
983 let mut optional = tls.clone();
984 optional.client_cert_required = false;
985 build_server_config(&optional, false).unwrap();
986
987 let empty = std::env::temp_dir().join(format!("fb_mtls_empty_{}.pem", std::process::id()));
989 std::fs::write(&empty, b"not a certificate").unwrap();
990 let mut bad = tls;
991 bad.client_ca_path = Some(empty.to_string_lossy().into_owned());
992 assert!(matches!(
993 build_server_config(&bad, false),
994 Err(TlsError::NoClientCaCerts(_))
995 ));
996
997 let _ = std::fs::remove_file(&empty);
998 for p in paths {
999 let _ = std::fs::remove_file(p);
1000 }
1001 }
1002
1003 #[tokio::test]
1004 async fn test_mtls_required_enforces_client_cert() {
1005 let (tls, ca_cert, ca_key, paths) = mtls_config("req", true);
1006 let (client_cert, client_key) = gen_signed("client-1", &ca_cert, &ca_key);
1007 let expected_fp = sha256_hex(client_cert.der().as_ref());
1008
1009 let (addr, captured) = spawn_mtls_server(&tls).await;
1010
1011 let ok = mtls_client_connects(addr, Some(client_material(&client_cert, &client_key))).await;
1014 assert!(ok, "client with a valid cert should connect");
1015 let mut id = None;
1016 for _ in 0..20 {
1017 tokio::time::sleep(Duration::from_millis(25)).await;
1018 id = captured.lock().unwrap().clone();
1019 if id.is_some() {
1020 break;
1021 }
1022 }
1023 let id = id.expect("server should capture client identity");
1024 assert_eq!(id.fingerprint, expected_fp);
1025 assert_eq!(id.subject_cn.as_deref(), Some("client-1"));
1026 assert!(id.san_dns.iter().any(|s| s == "client-1"));
1027
1028 let ok = mtls_client_connects(addr, None).await;
1030 assert!(!ok, "client without a cert must be rejected when required");
1031
1032 for p in paths {
1033 let _ = std::fs::remove_file(p);
1034 }
1035 }
1036
1037 #[tokio::test]
1038 async fn test_mtls_optional_allows_anonymous() {
1039 let (tls, _ca_cert, _ca_key, paths) = mtls_config("opt", false);
1040 let (addr, captured) = spawn_mtls_server(&tls).await;
1041
1042 let ok = mtls_client_connects(addr, None).await;
1044 assert!(ok, "anonymous client should connect in optional mode");
1045 tokio::time::sleep(Duration::from_millis(75)).await;
1046 assert_eq!(captured.lock().unwrap().clone(), None);
1047
1048 for p in paths {
1049 let _ = std::fs::remove_file(p);
1050 }
1051 }
1052
1053 fn write_named_cert(tag: &str, sans: Vec<String>) -> (String, String, Vec<u8>) {
1058 let certified = rcgen::generate_simple_self_signed(sans).unwrap();
1059 let dir = std::env::temp_dir();
1060 let pid = std::process::id();
1061 let cert = dir.join(format!("fb_sni_{}_{}.crt", tag, pid));
1062 let key = dir.join(format!("fb_sni_{}_{}.key", tag, pid));
1063 std::fs::write(&cert, certified.cert.pem()).unwrap();
1064 std::fs::write(&key, certified.key_pair.serialize_pem()).unwrap();
1065 let leaf = certified.cert.der().as_ref().to_vec();
1066 (
1067 cert.to_string_lossy().into_owned(),
1068 key.to_string_lossy().into_owned(),
1069 leaf,
1070 )
1071 }
1072
1073 #[tokio::test]
1074 async fn test_sni_multicert_selects_by_hostname() {
1075 use crate::config::SniCert;
1076
1077 let (def_cert, def_key, def_leaf) = write_named_cert("def", vec!["localhost".to_string()]);
1078 let (a_cert, a_key, a_leaf) = write_named_cert("a", vec!["a.example.com".to_string()]);
1079 let (w_cert, w_key, w_leaf) =
1080 write_named_cert("wild", vec!["x.tenant.example.com".to_string()]);
1081
1082 let tls = TlsConfig {
1083 cert_path: def_cert.clone(),
1084 key_path: def_key.clone(),
1085 min_version: "1.2".to_string(),
1086 client_ca_path: None,
1087 client_cert_required: true,
1088 sni_certs: vec![
1089 SniCert {
1090 server_name: "a.example.com".to_string(),
1091 cert_path: a_cert.clone(),
1092 key_path: a_key.clone(),
1093 },
1094 SniCert {
1095 server_name: "*.tenant.example.com".to_string(),
1096 cert_path: w_cert.clone(),
1097 key_path: w_key.clone(),
1098 },
1099 ],
1100 };
1101
1102 let shared = build_reloadable(&tls, false).unwrap();
1103 let addr = spawn_reload_server(shared).await;
1104
1105 assert_eq!(served_leaf_cert_sni(addr, "a.example.com").await, a_leaf);
1107 assert_eq!(
1108 served_leaf_cert_sni(addr, "x.tenant.example.com").await,
1109 w_leaf
1110 );
1111 assert_eq!(
1112 served_leaf_cert_sni(addr, "unmatched.example.com").await,
1113 def_leaf
1114 );
1115 assert_ne!(a_leaf, def_leaf);
1117 assert_ne!(w_leaf, def_leaf);
1118
1119 for p in [def_cert, def_key, a_cert, a_key, w_cert, w_key] {
1120 let _ = std::fs::remove_file(p);
1121 }
1122 }
1123}