1//! TLS configuration
2//!
3//! By default, a `Client` will make use of system-native transport layer
4//! security to connect to HTTPS destinations. This means schannel on Windows,
5//! Security-Framework on macOS, and OpenSSL on Linux.
6//!
7//! - Additional X509 certificates can be configured on a `ClientBuilder` with the
8//! [`Certificate`](Certificate) type.
9//! - Client certificates can be add to a `ClientBuilder` with the
10//! [`Identity`][Identity] type.
11//! - Various parts of TLS can also be configured or even disabled on the
12//! `ClientBuilder`.
13
14#[cfg(feature = "__rustls")]
15use rustls::{
16 client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier,
17 DigitallySignedStruct, Error as TLSError, ServerName,
18};
19use std::fmt;
20
21/// Represents a server X509 certificate.
22#[derive(Clone)]
23pub struct Certificate {
24 #[cfg(feature = "native-tls-crate")]
25 native: native_tls_crate::Certificate,
26 #[cfg(feature = "__rustls")]
27 original: Cert,
28}
29
30#[cfg(feature = "__rustls")]
31#[derive(Clone)]
32enum Cert {
33 Der(Vec<u8>),
34 Pem(Vec<u8>),
35}
36
37/// Represents a private key and X509 cert as a client certificate.
38#[derive(Clone)]
39pub struct Identity {
40 #[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))]
41 inner: ClientCert,
42}
43
44#[derive(Clone)]
45enum ClientCert {
46 #[cfg(feature = "native-tls")]
47 Pkcs12(native_tls_crate::Identity),
48 #[cfg(feature = "native-tls")]
49 Pkcs8(native_tls_crate::Identity),
50 #[cfg(feature = "__rustls")]
51 Pem {
52 key: rustls::PrivateKey,
53 certs: Vec<rustls::Certificate>,
54 },
55}
56
57impl Certificate {
58 /// Create a `Certificate` from a binary DER encoded certificate
59 ///
60 /// # Examples
61 ///
62 /// ```
63 /// # use std::fs::File;
64 /// # use std::io::Read;
65 /// # fn cert() -> Result<(), Box<std::error::Error>> {
66 /// let mut buf = Vec::new();
67 /// File::open("my_cert.der")?
68 /// .read_to_end(&mut buf)?;
69 /// let cert = reqwest::Certificate::from_der(&buf)?;
70 /// # drop(cert);
71 /// # Ok(())
72 /// # }
73 /// ```
74 pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
75 Ok(Certificate {
76 #[cfg(feature = "native-tls-crate")]
77 native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
78 #[cfg(feature = "__rustls")]
79 original: Cert::Der(der.to_owned()),
80 })
81 }
82
83 /// Create a `Certificate` from a PEM encoded certificate
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// # use std::fs::File;
89 /// # use std::io::Read;
90 /// # fn cert() -> Result<(), Box<std::error::Error>> {
91 /// let mut buf = Vec::new();
92 /// File::open("my_cert.pem")?
93 /// .read_to_end(&mut buf)?;
94 /// let cert = reqwest::Certificate::from_pem(&buf)?;
95 /// # drop(cert);
96 /// # Ok(())
97 /// # }
98 /// ```
99 pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
100 Ok(Certificate {
101 #[cfg(feature = "native-tls-crate")]
102 native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
103 #[cfg(feature = "__rustls")]
104 original: Cert::Pem(pem.to_owned()),
105 })
106 }
107
108 #[cfg(feature = "native-tls-crate")]
109 pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
110 tls.add_root_certificate(self.native);
111 }
112
113 #[cfg(feature = "__rustls")]
114 pub(crate) fn add_to_rustls(
115 self,
116 root_cert_store: &mut rustls::RootCertStore,
117 ) -> crate::Result<()> {
118 use std::io::Cursor;
119
120 match self.original {
121 Cert::Der(buf) => root_cert_store
122 .add(&rustls::Certificate(buf))
123 .map_err(crate::error::builder)?,
124 Cert::Pem(buf) => {
125 let mut pem = Cursor::new(buf);
126 let certs = rustls_pemfile::certs(&mut pem).map_err(|_| {
127 crate::error::builder(TLSError::General(String::from(
128 "No valid certificate was found",
129 )))
130 })?;
131 for c in certs {
132 root_cert_store
133 .add(&rustls::Certificate(c))
134 .map_err(crate::error::builder)?;
135 }
136 }
137 }
138 Ok(())
139 }
140}
141
142impl Identity {
143 /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
144 ///
145 /// The archive should contain a leaf certificate and its private key, as well any intermediate
146 /// certificates that allow clients to build a chain to a trusted root.
147 /// The chain certificates should be in order from the leaf certificate towards the root.
148 ///
149 /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
150 /// with the OpenSSL `pkcs12` tool:
151 ///
152 /// ```bash
153 /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
154 /// ```
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// # use std::fs::File;
160 /// # use std::io::Read;
161 /// # fn pkcs12() -> Result<(), Box<std::error::Error>> {
162 /// let mut buf = Vec::new();
163 /// File::open("my-ident.pfx")?
164 /// .read_to_end(&mut buf)?;
165 /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
166 /// # drop(pkcs12);
167 /// # Ok(())
168 /// # }
169 /// ```
170 ///
171 /// # Optional
172 ///
173 /// This requires the `native-tls` Cargo feature enabled.
174 #[cfg(feature = "native-tls")]
175 pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
176 Ok(Identity {
177 inner: ClientCert::Pkcs12(
178 native_tls_crate::Identity::from_pkcs12(der, password)
179 .map_err(crate::error::builder)?,
180 ),
181 })
182 }
183
184 /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
185 /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
186 ///
187 /// The certificate chain should contain any intermediate cerficates that should be sent to
188 /// clients to allow them to build a chain to a trusted root.
189 ///
190 /// A certificate chain here means a series of PEM encoded certificates concatenated together.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// # use std::fs;
196 /// # fn pkcs8() -> Result<(), Box<std::error::Error>> {
197 /// let cert = fs::read("client.pem")?;
198 /// let key = fs::read("key.pem")?;
199 /// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;
200 /// # drop(pkcs8);
201 /// # Ok(())
202 /// # }
203 /// ```
204 ///
205 /// # Optional
206 ///
207 /// This requires the `native-tls` Cargo feature enabled.
208 #[cfg(feature = "native-tls")]
209 pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
210 Ok(Identity {
211 inner: ClientCert::Pkcs8(
212 native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
213 ),
214 })
215 }
216
217 /// Parses PEM encoded private key and certificate.
218 ///
219 /// The input should contain a PEM encoded private key
220 /// and at least one PEM encoded certificate.
221 ///
222 /// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// # use std::fs::File;
228 /// # use std::io::Read;
229 /// # fn pem() -> Result<(), Box<std::error::Error>> {
230 /// let mut buf = Vec::new();
231 /// File::open("my-ident.pem")?
232 /// .read_to_end(&mut buf)?;
233 /// let id = reqwest::Identity::from_pem(&buf)?;
234 /// # drop(id);
235 /// # Ok(())
236 /// # }
237 /// ```
238 ///
239 /// # Optional
240 ///
241 /// This requires the `rustls-tls(-...)` Cargo feature enabled.
242 #[cfg(feature = "__rustls")]
243 pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
244 use std::io::Cursor;
245
246 let (key, certs) = {
247 let mut pem = Cursor::new(buf);
248 let mut sk = Vec::<rustls::PrivateKey>::new();
249 let mut certs = Vec::<rustls::Certificate>::new();
250
251 for item in std::iter::from_fn(|| rustls_pemfile::read_one(&mut pem).transpose()) {
252 match item.map_err(|_| {
253 crate::error::builder(TLSError::General(String::from(
254 "Invalid identity PEM file",
255 )))
256 })? {
257 rustls_pemfile::Item::X509Certificate(cert) => {
258 certs.push(rustls::Certificate(cert))
259 }
260 rustls_pemfile::Item::PKCS8Key(key) => sk.push(rustls::PrivateKey(key)),
261 rustls_pemfile::Item::RSAKey(key) => sk.push(rustls::PrivateKey(key)),
262 rustls_pemfile::Item::ECKey(key) => sk.push(rustls::PrivateKey(key)),
263 _ => {
264 return Err(crate::error::builder(TLSError::General(String::from(
265 "No valid certificate was found",
266 ))))
267 }
268 }
269 }
270
271 if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
272 (sk, certs)
273 } else {
274 return Err(crate::error::builder(TLSError::General(String::from(
275 "private key or certificate not found",
276 ))));
277 }
278 };
279
280 Ok(Identity {
281 inner: ClientCert::Pem { key, certs },
282 })
283 }
284
285 #[cfg(feature = "native-tls")]
286 pub(crate) fn add_to_native_tls(
287 self,
288 tls: &mut native_tls_crate::TlsConnectorBuilder,
289 ) -> crate::Result<()> {
290 match self.inner {
291 ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
292 tls.identity(id);
293 Ok(())
294 }
295 #[cfg(feature = "__rustls")]
296 ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
297 }
298 }
299
300 #[cfg(feature = "__rustls")]
301 pub(crate) fn add_to_rustls(
302 self,
303 config_builder: rustls::ConfigBuilder<
304 rustls::ClientConfig,
305 rustls::client::WantsTransparencyPolicyOrClientCert,
306 >,
307 ) -> crate::Result<rustls::ClientConfig> {
308 match self.inner {
309 ClientCert::Pem { key, certs } => config_builder
310 .with_single_cert(certs, key)
311 .map_err(crate::error::builder),
312 #[cfg(feature = "native-tls")]
313 ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => {
314 Err(crate::error::builder("incompatible TLS identity type"))
315 }
316 }
317 }
318}
319
320impl fmt::Debug for Certificate {
321 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322 f.debug_struct(name:"Certificate").finish()
323 }
324}
325
326impl fmt::Debug for Identity {
327 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
328 f.debug_struct(name:"Identity").finish()
329 }
330}
331
332/// A TLS protocol version.
333#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
334pub struct Version(InnerVersion);
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
337#[non_exhaustive]
338enum InnerVersion {
339 Tls1_0,
340 Tls1_1,
341 Tls1_2,
342 Tls1_3,
343}
344
345// These could perhaps be From/TryFrom implementations, but those would be
346// part of the public API so let's be careful
347impl Version {
348 /// Version 1.0 of the TLS protocol.
349 pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
350 /// Version 1.1 of the TLS protocol.
351 pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
352 /// Version 1.2 of the TLS protocol.
353 pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
354 /// Version 1.3 of the TLS protocol.
355 pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
356
357 #[cfg(feature = "default-tls")]
358 pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
359 match self.0 {
360 InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
361 InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
362 InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
363 InnerVersion::Tls1_3 => None,
364 }
365 }
366
367 #[cfg(feature = "__rustls")]
368 pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
369 match version {
370 rustls::ProtocolVersion::SSLv2 => None,
371 rustls::ProtocolVersion::SSLv3 => None,
372 rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
373 rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
374 rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
375 rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
376 _ => None,
377 }
378 }
379}
380
381pub(crate) enum TlsBackend {
382 // This is the default and HTTP/3 feature does not use it so suppress it.
383 #[allow(dead_code)]
384 #[cfg(feature = "default-tls")]
385 Default,
386 #[cfg(feature = "native-tls")]
387 BuiltNativeTls(native_tls_crate::TlsConnector),
388 #[cfg(feature = "__rustls")]
389 Rustls,
390 #[cfg(feature = "__rustls")]
391 BuiltRustls(rustls::ClientConfig),
392 #[cfg(any(feature = "native-tls", feature = "__rustls",))]
393 UnknownPreconfigured,
394}
395
396impl fmt::Debug for TlsBackend {
397 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
398 match self {
399 #[cfg(feature = "default-tls")]
400 TlsBackend::Default => write!(f, "Default"),
401 #[cfg(feature = "native-tls")]
402 TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
403 #[cfg(feature = "__rustls")]
404 TlsBackend::Rustls => write!(f, "Rustls"),
405 #[cfg(feature = "__rustls")]
406 TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"),
407 #[cfg(any(feature = "native-tls", feature = "__rustls",))]
408 TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
409 }
410 }
411}
412
413impl Default for TlsBackend {
414 fn default() -> TlsBackend {
415 #[cfg(all(feature = "default-tls", not(feature = "http3")))]
416 {
417 TlsBackend::Default
418 }
419
420 #[cfg(any(
421 all(feature = "__rustls", not(feature = "default-tls")),
422 feature = "http3"
423 ))]
424 {
425 TlsBackend::Rustls
426 }
427 }
428}
429
430#[cfg(feature = "__rustls")]
431pub(crate) struct NoVerifier;
432
433#[cfg(feature = "__rustls")]
434impl ServerCertVerifier for NoVerifier {
435 fn verify_server_cert(
436 &self,
437 _end_entity: &rustls::Certificate,
438 _intermediates: &[rustls::Certificate],
439 _server_name: &ServerName,
440 _scts: &mut dyn Iterator<Item = &[u8]>,
441 _ocsp_response: &[u8],
442 _now: std::time::SystemTime,
443 ) -> Result<ServerCertVerified, TLSError> {
444 Ok(ServerCertVerified::assertion())
445 }
446
447 fn verify_tls12_signature(
448 &self,
449 _message: &[u8],
450 _cert: &rustls::Certificate,
451 _dss: &DigitallySignedStruct,
452 ) -> Result<HandshakeSignatureValid, TLSError> {
453 Ok(HandshakeSignatureValid::assertion())
454 }
455
456 fn verify_tls13_signature(
457 &self,
458 _message: &[u8],
459 _cert: &rustls::Certificate,
460 _dss: &DigitallySignedStruct,
461 ) -> Result<HandshakeSignatureValid, TLSError> {
462 Ok(HandshakeSignatureValid::assertion())
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[cfg(feature = "default-tls")]
471 #[test]
472 fn certificate_from_der_invalid() {
473 Certificate::from_der(b"not der").unwrap_err();
474 }
475
476 #[cfg(feature = "default-tls")]
477 #[test]
478 fn certificate_from_pem_invalid() {
479 Certificate::from_pem(b"not pem").unwrap_err();
480 }
481
482 #[cfg(feature = "native-tls")]
483 #[test]
484 fn identity_from_pkcs12_der_invalid() {
485 Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
486 }
487
488 #[cfg(feature = "native-tls")]
489 #[test]
490 fn identity_from_pkcs8_pem_invalid() {
491 Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
492 }
493
494 #[cfg(feature = "__rustls")]
495 #[test]
496 fn identity_from_pem_invalid() {
497 Identity::from_pem(b"not pem").unwrap_err();
498 }
499
500 #[cfg(feature = "__rustls")]
501 #[test]
502 fn identity_from_pem_pkcs1_key() {
503 let pem = b"-----BEGIN CERTIFICATE-----\n\
504 -----END CERTIFICATE-----\n\
505 -----BEGIN RSA PRIVATE KEY-----\n\
506 -----END RSA PRIVATE KEY-----\n";
507
508 Identity::from_pem(pem).unwrap();
509 }
510}
511