[go: up one dir, main page]

ureq/tls/
mod.rs

1//! TLS for handling `https`.
2
3use std::collections::hash_map::DefaultHasher;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::sync::Arc;
7
8mod cert;
9pub use cert::{parse_pem, Certificate, PemItem, PrivateKey};
10
11#[cfg(feature = "_rustls")]
12pub(crate) mod rustls;
13
14#[cfg(feature = "native-tls")]
15pub(crate) mod native_tls;
16
17/// Setting for which TLS provider to use.
18///
19/// Defaults to [`Rustls`][Self::Rustls] because this has the highest chance
20/// to compile and "just work" straight out of the box without installing additional
21/// development dependencies.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23#[non_exhaustive]
24pub enum TlsProvider {
25    /// [Rustls](https://crates.io/crates/rustls) with the
26    /// [process-wide default cryptographic backend](https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#method.install_default),
27    /// or [Ring](https://crates.io/crates/ring) if no process-wide default is set.
28    ///
29    /// Requires the feature flag **rustls**.
30    ///
31    /// This is the default.
32    #[default]
33    Rustls,
34
35    /// [Native-TLS](https://crates.io/crates/native-tls) for cases where it's important to
36    /// use the TLS libraries installed on the host running ureq.
37    ///
38    /// Requires the feature flag **native-tls** and that using an [`Agent`](crate::Agent) with
39    /// this config option set in the [`TlsConfig`].
40    ///
41    /// The setting is never picked up automatically.
42    NativeTls,
43}
44
45impl TlsProvider {
46    pub(crate) fn is_feature_enabled(&self) -> bool {
47        match self {
48            TlsProvider::Rustls => {
49                cfg!(feature = "_rustls")
50            }
51            TlsProvider::NativeTls => {
52                cfg!(feature = "native-tls")
53            }
54        }
55    }
56
57    pub(crate) fn feature_name(&self) -> &'static str {
58        match self {
59            TlsProvider::Rustls => "rustls",
60            TlsProvider::NativeTls => "native-tls",
61        }
62    }
63}
64
65/// Configuration of TLS.
66///
67/// This configuration is in common for both the different TLS mechanisms (available through
68/// feature flags **rustls** and **native-tls**).
69#[derive(Clone)]
70pub struct TlsConfig {
71    provider: TlsProvider,
72    client_cert: Option<ClientCert>,
73    root_certs: RootCerts,
74    use_sni: bool,
75    disable_verification: bool,
76    #[cfg(feature = "_rustls")]
77    rustls_crypto_provider: Option<Arc<::rustls::crypto::CryptoProvider>>,
78}
79
80impl TlsConfig {
81    /// Builder to make a bespoke config.
82    pub fn builder() -> TlsConfigBuilder {
83        TlsConfigBuilder {
84            config: TlsConfig::default(),
85        }
86    }
87
88    pub(crate) fn hash_value(&self) -> u64 {
89        let mut hasher = DefaultHasher::new();
90        self.hash(&mut hasher);
91        hasher.finish()
92    }
93}
94
95impl TlsConfig {
96    /// The provider to use.
97    ///
98    /// Defaults to [`TlsProvider::Rustls`].
99    pub fn provider(&self) -> TlsProvider {
100        self.provider
101    }
102
103    /// Client certificate chain with corresponding private key.
104    ///
105    /// Defaults to `None`.
106    pub fn client_cert(&self) -> Option<&ClientCert> {
107        self.client_cert.as_ref()
108    }
109
110    /// The set of trusted root certificates to use to validate server certificates.
111    ///
112    /// Defaults to `WebPki`.
113    pub fn root_certs(&self) -> &RootCerts {
114        &self.root_certs
115    }
116
117    /// Whether to send SNI (Server Name Indication) to the remote server.
118    ///
119    /// This is used by the server to determine which domain/certificate we are connecting
120    /// to for servers where multiple domains/sites are hosted on the same IP.
121    ///
122    /// Defaults to `true`.
123    pub fn use_sni(&self) -> bool {
124        self.use_sni
125    }
126
127    /// **WARNING** Disable all server certificate verification.
128    ///
129    /// This breaks encryption and leaks secrets. Must never be enabled for code where
130    /// any level of security is required.
131    pub fn disable_verification(&self) -> bool {
132        self.disable_verification
133    }
134
135    /// Specific `CryptoProvider` to use for `rustls`.
136    ///
137    /// # UNSTABLE API
138    ///
139    /// **NOTE: This API is not guaranteed for semver.**
140    ///
141    /// `rustls` is not (yet) semver 1.x and ureq can't promise that this API is upheld.
142    /// If `rustls` makes a breaking change regarding `CryptoProvider` their configuration,
143    /// or incompatible data types between rustls versions, ureq will _NOT_ bump a major version.
144    ///
145    /// ureq will update to the latest `rustls` minor version using ureq minor versions.
146    #[cfg(feature = "_rustls")]
147    pub fn unversioned_rustls_crypto_provider(
148        &self,
149    ) -> &Option<Arc<::rustls::crypto::CryptoProvider>> {
150        &self.rustls_crypto_provider
151    }
152}
153
154/// Builder of [`TlsConfig`]
155pub struct TlsConfigBuilder {
156    config: TlsConfig,
157}
158
159impl TlsConfigBuilder {
160    /// The provider to use.
161    ///
162    /// Defaults to [`TlsProvider::Rustls`].
163    pub fn provider(mut self, v: TlsProvider) -> Self {
164        self.config.provider = v;
165        self
166    }
167
168    /// Client certificate chain with corresponding private key.
169    ///
170    /// Defaults to `None`.
171    pub fn client_cert(mut self, v: Option<ClientCert>) -> Self {
172        self.config.client_cert = v;
173        self
174    }
175
176    /// The set of trusted root certificates to use to validate server certificates.
177    ///
178    /// Defaults to `WebPki`.
179    pub fn root_certs(mut self, v: RootCerts) -> Self {
180        self.config.root_certs = v;
181        self
182    }
183
184    /// Whether to send SNI (Server Name Indication) to the remote server.
185    ///
186    /// This is used by the server to determine which domain/certificate we are connecting
187    /// to for servers where multiple domains/sites are hosted on the same IP.
188    ///
189    /// Defaults to `true`.
190    pub fn use_sni(mut self, v: bool) -> Self {
191        self.config.use_sni = v;
192        self
193    }
194
195    /// **WARNING** Disable all server certificate verification.
196    ///
197    /// This breaks encryption and leaks secrets. Must never be enabled for code where
198    /// any level of security is required.
199    pub fn disable_verification(mut self, v: bool) -> Self {
200        self.config.disable_verification = v;
201        self
202    }
203
204    /// Specific `CryptoProvider` to use for `rustls`.
205    ///
206    /// # UNSTABLE API
207    ///
208    /// **NOTE: This API is not guaranteed for semver.**
209    ///
210    /// `rustls` is not (yet) semver 1.x and ureq can't promise that this API is upheld.
211    /// If `rustls` makes a breaking change regarding `CryptoProvider` their configuration,
212    /// or incompatible data types between rustls versions, ureq will _NOT_ bump a major version.
213    ///
214    /// ureq will update to the latest `rustls` minor version using ureq minor versions.
215    ///
216    /// # Feature flags
217    ///
218    /// This requires either feature **rustls** or **rustls-no-provider**, you probably
219    /// want the latter when configuring an explicit crypto provider since
220    /// **rustls** compiles with `ring`, while **rustls-no-provider** does not.
221    ///
222    /// # Example
223    ///
224    /// This example uses `aws-lc-rs` for the [`Agent`][crate::Agent]. The following
225    /// depdendencies would compile ureq without `ring` and only aws-lc-rs.
226    ///
227    /// * `Cargo.toml`
228    ///
229    /// ```text
230    /// ureq = { version = "3", default-features = false, features = ["rustls-no-provider"] }
231    /// rustls = { version = "0.23", features = ["aws-lc-rs"] }
232    /// ```
233    ///
234    /// * Agent
235    ///
236    /// ```
237    /// use std::sync::Arc;
238    /// use ureq::{Agent};
239    /// use ureq::tls::{TlsConfig, TlsProvider};
240    /// use rustls::crypto;
241    ///
242    /// let crypto = Arc::new(crypto::aws_lc_rs::default_provider());
243    ///
244    /// let agent = Agent::config_builder()
245    ///     .tls_config(
246    ///         TlsConfig::builder()
247    ///             .provider(TlsProvider::Rustls)
248    ///             // requires rustls or rustls-no-provider feature
249    ///             .unversioned_rustls_crypto_provider(crypto)
250    ///             .build()
251    ///    )
252    ///    .build()
253    ///    .new_agent();
254    /// ```
255    #[cfg(feature = "_rustls")]
256    pub fn unversioned_rustls_crypto_provider(
257        mut self,
258        v: Arc<::rustls::crypto::CryptoProvider>,
259    ) -> Self {
260        self.config.rustls_crypto_provider = Some(v);
261        self
262    }
263
264    /// Finalize the config
265    pub fn build(self) -> TlsConfig {
266        self.config
267    }
268}
269
270/// A client certificate.
271#[derive(Debug, Clone, Hash)]
272pub struct ClientCert(Arc<(Vec<Certificate<'static>>, PrivateKey<'static>)>);
273
274impl ClientCert {
275    /// Creates a new client certificate from a chain and a private key.
276    pub fn new_with_certs(chain: &[Certificate<'static>], key: PrivateKey<'static>) -> Self {
277        Self(Arc::new((chain.to_vec(), key)))
278    }
279
280    /// Client certificate chain.
281    pub fn certs(&self) -> &[Certificate<'static>] {
282        &self.0 .0
283    }
284
285    /// Client certificate private key.
286    pub fn private_key(&self) -> &PrivateKey<'static> {
287        &self.0 .1
288    }
289}
290
291/// Configuration setting for root certs.
292#[derive(Debug, Clone, Hash)]
293#[non_exhaustive]
294pub enum RootCerts {
295    /// Use these specific certificates as root certs.
296    Specific(Arc<Vec<Certificate<'static>>>),
297
298    /// Use the platform's verifier.
299    ///
300    /// * For **rustls**, this uses the `rustls-platform-verifier` crate. It requires
301    ///   the feature **platform-verifier**.
302    /// * For **native-tls**, this uses the roots that native-tls loads by default.
303    PlatformVerifier,
304
305    /// Use Mozilla's root certificates instead of the platform.
306    ///
307    /// This is useful when you can't trust the system roots, such as in
308    /// environments where TLS is intercepted and decrypted by a proxy (MITM attack).
309    ///
310    /// This is the default value.
311    WebPki,
312}
313
314impl RootCerts {
315    /// Use these specific root certificates
316    pub fn new_with_certs(certs: &[Certificate<'static>]) -> Self {
317        certs.iter().cloned().into()
318    }
319}
320
321impl<I: IntoIterator<Item = Certificate<'static>>> From<I> for RootCerts {
322    fn from(value: I) -> Self {
323        RootCerts::Specific(Arc::new(value.into_iter().collect()))
324    }
325}
326
327impl Default for TlsConfig {
328    fn default() -> Self {
329        let provider = TlsProvider::default();
330        Self {
331            provider,
332            client_cert: None,
333            root_certs: RootCerts::WebPki,
334            use_sni: true,
335            disable_verification: false,
336            #[cfg(feature = "_rustls")]
337            rustls_crypto_provider: None,
338        }
339    }
340}
341
342impl fmt::Debug for TlsConfig {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        f.debug_struct("TlsConfig")
345            .field("provider", &self.provider)
346            .field("client_cert", &self.client_cert)
347            .field("root_certs", &self.root_certs)
348            .field("use_sni", &self.use_sni)
349            .field("disable_verification", &self.disable_verification)
350            .finish()
351    }
352}
353
354impl Hash for TlsConfig {
355    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
356        self.provider.hash(state);
357        self.client_cert.hash(state);
358        self.root_certs.hash(state);
359        self.use_sni.hash(state);
360        self.disable_verification.hash(state);
361
362        #[cfg(feature = "_rustls")]
363        if let Some(arc) = &self.rustls_crypto_provider {
364            (Arc::as_ptr(arc) as usize).hash(state);
365        }
366    }
367}
368
369#[cfg(test)]
370mod test {
371    use super::*;
372    use assert_no_alloc::*;
373
374    #[test]
375    fn tls_config_clone_does_not_allocate() {
376        let c = TlsConfig::default();
377        assert_no_alloc(|| c.clone());
378    }
379}