Skip to main content

nntp_proxy/
tls.rs

1//! TLS configuration and handshake management for NNTP connections
2//!
3//! This module provides high-performance TLS support using rustls with optimizations:
4//! - Ring crypto provider for pure Rust crypto operations
5//! - TLS 1.3 early data (0-RTT) enabled for faster reconnections
6//! - Session resumption enabled to avoid full handshakes
7//! - Pure Rust implementation (memory safe, no C dependencies)
8//! - System certificate loading with Mozilla CA bundle fallback
9
10use 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
21// Re-export TlsStream for use in other modules
22pub use tokio_rustls::client::TlsStream;
23
24/// Configuration for TLS connections
25#[derive(Debug, Clone)]
26pub struct TlsConfig {
27    /// Enable TLS for this connection
28    pub use_tls: bool,
29    /// Verify server certificates (recommended: true)  
30    pub tls_verify_cert: bool,
31    /// Path to custom CA certificate file (optional)
32    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    /// Create a builder for `TlsConfig`
47    ///
48    /// # Example
49    /// ```
50    /// use nntp_proxy::tls::TlsConfig;
51    ///
52    /// let config = TlsConfig::builder()
53    ///     .enabled(true)
54    ///     .verify_cert(true)
55    ///     .build();
56    /// ```
57    #[must_use]
58    pub fn builder() -> TlsConfigBuilder {
59        TlsConfigBuilder::default()
60    }
61}
62
63/// Builder for type-safe TLS configuration
64///
65/// Provides a fluent API for constructing TLS configurations with sensible defaults.
66///
67/// # Examples
68///
69/// Basic TLS with verification:
70/// ```
71/// use nntp_proxy::tls::TlsConfig;
72///
73/// let config = TlsConfig::builder()
74///     .enabled(true)
75///     .verify_cert(true)
76///     .build();
77/// ```
78///
79/// TLS with custom CA certificate:
80/// ```
81/// use nntp_proxy::tls::TlsConfig;
82///
83/// let config = TlsConfig::builder()
84///     .enabled(true)
85///     .verify_cert(true)
86///     .cert_path("/path/to/ca.pem")
87///     .build();
88/// ```
89///
90/// Insecure TLS (for testing only):
91/// ```
92/// use nntp_proxy::tls::TlsConfig;
93///
94/// let config = TlsConfig::builder()
95///     .enabled(true)
96///     .verify_cert(false)
97///     .build();
98/// ```
99#[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, // Secure by default
111            tls_cert_path: None,
112        }
113    }
114}
115
116impl TlsConfigBuilder {
117    /// Enable or disable TLS
118    ///
119    /// Default: `false`
120    #[must_use]
121    pub const fn enabled(mut self, use_tls: bool) -> Self {
122        self.use_tls = use_tls;
123        self
124    }
125
126    /// Enable or disable certificate verification
127    ///
128    /// **WARNING**: Disabling certificate verification is insecure and should only
129    /// be used for testing or with trusted private networks.
130    ///
131    /// Default: `true`
132    #[must_use]
133    pub const fn verify_cert(mut self, verify: bool) -> Self {
134        self.tls_verify_cert = verify;
135        self
136    }
137
138    /// Set path to custom CA certificate file
139    ///
140    /// The certificate should be in PEM format.
141    #[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    /// Build the `TlsConfig`
148    #[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
158// Certificate handling and custom verifiers
159
160mod rustls_backend {
161    use super::{
162        CertificateDer, DigitallySignedStruct, HandshakeSignatureValid, RootCertStore, RustlsError,
163        ServerCertVerified, ServerCertVerifier, ServerName, SignatureScheme, UnixTime,
164    };
165
166    /// Certificate loading results
167    #[derive(Debug)]
168    pub struct CertificateLoadResult {
169        pub root_store: RootCertStore,
170        pub sources: Vec<String>,
171    }
172
173    /// Custom certificate verifier that accepts all certificates (INSECURE!)
174    ///
175    /// This is used when `tls_verify_cert = false` for NNTP servers without valid certificates.
176    /// **WARNING**: This disables all certificate validation and should only be used for testing
177    /// or with trusted private networks.
178    #[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            // Accept all certificates without verification
191            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            // Accept all signatures without verification
201            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            // Accept all signatures without verification
211            Ok(HandshakeSignatureValid::assertion())
212        }
213
214        fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
215            // Support all signature schemes
216            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
235/// High-performance TLS connector with cached configuration
236///
237/// Caches the parsed TLS configuration including certificates to avoid
238/// expensive re-parsing on every connection. Certificates are loaded once
239/// during initialization and reused for all connections.
240pub struct TlsManager {
241    config: TlsConfig,
242    /// Cached TLS connector with pre-loaded certificates
243    ///
244    /// Avoids expensive certificate parsing overhead (DER parsing, X.509 validation,
245    /// signature verification) on every connection by loading certificates once at init.
246    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    /// Create a new TLS manager with the given configuration
269    ///
270    /// **Performance**: Loads and parses certificates once during initialization
271    /// instead of on every connection, eliminating certificate parsing overhead
272    /// (DER parsing, X.509 validation, signature verification).
273    ///
274    /// # Errors
275    /// Returns any certificate-loading or rustls configuration error.
276    pub fn new(config: TlsConfig) -> Result<Self, anyhow::Error> {
277        // Load certificates once during initialization
278        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    /// Perform TLS handshake
295    ///
296    /// # Errors
297    /// Returns any SNI conversion, connector, or handshake error for the
298    /// backend connection.
299    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    /// Load certificates from various sources with fallback chain (synchronous for init)
326    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        // 1. Load custom CA certificate if provided
333        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        // 2. Try to load system certificates
340        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        // 3. Fallback to Mozilla CA bundle if no certificates loaded
350        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    /// Load custom certificate from file
363    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    /// Load system certificates, returning count of successfully loaded certificates
386    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        // Log any errors but don't fail - we have fallback
397        for error in cert_result.errors {
398            warn!("TLS: Certificate loading error: {}", error);
399        }
400
401        added_count
402    }
403
404    /// Create optimized client configuration using ring crypto provider
405    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            // Use custom verifier that accepts all certificates
424            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        // Performance optimizations
433        client_config.enable_early_data = true; // Enable TLS 1.3 0-RTT for faster reconnections
434        client_config.resumption = rustls::client::Resumption::default(); // Enable session resumption
435
436        // Note: max_fragment_size is for outgoing records only (sending data to server)
437        // For incoming data (server->client), rustls uses internal buffering
438        // TLS 1.3 spec max is 16KB per record, larger values are not standard compliant
439        // and can cause connection failures with some servers
440        // client_config.max_fragment_size = Some(16384); // Keep default 16KB
441
442        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); // Secure by default
463        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        // Manager should successfully initialize with cached config
514        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        // Should have at least one source (system certificates or Mozilla CA bundle)
524        assert!(!result.sources.is_empty());
525        // Common sources include "system certificates" or "Mozilla CA bundle"
526        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) // Override
540            .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        // Both should share the same Arc<TlsConnector>
566        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        // Test with &str
585        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        // Test with String
589        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        // Should support all major signature schemes
603        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); // Should have many schemes
607    }
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        // Should have at least one source
615        assert!(!result.sources.is_empty());
616
617        // Sources should be descriptive strings
618        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        // Builder should default to secure settings
626        let config = TlsConfig::builder().enabled(true).build();
627
628        assert!(config.use_tls);
629        assert!(config.tls_verify_cert); // Verification enabled by default - SECURE
630        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        // Should successfully create manager even with verification disabled
642        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        // Should successfully create manager with verification enabled
651        assert!(manager.is_ok());
652    }
653
654    #[test]
655    fn test_certificate_loading_fallback_to_mozilla_bundle() {
656        // Even with empty config, should fall back to Mozilla CA bundle
657        let config = TlsConfig::default();
658        let result = TlsManager::load_certificates_sync(&config).unwrap();
659
660        // Should have loaded certificates from some source
661        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        // Empty string is technically a valid path (current directory)
714        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        // Should preserve exact string including spaces
726        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        // Both should successfully initialize
743        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        // Test all boolean combinations
753        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                // All configurations should create valid managers if TLS is enabled
764                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        // Test that builder methods can be called in any order
775        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}