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}