1use std::collections::{HashMap, HashSet};
64use std::fs::File;
65use std::io::BufReader;
66use std::path::{Path, PathBuf};
67use std::sync::Arc;
68use std::time::{Duration, Instant};
69
70use parking_lot::RwLock;
71use rustls::client::ClientConfig;
72use rustls::pki_types::CertificateDer;
73use rustls::server::{ClientHello, ResolvesServerCert};
74use rustls::sign::CertifiedKey;
75use rustls::{RootCertStore, ServerConfig};
76use tracing::{debug, error, info, trace, warn};
77
78use zentinel_config::{SniCertFolder, SniCertificate, TlsConfig, UpstreamTlsConfig};
79
80#[derive(Debug)]
82pub enum TlsError {
83 CertificateLoad(String),
85 KeyLoad(String),
87 ConfigBuild(String),
89 CertKeyMismatch(String),
91 InvalidCertificate(String),
93 OcspFetch(String),
95}
96
97impl std::fmt::Display for TlsError {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 match self {
100 TlsError::CertificateLoad(e) => write!(f, "Failed to load certificate: {}", e),
101 TlsError::KeyLoad(e) => write!(f, "Failed to load private key: {}", e),
102 TlsError::ConfigBuild(e) => write!(f, "Failed to build TLS config: {}", e),
103 TlsError::CertKeyMismatch(e) => write!(f, "Certificate/key mismatch: {}", e),
104 TlsError::InvalidCertificate(e) => write!(f, "Invalid certificate: {}", e),
105 TlsError::OcspFetch(e) => write!(f, "Failed to fetch OCSP response: {}", e),
106 }
107 }
108}
109
110impl std::error::Error for TlsError {}
111
112#[derive(Debug)]
120pub struct SniResolver {
121 default_cert: Arc<CertifiedKey>,
123 sni_certs: HashMap<String, Arc<CertifiedKey>>,
126 wildcard_certs: HashMap<String, Arc<CertifiedKey>>,
128}
129
130impl SniResolver {
131 pub fn from_config(config: &TlsConfig, listener_id: Option<&str>) -> Result<Self, TlsError> {
133 let listener_id_str = listener_id.unwrap_or("unknown");
134
135 let (cert_path_buf, key_path_buf);
137 let (cert_file, key_file) = match (&config.cert_file, &config.key_file) {
138 (Some(cert), Some(key)) => (cert.as_path(), key.as_path()),
139 _ if config.acme.is_some() => {
140 let acme = config.acme.as_ref().unwrap();
141 let primary = acme.domains.first().ok_or_else(|| {
142 TlsError::ConfigBuild(
143 "ACME configuration has no domains for cert path resolution".to_string(),
144 )
145 })?;
146 cert_path_buf = acme.storage.join("domains").join(primary).join("cert.pem");
147 key_path_buf = acme.storage.join("domains").join(primary).join("key.pem");
148 (cert_path_buf.as_path(), key_path_buf.as_path())
149 }
150 _ => {
151 return Err(TlsError::ConfigBuild(
152 "TLS configuration requires cert_file and key_file (or ACME block)".to_string(),
153 ));
154 }
155 };
156
157 let default_cert = load_certified_key(cert_file, key_file)?;
159
160 info!(
161 listener_id = %listener_id_str,
162 cert_file = %cert_file.display(),
163 "Loaded default TLS certificate"
164 );
165
166 let mut sni_certs = HashMap::new();
167 let mut wildcard_certs = HashMap::new();
168
169 let mut priority_exact: HashSet<String> = HashSet::new();
173 let mut priority_wildcard: HashSet<String> = HashSet::new();
174
175 let scanned = scan_cert_folders(&config.cert_folders, listener_id_str);
181 let all_sni_certs: Vec<&SniCertificate> = config
182 .additional_certs
183 .iter()
184 .chain(scanned.iter())
185 .collect();
186
187 for (i, sni_config) in all_sni_certs.into_iter().enumerate() {
189 let (sni_cert_path_buf, sni_key_path_buf);
191 let (sni_cert_path, sni_key_path) = match (&sni_config.cert_file, &sni_config.key_file)
192 {
193 (Some(cert), Some(key)) => (cert.as_path(), key.as_path()),
194 _ if sni_config.acme.is_some() => {
195 let acme = sni_config.acme.as_ref().unwrap();
196 let primary = acme.domains.first().ok_or_else(|| {
197 TlsError::ConfigBuild("SNI ACME configuration has no domains".to_string())
198 })?;
199 sni_cert_path_buf = acme.storage.join("domains").join(primary).join("cert.pem");
200 sni_key_path_buf = acme.storage.join("domains").join(primary).join("key.pem");
201 (sni_cert_path_buf.as_path(), sni_key_path_buf.as_path())
202 }
203 _ => unreachable!("Config validation ensures certs or acme"),
204 };
205
206 let cert = match load_certified_key(sni_cert_path, sni_key_path) {
207 Ok(cert) => Arc::new(cert),
208 Err(e) => {
209 if let Some(acme) = &sni_config.acme {
213 let primary = acme
214 .domains
215 .first()
216 .map(|s| s.as_str())
217 .unwrap_or("unknown");
218 warn!(
219 listener_id = %listener_id_str,
220 sni_index = i,
221 primary_domain = %primary,
222 error = %e,
223 "ACME SNI certificate not yet available, skipping initial load"
224 );
225
226 if let Some(metrics) = crate::tls_metrics::get_tls_metrics() {
228 metrics.record_sni_cert_skip(listener_id_str, primary);
229 }
230
231 continue;
232 } else {
233 return Err(e);
234 }
235 }
236 };
237
238 let priority_set: HashSet<String> = sni_config
240 .priority_hostnames
241 .iter()
242 .map(|h| h.to_lowercase())
243 .collect();
244 let has_priority = !priority_set.is_empty();
245
246 let hostnames = if !sni_config.hostnames.is_empty() {
248 sni_config.hostnames.clone()
249 } else if !priority_set.is_empty() {
250 extract_hostnames_from_cert(cert.cert.first().unwrap())?
252 } else if let Some(ref acme) = sni_config.acme {
253 acme.domains.clone()
255 } else {
256 extract_hostnames_from_cert(cert.cert.first().unwrap())?
258 };
259
260 if has_priority {
261 info!(
262 cert_file = %sni_cert_path.display(),
263 hostnames = ?hostnames,
264 priority_hostnames = ?sni_config.priority_hostnames,
265 "Loaded SNI certificate with priority tie-breaking"
266 );
267 } else if sni_config.hostnames.is_empty() && sni_config.acme.is_none() {
268 info!(
269 cert_file = %sni_cert_path.display(),
270 hostnames = ?hostnames,
271 "Loaded SNI certificate (auto-extracted hostnames)"
272 );
273 } else {
274 info!(
275 cert_file = %sni_cert_path.display(),
276 hostnames = ?hostnames,
277 "Loaded SNI certificate"
278 );
279 }
280
281 for hostname in &hostnames {
282 let hostname_lower = hostname.to_lowercase();
283 let is_priority = priority_set.contains(&hostname_lower);
284
285 if hostname_lower.starts_with("*.") {
286 let domain = hostname_lower.strip_prefix("*.").unwrap().to_string();
288
289 if let Some(existing) = wildcard_certs.get(&domain) {
290 if !Arc::ptr_eq(existing, &cert) {
291 let existing_has_priority = priority_wildcard.contains(&domain);
292
293 if is_priority && existing_has_priority {
294 return Err(TlsError::ConfigBuild(format!(
296 "Conflicting priority-hostnames: wildcard '*.{}' is claimed as priority by multiple certificates (including {:?}).",
297 domain,
298 sni_cert_path
299 )));
300 } else if is_priority {
301 debug!(
303 pattern = %hostname,
304 domain = %domain,
305 cert_file = %sni_cert_path.display(),
306 "Priority wildcard SNI certificate overwrites previous registration"
307 );
308 } else if existing_has_priority {
309 debug!(
311 pattern = %hostname,
312 domain = %domain,
313 cert_file = %sni_cert_path.display(),
314 "Skipping wildcard SNI registration, existing cert has priority"
315 );
316 continue;
317 } else if config.allow_sni_overlaps {
318 warn!(
324 listener_id = %listener_id_str,
325 pattern = %hostname,
326 cert_file = %sni_cert_path.display(),
327 "Overlapping wildcard SNI certificate ignored; \
328 an earlier certificate already claims this name"
329 );
330 continue;
331 } else {
332 return Err(TlsError::ConfigBuild(format!(
334 "Ambiguous SNI configuration: wildcard '*.{}' matches multiple certificates (including {:?}). \
335 Use explicit 'hostnames' or 'priority-hostnames' to resolve the conflict, \
336 or set 'allow-sni-overlaps true' to accept the first match in path order.",
337 domain,
338 sni_cert_path
339 )));
340 }
341 }
342 }
343
344 wildcard_certs.insert(domain.clone(), cert.clone());
345 if is_priority {
346 priority_wildcard.insert(domain.clone());
347 }
348 debug!(
349 pattern = %hostname,
350 domain = %domain,
351 priority = is_priority,
352 cert_file = %sni_cert_path.display(),
353 "Registered wildcard SNI certificate"
354 );
355 } else {
356 if let Some(existing) = sni_certs.get(&hostname_lower) {
358 if !Arc::ptr_eq(existing, &cert) {
359 let existing_has_priority = priority_exact.contains(&hostname_lower);
360
361 if is_priority && existing_has_priority {
362 return Err(TlsError::ConfigBuild(format!(
364 "Conflicting priority-hostnames: hostname '{}' is claimed as priority by multiple certificates (including {:?}).",
365 hostname_lower,
366 sni_cert_path
367 )));
368 } else if is_priority {
369 debug!(
371 hostname = %hostname_lower,
372 cert_file = %sni_cert_path.display(),
373 "Priority SNI certificate overwrites previous registration"
374 );
375 } else if existing_has_priority {
376 debug!(
378 hostname = %hostname_lower,
379 cert_file = %sni_cert_path.display(),
380 "Skipping SNI registration, existing cert has priority"
381 );
382 continue;
383 } else if config.allow_sni_overlaps {
384 warn!(
385 listener_id = %listener_id_str,
386 hostname = %hostname_lower,
387 cert_file = %sni_cert_path.display(),
388 "Overlapping SNI certificate ignored; \
389 an earlier certificate already claims this name"
390 );
391 continue;
392 } else {
393 return Err(TlsError::ConfigBuild(format!(
395 "Ambiguous SNI configuration: hostname '{}' matches multiple certificates (including {:?}). \
396 Use explicit 'hostnames' or 'priority-hostnames' to resolve the conflict, \
397 or set 'allow-sni-overlaps true' to accept the first match in path order.",
398 hostname_lower,
399 sni_cert_path
400 )));
401 }
402 }
403 }
404
405 sni_certs.insert(hostname_lower.clone(), cert.clone());
406 if is_priority {
407 priority_exact.insert(hostname_lower.clone());
408 }
409 debug!(
410 hostname = %hostname_lower,
411 priority = is_priority,
412 cert_file = %sni_cert_path.display(),
413 "Registered SNI certificate"
414 );
415 }
416 }
417 }
418
419 info!(
420 listener_id = %listener_id_str,
421 exact_certs = sni_certs.len(),
422 wildcard_certs = wildcard_certs.len(),
423 "SNI resolver initialized"
424 );
425
426 if let Some(metrics) = crate::tls_metrics::get_tls_metrics() {
427 metrics.set_certificates_loaded(
430 listener_id_str,
431 1 + sni_certs.len() + wildcard_certs.len(),
432 );
433 }
434
435 Ok(Self {
436 default_cert: Arc::new(default_cert),
437 sni_certs,
438 wildcard_certs,
439 })
440 }
441
442 pub fn resolve(&self, server_name: Option<&str>) -> Arc<CertifiedKey> {
447 let Some(name) = server_name else {
448 debug!("No SNI provided, using default certificate");
449 return self.default_cert.clone();
450 };
451
452 let name_lower = name.to_lowercase();
453
454 if let Some(cert) = self.sni_certs.get(&name_lower) {
456 debug!(hostname = %name_lower, "SNI exact match found");
457 return cert.clone();
458 }
459
460 let parts: Vec<&str> = name_lower.split('.').collect();
463 for i in 1..parts.len() {
464 let domain = parts[i..].join(".");
465 if let Some(cert) = self.wildcard_certs.get(&domain) {
466 debug!(
467 hostname = %name_lower,
468 wildcard_domain = %domain,
469 "SNI wildcard match found"
470 );
471 return cert.clone();
472 }
473 }
474
475 debug!(
476 hostname = %name_lower,
477 "No SNI match found, using default certificate"
478 );
479 self.default_cert.clone()
480 }
481}
482
483impl ResolvesServerCert for SniResolver {
484 fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
485 Some(self.resolve(client_hello.server_name()))
486 }
487}
488
489pub struct HotReloadableSniResolver {
499 inner: RwLock<Arc<SniResolver>>,
501 config: RwLock<TlsConfig>,
503 listener_id: String,
505 last_reload: RwLock<Instant>,
507}
508
509impl std::fmt::Debug for HotReloadableSniResolver {
510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511 f.debug_struct("HotReloadableSniResolver")
512 .field("last_reload", &*self.last_reload.read())
513 .field("listener_id", &self.listener_id)
514 .finish()
515 }
516}
517
518impl HotReloadableSniResolver {
519 pub fn from_config(
521 config: TlsConfig,
522 listener_id: impl Into<String>,
523 ) -> Result<Self, TlsError> {
524 let listener_id = listener_id.into();
525 let resolver = SniResolver::from_config(&config, Some(&listener_id))?;
526
527 Ok(Self {
528 inner: RwLock::new(Arc::new(resolver)),
529 config: RwLock::new(config),
530 listener_id,
531 last_reload: RwLock::new(Instant::now()),
532 })
533 }
534
535 pub fn reload(&self) -> Result<(), TlsError> {
540 let config = self.config.read();
541
542 let cert_file_display = config
543 .cert_file
544 .as_ref()
545 .map(|p| p.display().to_string())
546 .unwrap_or_else(|| "(acme-managed)".to_string());
547
548 info!(
549 listener_id = %self.listener_id,
550 cert_file = %cert_file_display,
551 sni_count = config.additional_certs.len(),
552 "Reloading TLS certificates"
553 );
554
555 let new_resolver = match SniResolver::from_config(&config, Some(&self.listener_id)) {
558 Ok(resolver) => resolver,
559 Err(e) => {
560 if let Some(metrics) = crate::tls_metrics::get_tls_metrics() {
561 metrics.record_reload(&self.listener_id, false);
562 }
563 return Err(e);
564 }
565 };
566
567 *self.inner.write() = Arc::new(new_resolver);
569 *self.last_reload.write() = Instant::now();
570
571 info!(
572 listener_id = %self.listener_id,
573 "TLS certificates reloaded successfully"
574 );
575 if let Some(metrics) = crate::tls_metrics::get_tls_metrics() {
576 metrics.record_reload(&self.listener_id, true);
577 }
578 Ok(())
579 }
580
581 pub fn update_config(&self, new_config: TlsConfig) -> Result<(), TlsError> {
583 let new_resolver = SniResolver::from_config(&new_config, Some(&self.listener_id))?;
585
586 *self.config.write() = new_config;
588 *self.inner.write() = Arc::new(new_resolver);
589 *self.last_reload.write() = Instant::now();
590
591 info!(
592 listener_id = %self.listener_id,
593 "TLS configuration updated and certificates reloaded"
594 );
595 Ok(())
596 }
597
598 pub fn last_reload_age(&self) -> Duration {
600 self.last_reload.read().elapsed()
601 }
602
603 pub fn resolve(&self, server_name: Option<&str>) -> Arc<CertifiedKey> {
607 self.inner.read().resolve(server_name)
608 }
609}
610
611impl ResolvesServerCert for HotReloadableSniResolver {
612 fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
613 Some(self.inner.read().resolve(client_hello.server_name()))
614 }
615}
616
617const CERT_EXTENSIONS: &[&str] = &["crt", "pem", "cert"];
619
620const KEY_EXTENSIONS: &[&str] = &["key"];
622
623fn scan_cert_folders(folders: &[SniCertFolder], listener_id: &str) -> Vec<SniCertificate> {
636 let mut found = Vec::new();
637
638 for folder in folders {
639 let dir = &folder.cert_folder;
640 let entries = match std::fs::read_dir(dir) {
641 Ok(entries) => entries,
642 Err(e) => {
643 warn!(
644 listener_id = %listener_id,
645 cert_folder = %dir.display(),
646 error = %e,
647 "Certificate folder could not be read; no certificates loaded from it"
648 );
649 continue;
650 }
651 };
652
653 let mut paths: Vec<PathBuf> = entries
657 .filter_map(|e| e.ok().map(|e| e.path()))
658 .filter(|p| p.is_file())
659 .collect();
660 paths.sort();
661
662 let mut pairs = 0usize;
663 for cert_path in &paths {
664 let Some(extension) = cert_path.extension().and_then(|e| e.to_str()) else {
665 continue;
666 };
667 if !CERT_EXTENSIONS.contains(&extension.to_ascii_lowercase().as_str()) {
668 continue;
669 }
670
671 let Some(key_path) = matching_key_path(cert_path) else {
672 warn!(
673 listener_id = %listener_id,
674 cert_file = %cert_path.display(),
675 "Certificate in scanned folder has no matching key file; skipping"
676 );
677 if let Some(metrics) = crate::tls_metrics::get_tls_metrics() {
678 metrics.record_folder_entry_skipped(listener_id, "no_key");
679 }
680 continue;
681 };
682
683 if let Err(e) = load_certified_key(cert_path, &key_path) {
688 warn!(
689 listener_id = %listener_id,
690 cert_file = %cert_path.display(),
691 key_file = %key_path.display(),
692 error = %e,
693 "Certificate pair in scanned folder could not be loaded; skipping"
694 );
695 if let Some(metrics) = crate::tls_metrics::get_tls_metrics() {
696 metrics.record_folder_entry_skipped(listener_id, "unreadable");
697 }
698 continue;
699 }
700
701 found.push(SniCertificate {
702 hostnames: Vec::new(),
703 priority_hostnames: Vec::new(),
704 cert_file: Some(cert_path.clone()),
705 key_file: Some(key_path),
706 acme: None,
707 });
708 pairs += 1;
709 }
710
711 info!(
712 listener_id = %listener_id,
713 cert_folder = %dir.display(),
714 certificates = pairs,
715 reload_mode = %folder.reload_mode,
716 "Scanned certificate folder"
717 );
718 }
719
720 found
721}
722
723fn matching_key_path(cert_path: &Path) -> Option<PathBuf> {
729 for extension in KEY_EXTENSIONS {
730 let candidate = cert_path.with_extension(extension);
731 if candidate != cert_path && candidate.is_file() {
732 return Some(candidate);
733 }
734 }
735 if cert_path
737 .extension()
738 .and_then(|e| e.to_str())?
739 .eq_ignore_ascii_case("pem")
740 && load_certified_key(cert_path, cert_path).is_ok()
741 {
742 return Some(cert_path.to_path_buf());
743 }
744 None
745}
746
747pub struct CertificateReloader {
751 resolvers: RwLock<HashMap<String, Arc<HotReloadableSniResolver>>>,
753}
754
755impl CertificateReloader {
756 pub fn new() -> Self {
758 Self {
759 resolvers: RwLock::new(HashMap::new()),
760 }
761 }
762
763 pub fn register(&self, listener_id: &str, resolver: Arc<HotReloadableSniResolver>) {
765 debug!(listener_id = %listener_id, "Registering TLS resolver for hot-reload");
766 self.resolvers
767 .write()
768 .insert(listener_id.to_string(), resolver);
769 }
770
771 pub fn reload_all(&self) -> (usize, Vec<(String, TlsError)>) {
775 let resolvers = self.resolvers.read();
776 let mut success_count = 0;
777 let mut errors = Vec::new();
778
779 info!(
780 listener_count = resolvers.len(),
781 "Reloading certificates for all TLS listeners"
782 );
783
784 for (listener_id, resolver) in resolvers.iter() {
785 match resolver.reload() {
786 Ok(()) => {
787 success_count += 1;
788 debug!(listener_id = %listener_id, "Certificate reload successful");
789 }
790 Err(e) => {
791 error!(listener_id = %listener_id, error = %e, "Certificate reload failed");
792 errors.push((listener_id.clone(), e));
793 }
794 }
795 }
796
797 if errors.is_empty() {
798 info!(
799 success_count = success_count,
800 "All certificates reloaded successfully"
801 );
802 } else {
803 warn!(
804 success_count = success_count,
805 error_count = errors.len(),
806 "Certificate reload completed with errors"
807 );
808 }
809
810 (success_count, errors)
811 }
812
813 pub fn status(&self) -> HashMap<String, Duration> {
815 self.resolvers
816 .read()
817 .iter()
818 .map(|(id, resolver)| (id.clone(), resolver.last_reload_age()))
819 .collect()
820 }
821}
822
823impl Default for CertificateReloader {
824 fn default() -> Self {
825 Self::new()
826 }
827}
828
829#[derive(Debug, Clone)]
835pub struct OcspCacheEntry {
836 pub response: Vec<u8>,
838 pub fetched_at: Instant,
840 pub expires_at: Option<Instant>,
842}
843
844pub struct OcspStapler {
848 cache: RwLock<HashMap<String, OcspCacheEntry>>,
850 refresh_interval: Duration,
852}
853
854impl OcspStapler {
855 pub fn new() -> Self {
857 Self {
858 cache: RwLock::new(HashMap::new()),
859 refresh_interval: Duration::from_secs(3600), }
861 }
862
863 pub fn with_refresh_interval(interval: Duration) -> Self {
865 Self {
866 cache: RwLock::new(HashMap::new()),
867 refresh_interval: interval,
868 }
869 }
870
871 pub fn get_response(&self, cert_fingerprint: &str) -> Option<Vec<u8>> {
873 let cache = self.cache.read();
874 if let Some(entry) = cache.get(cert_fingerprint) {
875 if entry.fetched_at.elapsed() < self.refresh_interval {
877 trace!(fingerprint = %cert_fingerprint, "OCSP cache hit");
878 return Some(entry.response.clone());
879 }
880 trace!(fingerprint = %cert_fingerprint, "OCSP cache expired");
881 }
882 None
883 }
884
885 pub fn fetch_ocsp_response(
890 &self,
891 cert_der: &[u8],
892 issuer_der: &[u8],
893 ) -> Result<Vec<u8>, TlsError> {
894 use x509_parser::prelude::*;
895
896 let (_, cert) = X509Certificate::from_der(cert_der)
898 .map_err(|e| TlsError::OcspFetch(format!("Failed to parse certificate: {}", e)))?;
899
900 let (_, issuer) = X509Certificate::from_der(issuer_der).map_err(|e| {
902 TlsError::OcspFetch(format!("Failed to parse issuer certificate: {}", e))
903 })?;
904
905 let ocsp_url = extract_ocsp_responder_url(&cert)?;
907 debug!(url = %ocsp_url, "Found OCSP responder URL");
908
909 let ocsp_request = build_ocsp_request(&cert, &issuer)?;
911
912 let response = send_ocsp_request_sync(&ocsp_url, &ocsp_request)?;
915
916 let fingerprint = calculate_cert_fingerprint(cert_der);
918
919 let entry = OcspCacheEntry {
921 response: response.clone(),
922 fetched_at: Instant::now(),
923 expires_at: None, };
925 self.cache.write().insert(fingerprint, entry);
926
927 info!("Successfully fetched and cached OCSP response");
928 Ok(response)
929 }
930
931 pub async fn fetch_ocsp_response_async(
933 &self,
934 cert_der: &[u8],
935 issuer_der: &[u8],
936 ) -> Result<Vec<u8>, TlsError> {
937 use x509_parser::prelude::*;
938
939 let (_, cert) = X509Certificate::from_der(cert_der)
941 .map_err(|e| TlsError::OcspFetch(format!("Failed to parse certificate: {}", e)))?;
942
943 let (_, issuer) = X509Certificate::from_der(issuer_der).map_err(|e| {
945 TlsError::OcspFetch(format!("Failed to parse issuer certificate: {}", e))
946 })?;
947
948 let ocsp_url = extract_ocsp_responder_url(&cert)?;
950 debug!(url = %ocsp_url, "Found OCSP responder URL");
951
952 let ocsp_request = build_ocsp_request(&cert, &issuer)?;
954
955 let response = send_ocsp_request_async(&ocsp_url, &ocsp_request).await?;
957
958 let fingerprint = calculate_cert_fingerprint(cert_der);
960
961 let entry = OcspCacheEntry {
963 response: response.clone(),
964 fetched_at: Instant::now(),
965 expires_at: None,
966 };
967 self.cache.write().insert(fingerprint, entry);
968
969 info!("Successfully fetched and cached OCSP response (async)");
970 Ok(response)
971 }
972
973 pub fn prefetch_for_config(&self, config: &TlsConfig) -> Vec<String> {
975 let mut warnings = Vec::new();
976
977 if !config.ocsp_stapling {
978 trace!("OCSP stapling disabled in config");
979 return warnings;
980 }
981
982 info!("Prefetching OCSP responses for certificates");
983
984 warnings.push("OCSP stapling prefetch not yet fully implemented".to_string());
987
988 warnings
989 }
990
991 pub fn clear_cache(&self) {
993 self.cache.write().clear();
994 info!("OCSP cache cleared");
995 }
996}
997
998impl Default for OcspStapler {
999 fn default() -> Self {
1000 Self::new()
1001 }
1002}
1003
1004fn extract_ocsp_responder_url(
1010 cert: &x509_parser::certificate::X509Certificate,
1011) -> Result<String, TlsError> {
1012 use x509_parser::prelude::*;
1013
1014 let aia = cert
1016 .extensions()
1017 .iter()
1018 .find(|ext| ext.oid == oid_registry::OID_PKIX_AUTHORITY_INFO_ACCESS)
1019 .ok_or_else(|| {
1020 TlsError::OcspFetch(
1021 "Certificate does not have Authority Information Access extension".to_string(),
1022 )
1023 })?;
1024
1025 let aia_value = match aia.parsed_extension() {
1027 ParsedExtension::AuthorityInfoAccess(aia) => aia,
1028 _ => {
1029 return Err(TlsError::OcspFetch(
1030 "Failed to parse Authority Information Access extension".to_string(),
1031 ))
1032 }
1033 };
1034
1035 for access in &aia_value.accessdescs {
1037 if access.access_method == oid_registry::OID_PKIX_ACCESS_DESCRIPTOR_OCSP {
1038 match &access.access_location {
1039 GeneralName::URI(url) => {
1040 return Ok(url.to_string());
1041 }
1042 _ => continue,
1043 }
1044 }
1045 }
1046
1047 Err(TlsError::OcspFetch(
1048 "Certificate AIA does not contain OCSP responder URL".to_string(),
1049 ))
1050}
1051
1052fn build_ocsp_request(
1056 cert: &x509_parser::certificate::X509Certificate,
1057 issuer: &x509_parser::certificate::X509Certificate,
1058) -> Result<Vec<u8>, TlsError> {
1059 use sha2::{Digest, Sha256};
1060
1061 let issuer_name_hash = {
1068 let mut hasher = Sha256::new();
1069 hasher.update(issuer.subject().as_raw());
1070 hasher.finalize()
1071 };
1072
1073 let issuer_key_hash = {
1075 let mut hasher = Sha256::new();
1076 hasher.update(issuer.public_key().subject_public_key.data.as_ref());
1077 hasher.finalize()
1078 };
1079
1080 let serial = cert.serial.to_bytes_be();
1082
1083 let request = build_ocsp_request_der(&issuer_name_hash, &issuer_key_hash, &serial);
1086
1087 Ok(request)
1088}
1089
1090fn build_ocsp_request_der(
1092 issuer_name_hash: &[u8],
1093 issuer_key_hash: &[u8],
1094 serial_number: &[u8],
1095) -> Vec<u8> {
1096 let sha256_oid: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
1098
1099 let hash_algorithm = der_sequence(&[&der_oid(sha256_oid), &der_null()]);
1101
1102 let cert_id = der_sequence(&[
1103 &hash_algorithm,
1104 &der_octet_string(issuer_name_hash),
1105 &der_octet_string(issuer_key_hash),
1106 &der_integer(serial_number),
1107 ]);
1108
1109 let request = der_sequence(&[&cert_id]);
1111
1112 let request_list = der_sequence(&[&request]);
1114
1115 let tbs_request = der_sequence(&[&request_list]);
1117
1118 der_sequence(&[&tbs_request])
1120}
1121
1122fn der_sequence(items: &[&[u8]]) -> Vec<u8> {
1124 let mut content = Vec::new();
1125 for item in items {
1126 content.extend_from_slice(item);
1127 }
1128 let mut result = vec![0x30]; result.extend(der_length(content.len()));
1130 result.extend(content);
1131 result
1132}
1133
1134fn der_oid(oid: &[u8]) -> Vec<u8> {
1135 let mut result = vec![0x06]; result.extend(der_length(oid.len()));
1137 result.extend_from_slice(oid);
1138 result
1139}
1140
1141fn der_null() -> Vec<u8> {
1142 vec![0x05, 0x00] }
1144
1145fn der_octet_string(data: &[u8]) -> Vec<u8> {
1146 let mut result = vec![0x04]; result.extend(der_length(data.len()));
1148 result.extend_from_slice(data);
1149 result
1150}
1151
1152fn der_integer(data: &[u8]) -> Vec<u8> {
1153 let mut result = vec![0x02]; let data = match data.iter().position(|&b| b != 0) {
1156 Some(pos) => &data[pos..],
1157 None => &[0],
1158 };
1159 if !data.is_empty() && data[0] & 0x80 != 0 {
1161 result.extend(der_length(data.len() + 1));
1162 result.push(0x00);
1163 } else {
1164 result.extend(der_length(data.len()));
1165 }
1166 result.extend_from_slice(data);
1167 result
1168}
1169
1170fn der_length(len: usize) -> Vec<u8> {
1171 if len < 128 {
1172 vec![len as u8]
1173 } else if len < 256 {
1174 vec![0x81, len as u8]
1175 } else {
1176 vec![0x82, (len >> 8) as u8, len as u8]
1177 }
1178}
1179
1180fn send_ocsp_request_sync(url: &str, request: &[u8]) -> Result<Vec<u8>, TlsError> {
1182 use std::io::{Read, Write};
1183 use std::net::TcpStream;
1184 use std::time::Duration;
1185
1186 let url = url::Url::parse(url)
1188 .map_err(|e| TlsError::OcspFetch(format!("Invalid OCSP URL: {}", e)))?;
1189
1190 let host = url
1191 .host_str()
1192 .ok_or_else(|| TlsError::OcspFetch("OCSP URL has no host".to_string()))?;
1193 let port = url.port().unwrap_or(80);
1194 let path = if url.path().is_empty() {
1195 "/"
1196 } else {
1197 url.path()
1198 };
1199
1200 let addr = format!("{}:{}", host, port);
1202 let mut stream = TcpStream::connect(&addr)
1203 .map_err(|e| TlsError::OcspFetch(format!("Failed to connect to OCSP responder: {}", e)))?;
1204
1205 stream
1206 .set_read_timeout(Some(Duration::from_secs(10)))
1207 .map_err(|e| TlsError::OcspFetch(format!("Failed to set timeout: {}", e)))?;
1208 stream
1209 .set_write_timeout(Some(Duration::from_secs(10)))
1210 .map_err(|e| TlsError::OcspFetch(format!("Failed to set timeout: {}", e)))?;
1211
1212 let http_request = format!(
1214 "POST {} HTTP/1.1\r\n\
1215 Host: {}\r\n\
1216 Content-Type: application/ocsp-request\r\n\
1217 Content-Length: {}\r\n\
1218 Connection: close\r\n\
1219 \r\n",
1220 path,
1221 host,
1222 request.len()
1223 );
1224
1225 stream
1227 .write_all(http_request.as_bytes())
1228 .map_err(|e| TlsError::OcspFetch(format!("Failed to send OCSP request: {}", e)))?;
1229 stream
1230 .write_all(request)
1231 .map_err(|e| TlsError::OcspFetch(format!("Failed to send OCSP request body: {}", e)))?;
1232
1233 let mut response = Vec::new();
1235 stream
1236 .read_to_end(&mut response)
1237 .map_err(|e| TlsError::OcspFetch(format!("Failed to read OCSP response: {}", e)))?;
1238
1239 let headers_end = response
1241 .windows(4)
1242 .position(|w| w == b"\r\n\r\n")
1243 .ok_or_else(|| TlsError::OcspFetch("Invalid HTTP response: no headers end".to_string()))?;
1244
1245 let body = &response[headers_end + 4..];
1246 if body.is_empty() {
1247 return Err(TlsError::OcspFetch("Empty OCSP response body".to_string()));
1248 }
1249
1250 Ok(body.to_vec())
1251}
1252
1253async fn send_ocsp_request_async(url: &str, request: &[u8]) -> Result<Vec<u8>, TlsError> {
1255 let client = reqwest::Client::builder()
1256 .timeout(Duration::from_secs(10))
1257 .build()
1258 .map_err(|e| TlsError::OcspFetch(format!("Failed to create HTTP client: {}", e)))?;
1259
1260 let response = client
1261 .post(url)
1262 .header("Content-Type", "application/ocsp-request")
1263 .body(request.to_vec())
1264 .send()
1265 .await
1266 .map_err(|e| TlsError::OcspFetch(format!("OCSP request failed: {}", e)))?;
1267
1268 if !response.status().is_success() {
1269 return Err(TlsError::OcspFetch(format!(
1270 "OCSP responder returned status: {}",
1271 response.status()
1272 )));
1273 }
1274
1275 let body = response
1276 .bytes()
1277 .await
1278 .map_err(|e| TlsError::OcspFetch(format!("Failed to read OCSP response: {}", e)))?;
1279
1280 Ok(body.to_vec())
1281}
1282
1283fn calculate_cert_fingerprint(cert_der: &[u8]) -> String {
1285 use sha2::{Digest, Sha256};
1286 let mut hasher = Sha256::new();
1287 hasher.update(cert_der);
1288 let result = hasher.finalize();
1289 hex::encode(result)
1290}
1291
1292pub fn load_client_cert_key(
1310 cert_path: &Path,
1311 key_path: &Path,
1312) -> Result<Arc<pingora_core::utils::tls::CertKey>, TlsError> {
1313 let cert_file = File::open(cert_path)
1315 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", cert_path.display(), e)))?;
1316 let mut cert_reader = BufReader::new(cert_file);
1317
1318 let cert_ders: Vec<Vec<u8>> = rustls_pemfile::certs(&mut cert_reader)
1320 .collect::<Result<Vec<_>, _>>()
1321 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", cert_path.display(), e)))?
1322 .into_iter()
1323 .map(|c| c.to_vec())
1324 .collect();
1325
1326 if cert_ders.is_empty() {
1327 return Err(TlsError::CertificateLoad(format!(
1328 "{}: No certificates found in PEM file",
1329 cert_path.display()
1330 )));
1331 }
1332
1333 let key_file = File::open(key_path)
1335 .map_err(|e| TlsError::KeyLoad(format!("{}: {}", key_path.display(), e)))?;
1336 let mut key_reader = BufReader::new(key_file);
1337
1338 let key_der = rustls_pemfile::private_key(&mut key_reader)
1340 .map_err(|e| TlsError::KeyLoad(format!("{}: {}", key_path.display(), e)))?
1341 .ok_or_else(|| {
1342 TlsError::KeyLoad(format!(
1343 "{}: No private key found in PEM file",
1344 key_path.display()
1345 ))
1346 })?
1347 .secret_der()
1348 .to_vec();
1349
1350 let cert_key = pingora_core::utils::tls::CertKey::new(cert_ders, key_der);
1352
1353 debug!(
1354 cert_path = %cert_path.display(),
1355 key_path = %key_path.display(),
1356 "Loaded mTLS client certificate for upstream connections"
1357 );
1358
1359 Ok(Arc::new(cert_key))
1360}
1361
1362pub fn build_upstream_tls_config(config: &UpstreamTlsConfig) -> Result<ClientConfig, TlsError> {
1367 let mut root_store = RootCertStore::empty();
1368
1369 if let Some(ca_path) = &config.ca_cert {
1371 let ca_file = File::open(ca_path)
1372 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", ca_path.display(), e)))?;
1373 let mut ca_reader = BufReader::new(ca_file);
1374
1375 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut ca_reader)
1376 .collect::<Result<Vec<_>, _>>()
1377 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", ca_path.display(), e)))?;
1378
1379 for cert in certs {
1380 root_store.add(cert).map_err(|e| {
1381 TlsError::InvalidCertificate(format!("Failed to add CA certificate: {}", e))
1382 })?;
1383 }
1384
1385 debug!(
1386 ca_file = %ca_path.display(),
1387 cert_count = root_store.len(),
1388 "Loaded upstream CA certificates"
1389 );
1390 } else if !config.insecure_skip_verify {
1391 root_store = RootCertStore {
1393 roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
1394 };
1395 trace!("Using webpki-roots for upstream TLS verification");
1396 }
1397
1398 let builder = ClientConfig::builder().with_root_certificates(root_store);
1400
1401 let client_config = if let (Some(cert_path), Some(key_path)) =
1402 (&config.client_cert, &config.client_key)
1403 {
1404 let cert_file = File::open(cert_path)
1406 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", cert_path.display(), e)))?;
1407 let mut cert_reader = BufReader::new(cert_file);
1408
1409 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_reader)
1410 .collect::<Result<Vec<_>, _>>()
1411 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", cert_path.display(), e)))?;
1412
1413 if certs.is_empty() {
1414 return Err(TlsError::CertificateLoad(format!(
1415 "{}: No certificates found",
1416 cert_path.display()
1417 )));
1418 }
1419
1420 let key_file = File::open(key_path)
1422 .map_err(|e| TlsError::KeyLoad(format!("{}: {}", key_path.display(), e)))?;
1423 let mut key_reader = BufReader::new(key_file);
1424
1425 let key = rustls_pemfile::private_key(&mut key_reader)
1426 .map_err(|e| TlsError::KeyLoad(format!("{}: {}", key_path.display(), e)))?
1427 .ok_or_else(|| {
1428 TlsError::KeyLoad(format!("{}: No private key found", key_path.display()))
1429 })?;
1430
1431 info!(
1432 cert_file = %cert_path.display(),
1433 "Configured mTLS client certificate for upstream connections"
1434 );
1435
1436 builder
1437 .with_client_auth_cert(certs, key)
1438 .map_err(|e| TlsError::CertKeyMismatch(format!("Failed to set client auth: {}", e)))?
1439 } else {
1440 builder.with_no_client_auth()
1442 };
1443
1444 debug!("Upstream TLS configuration built successfully");
1445 Ok(client_config)
1446}
1447
1448pub fn validate_upstream_tls_config(config: &UpstreamTlsConfig) -> Result<(), TlsError> {
1450 if let Some(ca_path) = &config.ca_cert {
1452 if !ca_path.exists() {
1453 return Err(TlsError::CertificateLoad(format!(
1454 "Upstream CA certificate not found: {}",
1455 ca_path.display()
1456 )));
1457 }
1458 }
1459
1460 if let Some(cert_path) = &config.client_cert {
1462 if !cert_path.exists() {
1463 return Err(TlsError::CertificateLoad(format!(
1464 "Upstream client certificate not found: {}",
1465 cert_path.display()
1466 )));
1467 }
1468
1469 match &config.client_key {
1471 Some(key_path) if !key_path.exists() => {
1472 return Err(TlsError::KeyLoad(format!(
1473 "Upstream client key not found: {}",
1474 key_path.display()
1475 )));
1476 }
1477 None => {
1478 return Err(TlsError::ConfigBuild(
1479 "client_cert specified without client_key".to_string(),
1480 ));
1481 }
1482 _ => {}
1483 }
1484 }
1485
1486 if config.client_key.is_some() && config.client_cert.is_none() {
1487 return Err(TlsError::ConfigBuild(
1488 "client_key specified without client_cert".to_string(),
1489 ));
1490 }
1491
1492 Ok(())
1493}
1494
1495fn load_certified_key(cert_path: &Path, key_path: &Path) -> Result<CertifiedKey, TlsError> {
1501 let cert_file = File::open(cert_path)
1503 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", cert_path.display(), e)))?;
1504 let mut cert_reader = BufReader::new(cert_file);
1505
1506 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut cert_reader)
1507 .collect::<Result<Vec<_>, _>>()
1508 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", cert_path.display(), e)))?;
1509
1510 if certs.is_empty() {
1511 return Err(TlsError::CertificateLoad(format!(
1512 "{}: No certificates found in file",
1513 cert_path.display()
1514 )));
1515 }
1516
1517 let key_file = File::open(key_path)
1519 .map_err(|e| TlsError::KeyLoad(format!("{}: {}", key_path.display(), e)))?;
1520 let mut key_reader = BufReader::new(key_file);
1521
1522 let key = rustls_pemfile::private_key(&mut key_reader)
1523 .map_err(|e| TlsError::KeyLoad(format!("{}: {}", key_path.display(), e)))?
1524 .ok_or_else(|| {
1525 TlsError::KeyLoad(format!(
1526 "{}: No private key found in file",
1527 key_path.display()
1528 ))
1529 })?;
1530
1531 let provider = rustls::crypto::CryptoProvider::get_default()
1533 .cloned()
1534 .unwrap_or_else(|| Arc::new(rustls::crypto::aws_lc_rs::default_provider()));
1535
1536 let signing_key = provider
1537 .key_provider
1538 .load_private_key(key)
1539 .map_err(|e| TlsError::CertKeyMismatch(format!("Failed to load private key: {:?}", e)))?;
1540
1541 Ok(CertifiedKey::new(certs, signing_key))
1542}
1543
1544fn extract_hostnames_from_cert(cert_der: &CertificateDer<'_>) -> Result<Vec<String>, TlsError> {
1552 use x509_parser::prelude::*;
1553
1554 let (_, cert) = X509Certificate::from_der(cert_der).map_err(|e| {
1555 TlsError::InvalidCertificate(format!("Failed to parse X.509 certificate: {}", e))
1556 })?;
1557
1558 let mut hostnames = Vec::new();
1559
1560 if let Ok(Some(san_ext)) = cert.subject_alternative_name() {
1562 for name in &san_ext.value.general_names {
1563 if let GeneralName::DNSName(dns) = name {
1564 hostnames.push(dns.to_lowercase());
1565 }
1566 }
1567 }
1568
1569 if hostnames.is_empty() {
1571 for attr in cert.subject().iter_common_name() {
1572 if let Ok(cn) = attr.as_str() {
1573 hostnames.push(cn.to_lowercase());
1574 }
1575 }
1576 }
1577
1578 if hostnames.is_empty() {
1579 return Err(TlsError::InvalidCertificate(
1580 "Certificate has no DNS names in SAN or CN".to_string(),
1581 ));
1582 }
1583
1584 Ok(hostnames)
1585}
1586
1587pub fn load_client_ca(ca_path: &Path) -> Result<RootCertStore, TlsError> {
1589 let ca_file = File::open(ca_path)
1590 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", ca_path.display(), e)))?;
1591 let mut ca_reader = BufReader::new(ca_file);
1592
1593 let mut root_store = RootCertStore::empty();
1594
1595 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut ca_reader)
1596 .collect::<Result<Vec<_>, _>>()
1597 .map_err(|e| TlsError::CertificateLoad(format!("{}: {}", ca_path.display(), e)))?;
1598
1599 for cert in certs {
1600 root_store.add(cert).map_err(|e| {
1601 TlsError::InvalidCertificate(format!("Failed to add CA certificate: {}", e))
1602 })?;
1603 }
1604
1605 if root_store.is_empty() {
1606 return Err(TlsError::CertificateLoad(format!(
1607 "{}: No CA certificates found",
1608 ca_path.display()
1609 )));
1610 }
1611
1612 info!(
1613 ca_file = %ca_path.display(),
1614 cert_count = root_store.len(),
1615 "Loaded client CA certificates"
1616 );
1617
1618 Ok(root_store)
1619}
1620
1621fn resolve_protocol_versions(config: &TlsConfig) -> Vec<&'static rustls::SupportedProtocolVersion> {
1623 use zentinel_common::types::TlsVersion;
1624
1625 let min = &config.min_version;
1626 let max = config.max_version.as_ref().unwrap_or(&TlsVersion::Tls13);
1627
1628 let mut versions = Vec::new();
1629
1630 if matches!(min, TlsVersion::Tls12) {
1632 versions.push(&rustls::version::TLS12);
1633 }
1634
1635 if matches!(max, TlsVersion::Tls13) {
1637 versions.push(&rustls::version::TLS13);
1638 }
1639
1640 if versions.is_empty() {
1641 warn!("No valid TLS versions resolved from config, falling back to TLS 1.2 + 1.3");
1643 versions.push(&rustls::version::TLS12);
1644 versions.push(&rustls::version::TLS13);
1645 }
1646
1647 versions
1648}
1649
1650fn resolve_cipher_suites(names: &[String]) -> Result<Vec<rustls::SupportedCipherSuite>, TlsError> {
1654 use rustls::crypto::aws_lc_rs::cipher_suite;
1655
1656 let known: &[(&str, rustls::SupportedCipherSuite)] = &[
1658 (
1660 "TLS_AES_256_GCM_SHA384",
1661 cipher_suite::TLS13_AES_256_GCM_SHA384,
1662 ),
1663 (
1664 "TLS_AES_128_GCM_SHA256",
1665 cipher_suite::TLS13_AES_128_GCM_SHA256,
1666 ),
1667 (
1668 "TLS_CHACHA20_POLY1305_SHA256",
1669 cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
1670 ),
1671 (
1673 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
1674 cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
1675 ),
1676 (
1677 "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
1678 cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
1679 ),
1680 (
1681 "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
1682 cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
1683 ),
1684 (
1685 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
1686 cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
1687 ),
1688 (
1689 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
1690 cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
1691 ),
1692 (
1693 "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
1694 cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
1695 ),
1696 ];
1697
1698 let mut suites = Vec::with_capacity(names.len());
1699 for name in names {
1700 let normalized = name.to_uppercase().replace('-', "_");
1701 match known.iter().find(|(n, _)| *n == normalized) {
1702 Some((_, suite)) => suites.push(*suite),
1703 None => {
1704 let available: Vec<&str> = known.iter().map(|(n, _)| *n).collect();
1705 return Err(TlsError::ConfigBuild(format!(
1706 "Unknown cipher suite '{}'. Available: {}",
1707 name,
1708 available.join(", ")
1709 )));
1710 }
1711 }
1712 }
1713
1714 Ok(suites)
1715}
1716
1717pub fn build_server_config(
1727 config: &TlsConfig,
1728 listener_id: &str,
1729) -> Result<ServerConfig, TlsError> {
1730 let resolver = SniResolver::from_config(config, Some(listener_id))?;
1731 build_server_config_with_resolver(config, Arc::new(resolver))
1732}
1733
1734pub fn build_server_config_with_resolver(
1743 config: &TlsConfig,
1744 resolver: Arc<dyn ResolvesServerCert>,
1745) -> Result<ServerConfig, TlsError> {
1746 let versions = resolve_protocol_versions(config);
1748 info!(
1749 versions = ?versions.iter().map(|v| format!("{:?}", v.version)).collect::<Vec<_>>(),
1750 "TLS protocol versions configured"
1751 );
1752
1753 let builder = if !config.cipher_suites.is_empty() {
1755 let suites = resolve_cipher_suites(&config.cipher_suites)?;
1756 info!(
1757 cipher_suites = ?config.cipher_suites,
1758 count = suites.len(),
1759 "Custom TLS cipher suites configured"
1760 );
1761 let provider = rustls::crypto::CryptoProvider {
1762 cipher_suites: suites,
1763 ..rustls::crypto::aws_lc_rs::default_provider()
1764 };
1765 ServerConfig::builder_with_provider(Arc::new(provider))
1766 .with_protocol_versions(&versions)
1767 .map_err(|e| {
1768 TlsError::ConfigBuild(format!("Invalid TLS protocol/cipher configuration: {}", e))
1769 })?
1770 } else {
1771 ServerConfig::builder_with_protocol_versions(&versions)
1772 };
1773
1774 let server_config = if config.client_auth {
1776 if let Some(ca_path) = &config.ca_file {
1777 let root_store = load_client_ca(ca_path)?;
1778 let verifier = rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
1779 .build()
1780 .map_err(|e| {
1781 TlsError::ConfigBuild(format!("Failed to build client verifier: {}", e))
1782 })?;
1783
1784 info!("mTLS enabled: client certificates required");
1785
1786 builder
1787 .with_client_cert_verifier(verifier)
1788 .with_cert_resolver(resolver.clone())
1789 } else {
1790 return Err(TlsError::ConfigBuild(
1797 "client_auth is enabled but no ca_file is configured. Client certificates \
1798 cannot be verified without a CA, and serving without client authentication \
1799 would contradict the configuration."
1800 .to_string(),
1801 ));
1802 }
1803 } else {
1804 builder
1805 .with_no_client_auth()
1806 .with_cert_resolver(resolver.clone())
1807 };
1808
1809 let mut server_config = server_config;
1811 server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
1812
1813 if !config.session_resumption {
1815 server_config.session_storage = Arc::new(rustls::server::NoServerSessionStorage {});
1816 info!("TLS session resumption disabled");
1817 }
1818
1819 debug!("TLS configuration built successfully");
1820
1821 Ok(server_config)
1822}
1823
1824pub fn validate_tls_config(config: &TlsConfig) -> Result<(), TlsError> {
1826 if config.acme.is_some() {
1828 trace!("Skipping manual cert validation for ACME-managed TLS");
1830 } else {
1831 match (&config.cert_file, &config.key_file) {
1833 (Some(cert_file), Some(key_file)) => {
1834 if !cert_file.exists() {
1835 return Err(TlsError::CertificateLoad(format!(
1836 "Certificate file not found: {}",
1837 cert_file.display()
1838 )));
1839 }
1840 if !key_file.exists() {
1841 return Err(TlsError::KeyLoad(format!(
1842 "Key file not found: {}",
1843 key_file.display()
1844 )));
1845 }
1846 }
1847 _ => {
1848 return Err(TlsError::ConfigBuild(
1849 "TLS configuration requires cert_file and key_file (or ACME block)".to_string(),
1850 ));
1851 }
1852 }
1853 }
1854
1855 for sni in &config.additional_certs {
1857 if sni.acme.is_some() {
1859 trace!("Skipping manual cert validation for ACME-managed SNI certificate");
1860 continue;
1861 }
1862
1863 match (&sni.cert_file, &sni.key_file) {
1865 (Some(cert_file), Some(key_file)) => {
1866 if !cert_file.exists() {
1867 return Err(TlsError::CertificateLoad(format!(
1868 "SNI certificate file not found: {}",
1869 cert_file.display()
1870 )));
1871 }
1872 if !key_file.exists() {
1873 return Err(TlsError::KeyLoad(format!(
1874 "SNI key file not found: {}",
1875 key_file.display()
1876 )));
1877 }
1878 }
1879 _ => {
1880 return Err(TlsError::ConfigBuild(
1881 "SNI certificate requires cert_file and key_file (or ACME block)".to_string(),
1882 ));
1883 }
1884 }
1885 }
1886
1887 if config.client_auth {
1889 if let Some(ca_path) = &config.ca_file {
1890 if !ca_path.exists() {
1891 return Err(TlsError::CertificateLoad(format!(
1892 "CA certificate file not found: {}",
1893 ca_path.display()
1894 )));
1895 }
1896 }
1897 }
1898
1899 Ok(())
1900}
1901
1902#[cfg(test)]
1903mod tests {
1904
1905 #[test]
1906 fn test_wildcard_matching() {
1907 let name = "foo.bar.example.com";
1910 let parts: Vec<&str> = name.split('.').collect();
1911
1912 assert_eq!(parts.len(), 4);
1913
1914 let domain1 = parts[1..].join(".");
1916 assert_eq!(domain1, "bar.example.com");
1917
1918 let domain2 = parts[2..].join(".");
1919 assert_eq!(domain2, "example.com");
1920 }
1921
1922 #[test]
1923 fn test_hostname_normalization() {
1924 let hostname = "Example.COM";
1925 let normalized = hostname.to_lowercase();
1926 assert_eq!(normalized, "example.com");
1927 }
1928}