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" )] |
15 | use rustls::{ |
16 | client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier, |
17 | DigitallySignedStruct, Error as TLSError, ServerName, |
18 | }; |
19 | use std::fmt; |
20 | |
21 | /// Represents a server X509 certificate. |
22 | #[derive (Clone)] |
23 | pub 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)] |
32 | enum 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)] |
39 | pub struct Identity { |
40 | #[cfg_attr (not(any(feature = "native-tls" , feature = "__rustls" )), allow(unused))] |
41 | inner: ClientCert, |
42 | } |
43 | |
44 | #[derive (Clone)] |
45 | enum 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 | |
57 | impl 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 | |
142 | impl 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 | |
320 | impl fmt::Debug for Certificate { |
321 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
322 | f.debug_struct(name:"Certificate" ).finish() |
323 | } |
324 | } |
325 | |
326 | impl 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)] |
334 | pub struct Version(InnerVersion); |
335 | |
336 | #[derive (Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
337 | #[non_exhaustive ] |
338 | enum 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 |
347 | impl 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 | |
381 | pub(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 | |
396 | impl 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 | |
413 | impl 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" )] |
431 | pub(crate) struct NoVerifier; |
432 | |
433 | #[cfg (feature = "__rustls" )] |
434 | impl 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)] |
467 | mod 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 | |