isahc/config/ssl.rs
1//! Configuration options related to SSL/TLS.
2
3use super::SetOpt;
4use curl::easy::{Easy2, SslOpt};
5use std::{
6 iter::FromIterator,
7 ops::{BitOr, BitOrAssign},
8 path::PathBuf,
9};
10
11#[derive(Clone, Debug)]
12enum PathOrBlob {
13 Path(PathBuf),
14 Blob(Vec<u8>),
15}
16
17/// A client certificate for SSL/TLS client validation.
18///
19/// Note that this isn't merely an X.509 certificate, but rather a certificate
20/// and private key pair.
21#[derive(Clone, Debug)]
22pub struct ClientCertificate {
23 /// Name of the cert format.
24 format: &'static str,
25
26 /// The certificate data, either a path or a blob.
27 data: PathOrBlob,
28
29 /// Private key corresponding to the SSL/TLS certificate.
30 private_key: Option<PrivateKey>,
31
32 /// Password to decrypt the certificate file.
33 password: Option<String>,
34}
35
36impl ClientCertificate {
37 /// Use a PEM-encoded certificate stored in the given byte buffer.
38 ///
39 /// The certificate object takes ownership of the byte buffer. If a borrowed
40 /// type is supplied, such as `&[u8]`, then the bytes will be copied.
41 ///
42 /// The certificate is not parsed or validated here. If the certificate is
43 /// malformed or the format is not supported by the underlying SSL/TLS
44 /// engine, an error will be returned when attempting to send a request
45 /// using the offending certificate.
46 pub fn pem<B, P>(bytes: B, private_key: P) -> Self
47 where
48 B: Into<Vec<u8>>,
49 P: Into<Option<PrivateKey>>,
50 {
51 Self {
52 format: "PEM",
53 data: PathOrBlob::Blob(bytes.into()),
54 private_key: private_key.into(),
55 password: None,
56 }
57 }
58
59 /// Use a DER-encoded certificate stored in the given byte buffer.
60 ///
61 /// The certificate object takes ownership of the byte buffer. If a borrowed
62 /// type is supplied, such as `&[u8]`, then the bytes will be copied.
63 ///
64 /// The certificate is not parsed or validated here. If the certificate is
65 /// malformed or the format is not supported by the underlying SSL/TLS
66 /// engine, an error will be returned when attempting to send a request
67 /// using the offending certificate.
68 pub fn der<B, P>(bytes: B, private_key: P) -> Self
69 where
70 B: Into<Vec<u8>>,
71 P: Into<Option<PrivateKey>>,
72 {
73 Self {
74 format: "DER",
75 data: PathOrBlob::Blob(bytes.into()),
76 private_key: private_key.into(),
77 password: None,
78 }
79 }
80
81 /// Use a certificate and private key from a PKCS #12 archive stored in the
82 /// given byte buffer.
83 ///
84 /// The certificate object takes ownership of the byte buffer. If a borrowed
85 /// type is supplied, such as `&[u8]`, then the bytes will be copied.
86 ///
87 /// The certificate is not parsed or validated here. If the certificate is
88 /// malformed or the format is not supported by the underlying SSL/TLS
89 /// engine, an error will be returned when attempting to send a request
90 /// using the offending certificate.
91 pub fn pkcs12<B, P>(bytes: B, password: P) -> Self
92 where
93 B: Into<Vec<u8>>,
94 P: Into<Option<String>>,
95 {
96 Self {
97 format: "P12",
98 data: PathOrBlob::Blob(bytes.into()),
99 private_key: None,
100 password: password.into(),
101 }
102 }
103
104 /// Get a certificate from a PEM-encoded file.
105 ///
106 /// The certificate file is not loaded or validated here. If the file does
107 /// not exist or the format is not supported by the underlying SSL/TLS
108 /// engine, an error will be returned when attempting to send a request
109 /// using the offending certificate.
110 pub fn pem_file(path: impl Into<PathBuf>, private_key: impl Into<Option<PrivateKey>>) -> Self {
111 Self {
112 format: "PEM",
113 data: PathOrBlob::Path(path.into()),
114 private_key: private_key.into(),
115 password: None,
116 }
117 }
118
119 /// Get a certificate from a DER-encoded file.
120 ///
121 /// The certificate file is not loaded or validated here. If the file does
122 /// not exist or the format is not supported by the underlying SSL/TLS
123 /// engine, an error will be returned when attempting to send a request
124 /// using the offending certificate.
125 pub fn der_file(path: impl Into<PathBuf>, private_key: impl Into<Option<PrivateKey>>) -> Self {
126 Self {
127 format: "DER",
128 data: PathOrBlob::Path(path.into()),
129 private_key: private_key.into(),
130 password: None,
131 }
132 }
133
134 /// Get a certificate and private key from a PKCS #12-encoded file.
135 ///
136 /// The certificate file is not loaded or validated here. If the file does
137 /// not exist or the format is not supported by the underlying SSL/TLS
138 /// engine, an error will be returned when attempting to send a request
139 /// using the offending certificate.
140 pub fn pkcs12_file(path: impl Into<PathBuf>, password: impl Into<Option<String>>) -> Self {
141 Self {
142 format: "P12",
143 data: PathOrBlob::Path(path.into()),
144 private_key: None,
145 password: password.into(),
146 }
147 }
148
149 /// Get a certificate and private key from a PKCS #12-encoded file.
150 ///
151 /// Use [`pkcs12_file`][ClientCertificate::pkcs12_file] instead.
152 #[inline]
153 #[doc(hidden)]
154 #[deprecated(
155 since = "1.4.0",
156 note = "please use the more clearly-named `pkcs12_file` instead"
157 )]
158 pub fn p12_file(path: impl Into<PathBuf>, password: impl Into<Option<String>>) -> Self {
159 Self::pkcs12_file(path, password)
160 }
161}
162
163impl SetOpt for ClientCertificate {
164 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
165 easy.ssl_cert_type(self.format)?;
166
167 match &self.data {
168 PathOrBlob::Path(path) => easy.ssl_cert(path.as_path()),
169 PathOrBlob::Blob(bytes) => easy.ssl_cert_blob(bytes.as_slice()),
170 }?;
171
172 if let Some(key) = self.private_key.as_ref() {
173 key.set_opt(easy)?;
174 }
175
176 if let Some(password) = self.password.as_ref() {
177 easy.key_password(password)?;
178 }
179
180 Ok(())
181 }
182}
183
184/// A private key file.
185#[derive(Clone, Debug)]
186pub struct PrivateKey {
187 /// Key format name.
188 format: &'static str,
189
190 /// The certificate data, either a path or a blob.
191 data: PathOrBlob,
192
193 /// Password to decrypt the key file.
194 password: Option<String>,
195}
196
197impl PrivateKey {
198 /// Use a PEM-encoded private key stored in the given byte buffer.
199 ///
200 /// The private key object takes ownership of the byte buffer. If a borrowed
201 /// type is supplied, such as `&[u8]`, then the bytes will be copied.
202 ///
203 /// The key is not parsed or validated here. If the key is malformed or the
204 /// format is not supported by the underlying SSL/TLS engine, an error will
205 /// be returned when attempting to send a request using the offending key.
206 pub fn pem<B, P>(bytes: B, password: P) -> Self
207 where
208 B: Into<Vec<u8>>,
209 P: Into<Option<String>>,
210 {
211 Self {
212 format: "PEM",
213 data: PathOrBlob::Blob(bytes.into()),
214 password: password.into(),
215 }
216 }
217
218 /// Use a DER-encoded private key stored in the given byte buffer.
219 ///
220 /// The private key object takes ownership of the byte buffer. If a borrowed
221 /// type is supplied, such as `&[u8]`, then the bytes will be copied.
222 ///
223 /// The key is not parsed or validated here. If the key is malformed or the
224 /// format is not supported by the underlying SSL/TLS engine, an error will
225 /// be returned when attempting to send a request using the offending key.
226 pub fn der<B, P>(bytes: B, password: P) -> Self
227 where
228 B: Into<Vec<u8>>,
229 P: Into<Option<String>>,
230 {
231 Self {
232 format: "DER",
233 data: PathOrBlob::Blob(bytes.into()),
234 password: password.into(),
235 }
236 }
237
238 /// Get a PEM-encoded private key file.
239 ///
240 /// The key file is not loaded or validated here. If the file does not exist
241 /// or the format is not supported by the underlying SSL/TLS engine, an
242 /// error will be returned when attempting to send a request using the
243 /// offending key.
244 pub fn pem_file(path: impl Into<PathBuf>, password: impl Into<Option<String>>) -> Self {
245 Self {
246 format: "PEM",
247 data: PathOrBlob::Path(path.into()),
248 password: password.into(),
249 }
250 }
251
252 /// Get a DER-encoded private key file.
253 ///
254 /// The key file is not loaded or validated here. If the file does not exist
255 /// or the format is not supported by the underlying SSL/TLS engine, an
256 /// error will be returned when attempting to send a request using the
257 /// offending key.
258 pub fn der_file(path: impl Into<PathBuf>, password: impl Into<Option<String>>) -> Self {
259 Self {
260 format: "DER",
261 data: PathOrBlob::Path(path.into()),
262 password: password.into(),
263 }
264 }
265}
266
267impl SetOpt for PrivateKey {
268 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
269 easy.ssl_key_type(self.format)?;
270
271 match &self.data {
272 PathOrBlob::Path(path) => easy.ssl_key(path.as_path()),
273 PathOrBlob::Blob(bytes) => easy.ssl_key_blob(bytes.as_slice()),
274 }?;
275
276 if let Some(password) = self.password.as_ref() {
277 easy.key_password(password)?;
278 }
279
280 Ok(())
281 }
282}
283
284/// A public CA certificate bundle file.
285#[derive(Clone, Debug)]
286pub struct CaCertificate {
287 /// Path to the certificate bundle file. Currently only file paths are
288 /// supported.
289 path: PathBuf,
290}
291
292impl CaCertificate {
293 /// Get a CA certificate from a path to a certificate bundle file.
294 ///
295 /// The certificate file is not loaded or validated here. If the file does
296 /// not exist or the format is not supported by the underlying SSL/TLS
297 /// engine, an error will be returned when attempting to send a request
298 /// using the offending certificate.
299 pub fn file(ca_bundle_path: impl Into<PathBuf>) -> Self {
300 Self {
301 path: ca_bundle_path.into(),
302 }
303 }
304}
305
306impl SetOpt for CaCertificate {
307 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
308 easy.cainfo(&self.path)
309 }
310}
311
312#[derive(Clone, Debug)]
313pub(crate) struct Ciphers(String);
314
315impl FromIterator<String> for Ciphers {
316 fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
317 Ciphers(iter.into_iter().collect::<Vec<_>>().join(":"))
318 }
319}
320
321impl SetOpt for Ciphers {
322 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
323 easy.ssl_cipher_list(&self.0)
324 }
325}
326
327/// A flag that can be used to alter the behavior of SSL/TLS connections.
328///
329/// Most options are for disabling security checks that introduce security
330/// risks, but may be required as a last resort.
331#[derive(Clone, Copy, Debug)]
332pub struct SslOption(usize);
333
334impl Default for SslOption {
335 fn default() -> Self {
336 Self::NONE
337 }
338}
339
340impl SslOption {
341 /// An empty set of options. This is the default.
342 pub const NONE: Self = SslOption(0);
343
344 /// Disables certificate validation.
345 ///
346 /// # Warning
347 ///
348 /// You should think very carefully before using this method. If invalid
349 /// certificates are trusted, *any* certificate for any site will be trusted
350 /// for use. This includes expired certificates. This introduces significant
351 /// vulnerabilities, and should only be used as a last resort.
352 pub const DANGER_ACCEPT_INVALID_CERTS: Self = SslOption(0b0001);
353
354 /// Disables hostname verification on certificates.
355 ///
356 /// # Warning
357 ///
358 /// You should think very carefully before you use this method. If hostname
359 /// verification is not used, any valid certificate for any site will be
360 /// trusted for use from any other. This introduces a significant
361 /// vulnerability to man-in-the-middle attacks.
362 pub const DANGER_ACCEPT_INVALID_HOSTS: Self = SslOption(0b0010);
363
364 /// Disables certificate revocation checks for backends where such behavior
365 /// is present.
366 ///
367 /// This option is only supported for Schannel (the native Windows SSL
368 /// library).
369 pub const DANGER_ACCEPT_REVOKED_CERTS: Self = SslOption(0b0100);
370
371 const fn contains(self, other: Self) -> bool {
372 (self.0 & other.0) == other.0
373 }
374}
375
376impl BitOr for SslOption {
377 type Output = Self;
378
379 fn bitor(mut self, other: Self) -> Self {
380 self |= other;
381 self
382 }
383}
384
385impl BitOrAssign for SslOption {
386 fn bitor_assign(&mut self, rhs: Self) {
387 self.0 |= rhs.0;
388 }
389}
390
391impl SetOpt for SslOption {
392 fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
393 let mut opt = SslOpt::new();
394 opt.no_revoke(self.contains(Self::DANGER_ACCEPT_REVOKED_CERTS));
395
396 easy.ssl_options(&opt)?;
397 easy.ssl_verify_peer(!self.contains(Self::DANGER_ACCEPT_INVALID_CERTS))?;
398 easy.ssl_verify_host(!self.contains(Self::DANGER_ACCEPT_INVALID_HOSTS))
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::SslOption;
405
406 #[test]
407 fn default_ssl_options() {
408 let options = SslOption::default();
409
410 assert!(!options.contains(SslOption::DANGER_ACCEPT_INVALID_CERTS));
411 assert!(!options.contains(SslOption::DANGER_ACCEPT_INVALID_HOSTS));
412 assert!(!options.contains(SslOption::DANGER_ACCEPT_REVOKED_CERTS));
413 }
414
415 #[test]
416 fn ssl_option_invalid_certs() {
417 let options = SslOption::DANGER_ACCEPT_INVALID_CERTS;
418
419 assert!(options.contains(SslOption::DANGER_ACCEPT_INVALID_CERTS));
420 assert!(!options.contains(SslOption::DANGER_ACCEPT_INVALID_HOSTS));
421
422 let options = SslOption::DANGER_ACCEPT_INVALID_HOSTS;
423
424 assert!(!options.contains(SslOption::DANGER_ACCEPT_INVALID_CERTS));
425 assert!(options.contains(SslOption::DANGER_ACCEPT_INVALID_HOSTS));
426 }
427}