1use crate::connection_error::ConnectionError;
11use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
12use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
13use rustls::{
14 ClientConfig, DigitallySignedStruct, Error as RustlsError, RootCertStore, SignatureScheme,
15};
16use std::sync::Arc;
17use tokio::net::TcpStream;
18use tokio_rustls::TlsConnector;
19use tracing::{debug, warn};
20
21pub use tokio_rustls::client::TlsStream;
23
24#[derive(Debug, Clone)]
26pub struct TlsConfig {
27 pub use_tls: bool,
29 pub tls_verify_cert: bool,
31 pub tls_cert_path: Option<String>,
33}
34
35impl Default for TlsConfig {
36 fn default() -> Self {
37 Self {
38 use_tls: false,
39 tls_verify_cert: true,
40 tls_cert_path: None,
41 }
42 }
43}
44
45impl TlsConfig {
46 #[must_use]
58 pub fn builder() -> TlsConfigBuilder {
59 TlsConfigBuilder::default()
60 }
61}
62
63#[derive(Debug, Clone)]
100pub struct TlsConfigBuilder {
101 use_tls: bool,
102 tls_verify_cert: bool,
103 tls_cert_path: Option<String>,
104}
105
106impl Default for TlsConfigBuilder {
107 fn default() -> Self {
108 Self {
109 use_tls: false,
110 tls_verify_cert: true, tls_cert_path: None,
112 }
113 }
114}
115
116impl TlsConfigBuilder {
117 #[must_use]
121 pub const fn enabled(mut self, use_tls: bool) -> Self {
122 self.use_tls = use_tls;
123 self
124 }
125
126 #[must_use]
133 pub const fn verify_cert(mut self, verify: bool) -> Self {
134 self.tls_verify_cert = verify;
135 self
136 }
137
138 #[must_use]
142 pub fn cert_path<S: Into<String>>(mut self, path: S) -> Self {
143 self.tls_cert_path = Some(path.into());
144 self
145 }
146
147 #[must_use]
149 pub fn build(self) -> TlsConfig {
150 TlsConfig {
151 use_tls: self.use_tls,
152 tls_verify_cert: self.tls_verify_cert,
153 tls_cert_path: self.tls_cert_path,
154 }
155 }
156}
157
158mod rustls_backend {
161 use super::{
162 CertificateDer, DigitallySignedStruct, HandshakeSignatureValid, RootCertStore, RustlsError,
163 ServerCertVerified, ServerCertVerifier, ServerName, SignatureScheme, UnixTime,
164 };
165
166 #[derive(Debug)]
168 pub struct CertificateLoadResult {
169 pub root_store: RootCertStore,
170 pub sources: Vec<String>,
171 }
172
173 #[derive(Debug)]
179 pub struct NoVerifier;
180
181 impl ServerCertVerifier for NoVerifier {
182 fn verify_server_cert(
183 &self,
184 _end_entity: &CertificateDer<'_>,
185 _intermediates: &[CertificateDer<'_>],
186 _server_name: &ServerName<'_>,
187 _ocsp_response: &[u8],
188 _now: UnixTime,
189 ) -> Result<ServerCertVerified, RustlsError> {
190 Ok(ServerCertVerified::assertion())
192 }
193
194 fn verify_tls12_signature(
195 &self,
196 _message: &[u8],
197 _cert: &CertificateDer<'_>,
198 _dss: &DigitallySignedStruct,
199 ) -> Result<HandshakeSignatureValid, RustlsError> {
200 Ok(HandshakeSignatureValid::assertion())
202 }
203
204 fn verify_tls13_signature(
205 &self,
206 _message: &[u8],
207 _cert: &CertificateDer<'_>,
208 _dss: &DigitallySignedStruct,
209 ) -> Result<HandshakeSignatureValid, RustlsError> {
210 Ok(HandshakeSignatureValid::assertion())
212 }
213
214 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
215 vec![
217 SignatureScheme::RSA_PKCS1_SHA1,
218 SignatureScheme::ECDSA_SHA1_Legacy,
219 SignatureScheme::RSA_PKCS1_SHA256,
220 SignatureScheme::ECDSA_NISTP256_SHA256,
221 SignatureScheme::RSA_PKCS1_SHA384,
222 SignatureScheme::ECDSA_NISTP384_SHA384,
223 SignatureScheme::RSA_PKCS1_SHA512,
224 SignatureScheme::ECDSA_NISTP521_SHA512,
225 SignatureScheme::RSA_PSS_SHA256,
226 SignatureScheme::RSA_PSS_SHA384,
227 SignatureScheme::RSA_PSS_SHA512,
228 SignatureScheme::ED25519,
229 SignatureScheme::ED448,
230 ]
231 }
232 }
233}
234
235pub struct TlsManager {
241 config: TlsConfig,
242 cached_connector: Arc<TlsConnector>,
247}
248
249impl std::fmt::Debug for TlsManager {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 f.debug_struct("TlsManager")
252 .field("config", &self.config)
253 .field("cached_connector", &"<TlsConnector>")
254 .finish()
255 }
256}
257
258impl Clone for TlsManager {
259 fn clone(&self) -> Self {
260 Self {
261 config: self.config.clone(),
262 cached_connector: Arc::clone(&self.cached_connector),
263 }
264 }
265}
266
267impl TlsManager {
268 pub fn new(config: TlsConfig) -> Result<Self, anyhow::Error> {
277 let cert_result = Self::load_certificates_sync(&config)?;
279 let client_config = Self::create_optimized_config_inner(cert_result.root_store, &config)?;
280
281 debug!(
282 "TLS: Initialized with certificate sources: {}",
283 cert_result.sources.join(", ")
284 );
285
286 let cached_connector = Arc::new(TlsConnector::from(Arc::new(client_config)));
287
288 Ok(Self {
289 config,
290 cached_connector,
291 })
292 }
293
294 pub async fn handshake(
300 &self,
301 stream: TcpStream,
302 hostname: &str,
303 backend_name: &str,
304 ) -> Result<TlsStream<TcpStream>, anyhow::Error> {
305 use anyhow::Context;
306
307 debug!("TLS: Connecting to {} with cached config", hostname);
308
309 let domain = rustls_pki_types::ServerName::try_from(hostname)
310 .context("Invalid hostname for TLS")?
311 .to_owned();
312
313 self.cached_connector
314 .connect(domain, stream)
315 .await
316 .map_err(|e| {
317 ConnectionError::TlsHandshake {
318 backend: backend_name.to_string(),
319 source: Box::new(e),
320 }
321 .into()
322 })
323 }
324
325 fn load_certificates_sync(
327 config: &TlsConfig,
328 ) -> Result<rustls_backend::CertificateLoadResult, anyhow::Error> {
329 let mut root_store = RootCertStore::empty();
330 let mut sources = Vec::new();
331
332 if let Some(cert_path) = &config.tls_cert_path {
334 debug!("TLS: Loading custom CA certificate from: {}", cert_path);
335 Self::load_custom_certificate_sync(&mut root_store, cert_path)?;
336 sources.push("custom certificate".to_string());
337 }
338
339 let system_count = Self::load_system_certificates_sync(&mut root_store);
341 if system_count > 0 {
342 debug!(
343 "TLS: Loaded {} certificates from system store",
344 system_count
345 );
346 sources.push("system certificates".to_string());
347 }
348
349 if root_store.is_empty() {
351 debug!("TLS: No system certificates available, using Mozilla CA bundle fallback");
352 root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
353 sources.push("Mozilla CA bundle".to_string());
354 }
355
356 Ok(rustls_backend::CertificateLoadResult {
357 root_store,
358 sources,
359 })
360 }
361
362 fn load_custom_certificate_sync(
364 root_store: &mut RootCertStore,
365 cert_path: &str,
366 ) -> Result<(), anyhow::Error> {
367 use anyhow::Context;
368
369 let cert_data = std::fs::read(cert_path)
370 .with_context(|| format!("Failed to read TLS certificate from {cert_path}"))?;
371
372 let certs = rustls_pemfile::certs(&mut cert_data.as_slice())
373 .collect::<Result<Vec<_>, _>>()
374 .context("Failed to parse TLS certificate")?;
375
376 for cert in certs {
377 root_store
378 .add(cert)
379 .context("Failed to add custom certificate to store")?;
380 }
381
382 Ok(())
383 }
384
385 fn load_system_certificates_sync(root_store: &mut RootCertStore) -> usize {
387 let cert_result = rustls_native_certs::load_native_certs();
388 let mut added_count = 0;
389
390 for cert in cert_result.certs {
391 if root_store.add(cert).is_ok() {
392 added_count += 1;
393 }
394 }
395
396 for error in cert_result.errors {
398 warn!("TLS: Certificate loading error: {}", error);
399 }
400
401 added_count
402 }
403
404 fn create_optimized_config_inner(
406 root_store: RootCertStore,
407 config: &TlsConfig,
408 ) -> Result<ClientConfig, anyhow::Error> {
409 use anyhow::Context;
410 use rustls_backend::NoVerifier;
411
412 let mut client_config = if config.tls_verify_cert {
413 debug!("TLS: Certificate verification enabled with ring crypto provider");
414 ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
415 .with_safe_default_protocol_versions()
416 .context("Failed to create TLS config with ring provider")?
417 .with_root_certificates(root_store)
418 .with_no_client_auth()
419 } else {
420 warn!(
421 "TLS: Certificate verification DISABLED - this is insecure and should only be used for testing!"
422 );
423 ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
425 .with_safe_default_protocol_versions()
426 .context("Failed to create TLS config with ring provider")?
427 .dangerous()
428 .with_custom_certificate_verifier(Arc::new(NoVerifier))
429 .with_no_client_auth()
430 };
431
432 client_config.enable_early_data = true; client_config.resumption = rustls::client::Resumption::default(); Ok(client_config)
443 }
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn test_tls_config_default() {
452 let config = TlsConfig::default();
453 assert!(!config.use_tls);
454 assert!(config.tls_verify_cert);
455 assert!(config.tls_cert_path.is_none());
456 }
457
458 #[test]
459 fn test_tls_config_builder_default() {
460 let config = TlsConfig::builder().build();
461 assert!(!config.use_tls);
462 assert!(config.tls_verify_cert); assert!(config.tls_cert_path.is_none());
464 }
465
466 #[test]
467 fn test_tls_config_builder_enabled() {
468 let config = TlsConfig::builder().enabled(true).verify_cert(true).build();
469 assert!(config.use_tls);
470 assert!(config.tls_verify_cert);
471 assert!(config.tls_cert_path.is_none());
472 }
473
474 #[test]
475 fn test_tls_config_builder_with_cert_path() {
476 let config = TlsConfig::builder()
477 .enabled(true)
478 .verify_cert(true)
479 .cert_path("/path/to/cert.pem")
480 .build();
481 assert!(config.use_tls);
482 assert!(config.tls_verify_cert);
483 assert_eq!(config.tls_cert_path, Some("/path/to/cert.pem".to_string()));
484 }
485
486 #[test]
487 fn test_tls_config_builder_insecure() {
488 let config = TlsConfig::builder()
489 .enabled(true)
490 .verify_cert(false)
491 .build();
492 assert!(config.use_tls);
493 assert!(!config.tls_verify_cert);
494 assert!(config.tls_cert_path.is_none());
495 }
496
497 #[test]
498 fn test_tls_config_builder_fluent_api() {
499 let config = TlsConfig::builder()
500 .enabled(true)
501 .verify_cert(true)
502 .cert_path("/custom/ca.pem".to_string())
503 .build();
504 assert!(config.use_tls);
505 assert!(config.tls_verify_cert);
506 assert_eq!(config.tls_cert_path, Some("/custom/ca.pem".to_string()));
507 }
508
509 #[test]
510 fn test_tls_manager_creation() {
511 let config = TlsConfig::default();
512 let manager = TlsManager::new(config).unwrap();
513 assert!(Arc::strong_count(&manager.cached_connector) >= 1);
515 }
516
517 #[test]
518 fn test_certificate_loading() {
519 let config = TlsConfig::default();
520
521 let result = TlsManager::load_certificates_sync(&config).unwrap();
522 assert!(!result.root_store.is_empty());
523 assert!(!result.sources.is_empty());
525 assert!(
527 result
528 .sources
529 .iter()
530 .any(|s| s.contains("Mozilla") || s.contains("system"))
531 );
532 }
533
534 #[test]
535 fn test_tls_config_builder_chaining() {
536 let config = TlsConfig::builder()
537 .enabled(false)
538 .verify_cert(true)
539 .enabled(true) .build();
541
542 assert!(config.use_tls);
543 assert!(config.tls_verify_cert);
544 }
545
546 #[test]
547 fn test_tls_config_clone() {
548 let config1 = TlsConfig::builder()
549 .enabled(true)
550 .cert_path("/test/path")
551 .build();
552
553 let config2 = config1.clone();
554 assert_eq!(config1.use_tls, config2.use_tls);
555 assert_eq!(config1.tls_verify_cert, config2.tls_verify_cert);
556 assert_eq!(config1.tls_cert_path, config2.tls_cert_path);
557 }
558
559 #[test]
560 fn test_tls_manager_clone() {
561 let config = TlsConfig::default();
562 let manager1 = TlsManager::new(config).unwrap();
563 let manager2 = manager1.clone();
564
565 assert!(Arc::ptr_eq(
567 &manager1.cached_connector,
568 &manager2.cached_connector
569 ));
570 }
571
572 #[test]
573 fn test_tls_manager_debug() {
574 let config = TlsConfig::default();
575 let manager = TlsManager::new(config).unwrap();
576 let debug_str = format!("{manager:?}");
577
578 assert!(debug_str.contains("TlsManager"));
579 assert!(debug_str.contains("<TlsConnector>"));
580 }
581
582 #[test]
583 fn test_tls_config_builder_cert_path_string_types() {
584 let config1 = TlsConfig::builder().cert_path("/path/to/cert.pem").build();
586 assert_eq!(config1.tls_cert_path, Some("/path/to/cert.pem".to_string()));
587
588 let config2 = TlsConfig::builder()
590 .cert_path("/another/path.pem".to_string())
591 .build();
592 assert_eq!(config2.tls_cert_path, Some("/another/path.pem".to_string()));
593 }
594
595 #[test]
596 fn test_no_verifier_supported_schemes() {
597 use rustls_backend::NoVerifier;
598
599 let verifier = NoVerifier;
600 let schemes = verifier.supported_verify_schemes();
601
602 assert!(schemes.contains(&SignatureScheme::RSA_PKCS1_SHA256));
604 assert!(schemes.contains(&SignatureScheme::ECDSA_NISTP256_SHA256));
605 assert!(schemes.contains(&SignatureScheme::ED25519));
606 assert!(schemes.len() >= 10); }
608
609 #[test]
610 fn test_certificate_load_result_sources() {
611 let config = TlsConfig::default();
612 let result = TlsManager::load_certificates_sync(&config).unwrap();
613
614 assert!(!result.sources.is_empty());
616
617 for source in &result.sources {
619 assert!(!source.is_empty());
620 }
621 }
622
623 #[test]
624 fn test_tls_config_builder_defaults_are_secure() {
625 let config = TlsConfig::builder().enabled(true).build();
627
628 assert!(config.use_tls);
629 assert!(config.tls_verify_cert); assert!(config.tls_cert_path.is_none());
631 }
632
633 #[test]
634 fn test_tls_manager_with_verify_disabled() {
635 let config = TlsConfig::builder()
636 .enabled(true)
637 .verify_cert(false)
638 .build();
639 let manager = TlsManager::new(config);
640
641 assert!(manager.is_ok());
643 }
644
645 #[test]
646 fn test_tls_manager_with_verify_enabled() {
647 let config = TlsConfig::builder().enabled(true).verify_cert(true).build();
648 let manager = TlsManager::new(config);
649
650 assert!(manager.is_ok());
652 }
653
654 #[test]
655 fn test_certificate_loading_fallback_to_mozilla_bundle() {
656 let config = TlsConfig::default();
658 let result = TlsManager::load_certificates_sync(&config).unwrap();
659
660 assert!(!result.root_store.is_empty());
662 assert!(!result.sources.is_empty());
663 }
664
665 #[test]
666 fn test_tls_config_debug_format() {
667 let config = TlsConfig::builder()
668 .enabled(true)
669 .verify_cert(false)
670 .cert_path("/test")
671 .build();
672
673 let debug_str = format!("{config:?}");
674
675 assert!(debug_str.contains("TlsConfig"));
676 assert!(debug_str.contains("use_tls"));
677 assert!(debug_str.contains("tls_verify_cert"));
678 }
679
680 #[test]
681 fn test_tls_config_builder_debug_format() {
682 let builder = TlsConfig::builder().enabled(true).verify_cert(false);
683
684 let debug_str = format!("{builder:?}");
685
686 assert!(debug_str.contains("TlsConfigBuilder"));
687 }
688
689 #[test]
690 fn test_no_verifier_debug_format() {
691 use rustls_backend::NoVerifier;
692
693 let verifier = NoVerifier;
694 let debug_str = format!("{verifier:?}");
695
696 assert!(debug_str.contains("NoVerifier"));
697 }
698
699 #[test]
700 fn test_certificate_load_result_debug_format() {
701 let config = TlsConfig::default();
702 let result = TlsManager::load_certificates_sync(&config).unwrap();
703
704 let debug_str = format!("{result:?}");
705
706 assert!(debug_str.contains("CertificateLoadResult"));
707 assert!(debug_str.contains("root_store"));
708 assert!(debug_str.contains("sources"));
709 }
710
711 #[test]
712 fn test_tls_config_builder_cert_path_empty_string() {
713 let config = TlsConfig::builder().cert_path("").build();
715
716 assert_eq!(config.tls_cert_path, Some(String::new()));
717 }
718
719 #[test]
720 fn test_tls_config_builder_cert_path_with_spaces() {
721 let config = TlsConfig::builder()
722 .cert_path(" /path/with spaces.pem ")
723 .build();
724
725 assert_eq!(
727 config.tls_cert_path,
728 Some(" /path/with spaces.pem ".to_string())
729 );
730 }
731
732 #[test]
733 fn test_multiple_tls_managers_from_same_config() {
734 let config = TlsConfig::builder()
735 .enabled(true)
736 .verify_cert(false)
737 .build();
738
739 let manager1 = TlsManager::new(config.clone()).unwrap();
740 let manager2 = TlsManager::new(config).unwrap();
741
742 let debug1 = format!("{manager1:?}");
744 let debug2 = format!("{manager2:?}");
745
746 assert!(debug1.contains("TlsManager"));
747 assert!(debug2.contains("TlsManager"));
748 }
749
750 #[test]
751 fn test_tls_config_all_combinations() {
752 for use_tls in [true, false] {
754 for verify_cert in [true, false] {
755 let config = TlsConfig::builder()
756 .enabled(use_tls)
757 .verify_cert(verify_cert)
758 .build();
759
760 assert_eq!(config.use_tls, use_tls);
761 assert_eq!(config.tls_verify_cert, verify_cert);
762
763 if use_tls {
765 let manager = TlsManager::new(config);
766 assert!(manager.is_ok());
767 }
768 }
769 }
770 }
771
772 #[test]
773 fn test_tls_config_builder_method_chaining_order() {
774 let config1 = TlsConfig::builder()
776 .enabled(true)
777 .verify_cert(false)
778 .cert_path("/test")
779 .build();
780
781 let config2 = TlsConfig::builder()
782 .cert_path("/test")
783 .verify_cert(false)
784 .enabled(true)
785 .build();
786
787 assert_eq!(config1.use_tls, config2.use_tls);
788 assert_eq!(config1.tls_verify_cert, config2.tls_verify_cert);
789 assert_eq!(config1.tls_cert_path, config2.tls_cert_path);
790 }
791
792 #[test]
793 fn test_tls_config_default_matches_builder_default() {
794 let default_config = TlsConfig::default();
795 let builder_config = TlsConfig::builder().build();
796
797 assert_eq!(default_config.use_tls, builder_config.use_tls);
798 assert_eq!(
799 default_config.tls_verify_cert,
800 builder_config.tls_verify_cert
801 );
802 assert_eq!(default_config.tls_cert_path, builder_config.tls_cert_path);
803 }
804}