Skip to main content

nexus_net/tls/
config.rs

1use std::sync::Arc;
2
3use rustls::ClientConfig;
4use rustls::pki_types::CertificateDer;
5
6use super::TlsError;
7
8/// Shared TLS configuration. Create once at startup, pass to each connection.
9///
10/// Wraps `Arc<ClientConfig>` — cloning is cheap.
11///
12/// # Examples
13///
14/// ```ignore
15/// // Safe defaults: system root certs, TLS 1.2+1.3, AES-GCM preferred.
16/// let config = TlsConfig::new()?;
17///
18/// // TLS 1.3 only, no certificate verification (testing).
19/// let config = TlsConfig::builder()
20///     .tls13_only()
21///     .danger_no_verify()
22///     .build()?;
23/// ```
24#[derive(Clone)]
25pub struct TlsConfig {
26    pub(crate) inner: Arc<ClientConfig>,
27}
28
29impl TlsConfig {
30    /// Create with safe defaults.
31    ///
32    /// - System root certificates via `rustls-native-certs`
33    /// - TLS 1.2 + 1.3 (both supported)
34    /// - aws-lc-rs crypto backend (AES-GCM with AES-NI)
35    pub fn new() -> Result<Self, TlsError> {
36        Self::builder().build()
37    }
38
39    /// Access the underlying rustls `ClientConfig`.
40    ///
41    /// Useful for creating a `tokio_rustls::TlsConnector` or other
42    /// rustls-based adapters.
43    pub fn client_config(&self) -> &Arc<ClientConfig> {
44        &self.inner
45    }
46
47    /// Create a builder for custom configuration.
48    #[must_use]
49    pub fn builder() -> TlsConfigBuilder {
50        TlsConfigBuilder {
51            custom_roots: Vec::new(),
52            skip_system_certs: false,
53            no_verify: false,
54            tls13_only: false,
55        }
56    }
57}
58
59/// Builder for [`TlsConfig`].
60pub struct TlsConfigBuilder {
61    custom_roots: Vec<CertificateDer<'static>>,
62    skip_system_certs: bool,
63    no_verify: bool,
64    tls13_only: bool,
65}
66
67impl TlsConfigBuilder {
68    /// Add a custom root certificate (DER-encoded).
69    ///
70    /// Useful for internal CAs or self-signed certificates.
71    #[must_use]
72    pub fn add_root_cert(mut self, der: impl Into<CertificateDer<'static>>) -> Self {
73        self.custom_roots.push(der.into());
74        self
75    }
76
77    /// Skip loading system root certificates.
78    ///
79    /// Use when providing all root certificates manually.
80    #[must_use]
81    pub fn skip_system_certs(mut self) -> Self {
82        self.skip_system_certs = true;
83        self
84    }
85
86    /// Disable certificate verification entirely.
87    ///
88    /// # Safety
89    ///
90    /// This disables all server identity checks. Use only for testing
91    /// against local servers with self-signed certificates.
92    #[must_use]
93    pub fn danger_no_verify(mut self) -> Self {
94        self.no_verify = true;
95        self
96    }
97
98    /// Restrict to TLS 1.3 only.
99    ///
100    /// TLS 1.3 has a simpler handshake (1-RTT vs 2-RTT) and mandatory
101    /// forward secrecy. Disable TLS 1.2 if all endpoints support 1.3.
102    #[must_use]
103    pub fn tls13_only(mut self) -> Self {
104        self.tls13_only = true;
105        self
106    }
107
108    /// Build the configuration.
109    pub fn build(self) -> Result<TlsConfig, TlsError> {
110        let versions: &[&rustls::SupportedProtocolVersion] = if self.tls13_only {
111            &[&rustls::version::TLS13]
112        } else {
113            rustls::ALL_VERSIONS
114        };
115
116        let config = if self.no_verify {
117            ClientConfig::builder_with_protocol_versions(versions)
118                .dangerous()
119                .with_custom_certificate_verifier(Arc::new(NoVerifier))
120                .with_no_client_auth()
121        } else {
122            let mut root_store = rustls::RootCertStore::empty();
123
124            if !self.skip_system_certs {
125                let result = rustls_native_certs::load_native_certs();
126                if result.certs.is_empty() {
127                    return Err(TlsError::NoRootCerts);
128                }
129                root_store.add_parsable_certificates(result.certs);
130            }
131
132            for cert in self.custom_roots {
133                root_store.add(cert).map_err(TlsError::Rustls)?;
134            }
135
136            if root_store.is_empty() {
137                return Err(TlsError::NoRootCerts);
138            }
139
140            ClientConfig::builder_with_protocol_versions(versions)
141                .with_root_certificates(root_store)
142                .with_no_client_auth()
143        };
144
145        Ok(TlsConfig {
146            inner: Arc::new(config),
147        })
148    }
149}
150
151/// Certificate verifier that accepts everything. Testing only.
152#[derive(Debug)]
153struct NoVerifier;
154
155impl rustls::client::danger::ServerCertVerifier for NoVerifier {
156    fn verify_server_cert(
157        &self,
158        _end_entity: &CertificateDer<'_>,
159        _intermediates: &[CertificateDer<'_>],
160        _server_name: &rustls::pki_types::ServerName<'_>,
161        _ocsp_response: &[u8],
162        _now: rustls::pki_types::UnixTime,
163    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
164        Ok(rustls::client::danger::ServerCertVerified::assertion())
165    }
166
167    fn verify_tls12_signature(
168        &self,
169        _message: &[u8],
170        _cert: &CertificateDer<'_>,
171        _dss: &rustls::DigitallySignedStruct,
172    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
173        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
174    }
175
176    fn verify_tls13_signature(
177        &self,
178        _message: &[u8],
179        _cert: &CertificateDer<'_>,
180        _dss: &rustls::DigitallySignedStruct,
181    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
182        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
183    }
184
185    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
186        rustls::crypto::aws_lc_rs::default_provider()
187            .signature_verification_algorithms
188            .supported_schemes()
189    }
190}