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}