iroh_relay/
tls.rs

1//! TLS verification configuration for iroh and iroh-relay.
2
3use std::{io, sync::Arc};
4
5use rustls::{
6    client::{ClientConfig, WebPkiServerVerifier, danger::ServerCertVerifier},
7    crypto::CryptoProvider,
8};
9use webpki_types::CertificateDer;
10
11/// Configures the trusted CA root certificates for non-iroh TLS connections.
12///
13/// These roots are used whenever iroh establishes standard TLS connections to
14/// external services, such as iroh relays, pkarr servers, or DNS-over-HTTPS
15/// resolvers.
16///
17/// The configured Certificate Authority (CA) roots are only used for verifying
18/// the validity of TLS certificates presented by those external services. These
19/// CAs don't need to be trusted for the integrity or authenticity of native
20/// iroh connections, which rely on iroh's own cryptographic authentication mechanisms.
21#[derive(Debug, Clone)]
22pub struct CaTlsConfig {
23    mode: Mode,
24    extra_roots: Vec<CertificateDer<'static>>,
25}
26
27/// Renamed to [`CaTlsConfig`].
28#[deprecated(since = "1.0.0", note = "Renamed to `CaTlsConfig`")]
29pub type CaRootsConfig = CaTlsConfig;
30
31#[derive(derive_more::Debug, Clone)]
32enum Mode {
33    /// Use a compiled-in copy of the root certificates trusted by Mozilla.
34    ///
35    /// See [`webpki_roots`].
36    EmbeddedWebPki,
37    /// Use the operating system's certificate facilities for verifying the validity of TLS certificates.
38    ///
39    /// See [`rustls_platform_verifier`] for details how roots are retrieved on different platforms.
40    #[cfg(feature = "platform-verifier")]
41    System,
42    /// Only trust explicitly set root certificates.
43    ExtraRootsOnly,
44    /// Use a callback to create a [`ServerCertVerifier`].
45    CustomServerCertVerifier {
46        #[debug("Arc<dyn Fn>")]
47        builder: ServerCertVerifierBuilder,
48    },
49    /// INSECURE: Do not verify server certificates at all.
50    ///
51    /// May only be used in tests or local development setups.
52    #[cfg(any(test, feature = "test-utils"))]
53    InsecureSkipVerify,
54}
55
56impl Default for CaTlsConfig {
57    fn default() -> Self {
58        Self {
59            mode: Mode::EmbeddedWebPki,
60            extra_roots: vec![],
61        }
62    }
63}
64
65impl CaTlsConfig {
66    /// Uses the operating system's certificate facilities for verifying the validity of TLS certificates.
67    ///
68    /// See [`rustls_platform_verifier`] for details how trust anchors are retrieved on different platforms.
69    ///
70    /// Note: Additional certificates added via [`Self::with_extra_roots`] will be ignored on Android due to
71    /// missing support in [`rustls`].
72    #[cfg(feature = "platform-verifier")]
73    pub fn system() -> Self {
74        Self {
75            mode: Mode::System,
76            extra_roots: Vec::new(),
77        }
78    }
79
80    /// Uses a compiled-in copy of the root certificates trusted by Mozilla.
81    ///
82    /// See [`webpki_roots`] for details.
83    pub fn embedded() -> Self {
84        Self {
85            mode: Mode::EmbeddedWebPki,
86            extra_roots: Vec::new(),
87        }
88    }
89
90    /// INSECURE: Do not verify server certificates at all.
91    ///
92    /// May only be used in tests or local development setups.
93    #[cfg(any(test, feature = "test-utils"))]
94    pub fn insecure_skip_verify() -> Self {
95        Self {
96            mode: Mode::InsecureSkipVerify,
97            extra_roots: Vec::new(),
98        }
99    }
100
101    /// Only trusts the explicitly set root certificates.
102    pub fn custom_roots(roots: impl IntoIterator<Item = CertificateDer<'static>>) -> Self {
103        Self {
104            mode: Mode::ExtraRootsOnly,
105            extra_roots: roots.into_iter().collect(),
106        }
107    }
108
109    /// Renamed to [`Self::custom_roots`].
110    #[deprecated(since = "1.0.0", note = "Renamed to `custom_roots`")]
111    pub fn custom(roots: impl IntoIterator<Item = CertificateDer<'static>>) -> Self {
112        Self::custom_roots(roots)
113    }
114
115    /// Creates a [`CaTlsConfig`] that uses a callback function to create a [`ServerCertVerifier`].
116    ///
117    /// This is an advanced feature and you should only use this if none of the other constructor
118    /// functions cover your needs. Wrongly implementing the callback may lead to insecure connections
119    /// being accepted.
120    ///
121    /// The [`CryptoProvider`] passed to the callback should be used for all cryptographic operations.
122    ///
123    /// ## Example
124    ///
125    /// This example implements the behavior of [`Self::embedded`] via [`Self::custom_server_cert_verifier`].
126    ///
127    /// ```rust
128    /// # use std::{io, sync::Arc};
129    /// # use iroh_relay::tls::CaTlsConfig;
130    /// # use rustls::client::WebPkiServerVerifier;
131    /// let tls_config = CaTlsConfig::custom_server_cert_verifier(Arc::new(move |crypto_provider| {
132    ///     let root_store = Arc::new(rustls::RootCertStore {
133    ///         roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
134    ///     });
135    ///     let verifier = WebPkiServerVerifier::builder_with_provider(root_store, crypto_provider)
136    ///         .build()
137    ///         .map_err(io::Error::other)?;
138    ///     Ok(verifier)
139    /// }));
140    /// ```
141    pub fn custom_server_cert_verifier(builder: ServerCertVerifierBuilder) -> Self {
142        Self {
143            mode: Mode::CustomServerCertVerifier { builder },
144            extra_roots: Vec::new(),
145        }
146    }
147
148    /// Adds additional root certificates to the list of trusted certificates.
149    ///
150    /// Ignored when using [`Self::custom_server_cert_verifier`].
151    pub fn with_extra_roots(
152        mut self,
153        extra_roots: impl IntoIterator<Item = CertificateDer<'static>>,
154    ) -> Self {
155        self.extra_roots.extend(extra_roots);
156        self
157    }
158
159    /// Builds a [`ServerCertVerifier`] from this config.
160    pub fn server_cert_verifier(
161        &self,
162        crypto_provider: Arc<CryptoProvider>,
163    ) -> io::Result<Arc<dyn ServerCertVerifier>> {
164        Ok(match self.mode {
165            #[cfg(feature = "platform-verifier")]
166            Mode::System => {
167                #[cfg(not(target_os = "android"))]
168                let verifier = rustls_platform_verifier::Verifier::new_with_extra_roots(
169                    self.extra_roots.clone(),
170                    crypto_provider,
171                );
172                #[cfg(target_os = "android")]
173                let verifier = rustls_platform_verifier::Verifier::new(crypto_provider);
174                Arc::new(verifier.map_err(io::Error::other)?)
175            }
176            Mode::EmbeddedWebPki => {
177                let mut root_store = rustls::RootCertStore {
178                    roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
179                };
180                root_store.add_parsable_certificates(self.extra_roots.clone());
181                WebPkiServerVerifier::builder_with_provider(Arc::new(root_store), crypto_provider)
182                    .build()
183                    .map_err(io::Error::other)?
184            }
185            Mode::ExtraRootsOnly => {
186                let mut root_store = rustls::RootCertStore { roots: vec![] };
187                root_store.add_parsable_certificates(self.extra_roots.clone());
188                WebPkiServerVerifier::builder_with_provider(Arc::new(root_store), crypto_provider)
189                    .build()
190                    .map_err(io::Error::other)?
191            }
192            Mode::CustomServerCertVerifier { ref builder } => builder(crypto_provider)?,
193            #[cfg(any(test, feature = "test-utils"))]
194            Mode::InsecureSkipVerify => {
195                tracing::warn!(
196                    "Insecure TLS config: server certificates will be trusted without verification"
197                );
198                Arc::new(no_cert_verifier::NoCertVerifier { crypto_provider })
199            }
200        })
201    }
202
203    /// Builds a [`ClientConfig`] from this config.
204    pub fn client_config(&self, crypto_provider: Arc<CryptoProvider>) -> io::Result<ClientConfig> {
205        let verifier = self.server_cert_verifier(crypto_provider.clone())?;
206        let config = ClientConfig::builder_with_provider(crypto_provider)
207            .with_safe_default_protocol_versions()
208            .expect(
209                "configured crypto provider is missing support for required TLS protocol versions",
210            )
211            .dangerous()
212            .with_custom_certificate_verifier(verifier)
213            .with_no_client_auth();
214        Ok(config)
215    }
216}
217
218/// Function to build a [`ServerCertVerifier`] from a [`CryptoProvider`].
219///
220/// See [`CaTlsConfig::custom_server_cert_verifier`].
221pub type ServerCertVerifierBuilder = Arc<
222    dyn Fn(Arc<CryptoProvider>) -> io::Result<Arc<dyn ServerCertVerifier>> + Send + Sync + 'static,
223>;
224
225/// Returns the default crypto provider, if enabled via a feature flag.
226///
227/// Prefers `ring` over `aws-lc-rs` if both are enabled.
228#[cfg(feature = "tls-ring")]
229pub fn default_provider() -> Arc<CryptoProvider> {
230    Arc::new(rustls::crypto::ring::default_provider())
231}
232
233/// Returns the default crypto provider, if enabled via a feature flag.
234///
235/// Prefers `ring` over `aws-lc-rs` if both are enabled.
236#[cfg(all(feature = "tls-aws-lc-rs", not(feature = "tls-ring")))]
237pub fn default_provider() -> Arc<CryptoProvider> {
238    Arc::new(rustls::crypto::aws_lc_rs::default_provider())
239}
240
241/// Creates a client config that trusts any servers without verifying their TLS certificate.
242///
243/// Should be used for testing local relay setups only.
244#[cfg(all(any(test, feature = "test-utils"), with_crypto_provider))]
245pub fn make_dangerous_client_config() -> ClientConfig {
246    CaTlsConfig::insecure_skip_verify()
247        .client_config(default_provider())
248        .expect("infallible")
249}
250
251#[cfg(any(test, feature = "test-utils"))]
252mod no_cert_verifier {
253    use std::sync::Arc;
254
255    use rustls::{
256        client::danger::{ServerCertVerified, ServerCertVerifier},
257        crypto::CryptoProvider,
258        pki_types::{CertificateDer, ServerName},
259    };
260
261    /// Used to allow self signed certificates in tests
262    #[derive(Debug)]
263    pub(super) struct NoCertVerifier {
264        pub(super) crypto_provider: Arc<CryptoProvider>,
265    }
266
267    impl ServerCertVerifier for NoCertVerifier {
268        fn verify_server_cert(
269            &self,
270            _end_entity: &CertificateDer,
271            _intermediates: &[CertificateDer],
272            _server_name: &ServerName,
273            _ocsp_response: &[u8],
274            _now: rustls::pki_types::UnixTime,
275        ) -> Result<ServerCertVerified, rustls::Error> {
276            Ok(ServerCertVerified::assertion())
277        }
278
279        fn verify_tls12_signature(
280            &self,
281            _message: &[u8],
282            _cert: &CertificateDer<'_>,
283            _dss: &rustls::DigitallySignedStruct,
284        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
285            Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
286        }
287
288        fn verify_tls13_signature(
289            &self,
290            _message: &[u8],
291            _cert: &CertificateDer<'_>,
292            _dss: &rustls::DigitallySignedStruct,
293        ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
294            Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
295        }
296
297        fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
298            self.crypto_provider
299                .signature_verification_algorithms
300                .supported_schemes()
301        }
302    }
303}