From aba01ff7df33a3f29e3f7fdbd24ee90390276335 Mon Sep 17 00:00:00 2001 From: Ksenia Vazhdaeva Date: Fri, 18 Oct 2024 22:28:30 +0700 Subject: [PATCH] feat: Add support for Certificate Revocation Lists (#2433) Currently requires rustls backend. --- src/async_impl/client.rs | 63 ++++++++++++++++++++++-- src/blocking/client.rs | 29 +++++++++++ src/tls.rs | 103 +++++++++++++++++++++++++++++++++++++++ tests/support/crl.pem | 11 +++++ 4 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 tests/support/crl.pem diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 095adf4d8..6e21908a3 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -39,6 +39,8 @@ use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolv use crate::error; use crate::into_url::try_uri; use crate::redirect::{self, remove_sensitive_headers}; +#[cfg(feature = "__rustls")] +use crate::tls::CertificateRevocationList; #[cfg(feature = "__tls")] use crate::tls::{self, TlsBackend}; #[cfg(feature = "__tls")] @@ -118,6 +120,8 @@ struct Config { tls_built_in_certs_webpki: bool, #[cfg(feature = "rustls-tls-native-roots")] tls_built_in_certs_native: bool, + #[cfg(feature = "__rustls")] + crls: Vec, #[cfg(feature = "__tls")] min_tls_version: Option, #[cfg(feature = "__tls")] @@ -217,6 +221,8 @@ impl ClientBuilder { tls_built_in_certs_native: true, #[cfg(any(feature = "native-tls", feature = "__rustls"))] identity: None, + #[cfg(feature = "__rustls")] + crls: vec![], #[cfg(feature = "__tls")] min_tls_version: None, #[cfg(feature = "__tls")] @@ -588,9 +594,10 @@ impl ClientBuilder { // Build TLS config let signature_algorithms = provider.signature_verification_algorithms; - let config_builder = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&versions) - .map_err(|_| crate::error::builder("invalid TLS versions"))?; + let config_builder = + rustls::ClientConfig::builder_with_provider(provider.clone()) + .with_protocol_versions(&versions) + .map_err(|_| crate::error::builder("invalid TLS versions"))?; let config_builder = if !config.certs_verification { config_builder @@ -604,7 +611,26 @@ impl ClientBuilder { signature_algorithms, ))) } else { - config_builder.with_root_certificates(root_cert_store) + if config.crls.is_empty() { + config_builder.with_root_certificates(root_cert_store) + } else { + let crls = config + .crls + .iter() + .map(|e| e.as_rustls_crl()) + .collect::>(); + let verifier = + rustls::client::WebPkiServerVerifier::builder_with_provider( + Arc::new(root_cert_store), + provider, + ) + .with_crls(crls) + .build() + .map_err(|_| { + crate::error::builder("invalid TLS verification settings") + })?; + config_builder.with_webpki_verifier(verifier) + } }; // Finalize TLS config @@ -1406,6 +1432,35 @@ impl ClientBuilder { self } + /// Add a certificate revocation list. + /// + /// + /// # Optional + /// + /// This requires the `rustls-tls(-...)` Cargo feature enabled. + #[cfg(feature = "__rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))] + pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder { + self.config.crls.push(crl); + self + } + + /// Add multiple certificate revocation lists. + /// + /// + /// # Optional + /// + /// This requires the `rustls-tls(-...)` Cargo feature enabled. + #[cfg(feature = "__rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))] + pub fn add_crls( + mut self, + crls: impl IntoIterator, + ) -> ClientBuilder { + self.config.crls.extend(crls); + self + } + /// Controls the use of built-in/preloaded certificates during certificate validation. /// /// Defaults to `true` -- built-in system certs will be used. diff --git a/src/blocking/client.rs b/src/blocking/client.rs index d4b973ee6..9a447e2d5 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -19,6 +19,8 @@ use super::wait; use crate::dns::Resolve; #[cfg(feature = "__tls")] use crate::tls; +#[cfg(feature = "__rustls")] +use crate::tls::CertificateRevocationList; #[cfg(feature = "__tls")] use crate::Certificate; #[cfg(any(feature = "native-tls", feature = "__rustls"))] @@ -606,6 +608,33 @@ impl ClientBuilder { self.with_inner(move |inner| inner.add_root_certificate(cert)) } + /// Add a certificate revocation list. + /// + /// + /// # Optional + /// + /// This requires the `rustls-tls(-...)` Cargo feature enabled. + #[cfg(feature = "__rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))] + pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder { + self.with_inner(move |inner| inner.add_crl(crl)) + } + + /// Add multiple certificate revocation lists. + /// + /// + /// # Optional + /// + /// This requires the `rustls-tls(-...)` Cargo feature enabled. + #[cfg(feature = "__rustls")] + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))] + pub fn add_crls( + mut self, + crls: impl IntoIterator, + ) -> ClientBuilder { + self.with_inner(move |inner| inner.add_crls(crls)) + } + /// Controls the use of built-in system certificates during certificate validation. /// /// Defaults to `true` -- built-in system certs will be used. diff --git a/src/tls.rs b/src/tls.rs index 83f3feee8..e5480dc96 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -58,6 +58,13 @@ use std::{ io::{BufRead, BufReader}, }; +/// Represents a X509 certificate revocation list. +#[cfg(feature = "__rustls")] +pub struct CertificateRevocationList { + #[cfg(feature = "__rustls")] + inner: rustls_pki_types::CertificateRevocationListDer<'static>, +} + /// Represents a server X509 certificate. #[derive(Clone)] pub struct Certificate { @@ -409,6 +416,75 @@ impl Identity { } } +#[cfg(feature = "__rustls")] +impl CertificateRevocationList { + /// Parses a PEM encoded CRL. + /// + /// # Examples + /// + /// ``` + /// # use std::fs::File; + /// # use std::io::Read; + /// # fn crl() -> Result<(), Box> { + /// let mut buf = Vec::new(); + /// File::open("my_crl.pem")? + /// .read_to_end(&mut buf)?; + /// let crl = reqwest::tls::CertificateRevocationList::from_pem(&buf)?; + /// # drop(crl); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Optional + /// + /// This requires the `rustls-tls(-...)` Cargo feature enabled. + #[cfg(feature = "__rustls")] + pub fn from_pem(pem: &[u8]) -> crate::Result { + Ok(CertificateRevocationList { + #[cfg(feature = "__rustls")] + inner: rustls_pki_types::CertificateRevocationListDer::from(pem.to_vec()), + }) + } + + /// Creates a collection of `CertificateRevocationList`s from a PEM encoded CRL bundle. + /// Example byte sources may be `.crl` or `.pem` files. + /// + /// # Examples + /// + /// ``` + /// # use std::fs::File; + /// # use std::io::Read; + /// # fn crls() -> Result<(), Box> { + /// let mut buf = Vec::new(); + /// File::open("crl-bundle.crl")? + /// .read_to_end(&mut buf)?; + /// let crls = reqwest::tls::CertificateRevocationList::from_pem_bundle(&buf)?; + /// # drop(crls); + /// # Ok(()) + /// # } + /// ``` + /// + /// # Optional + /// + /// This requires the `rustls-tls(-...)` Cargo feature enabled. + #[cfg(feature = "__rustls")] + pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result> { + let mut reader = BufReader::new(pem_bundle); + + rustls_pemfile::crls(&mut reader) + .map(|result| match result { + Ok(crl) => Ok(CertificateRevocationList { inner: crl }), + Err(_) => Err(crate::error::builder("invalid crl encoding")), + }) + .collect::>>() + } + + #[cfg(feature = "__rustls")] + pub(crate) fn as_rustls_crl<'a>(&self) -> rustls_pki_types::CertificateRevocationListDer<'a> { + self.inner.clone() + } +} + impl fmt::Debug for Certificate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Certificate").finish() @@ -421,6 +497,13 @@ impl fmt::Debug for Identity { } } +#[cfg(feature = "__rustls")] +impl fmt::Debug for CertificateRevocationList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("CertificateRevocationList").finish() + } +} + /// A TLS protocol version. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Version(InnerVersion); @@ -736,4 +819,24 @@ mod tests { assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok()) } + + #[cfg(feature = "__rustls")] + #[test] + fn crl_from_pem() { + let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n"; + + CertificateRevocationList::from_pem(pem).unwrap(); + } + + #[cfg(feature = "__rustls")] + #[test] + fn crl_from_pem_bundle() { + let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap(); + + let result = CertificateRevocationList::from_pem_bundle(&pem_bundle); + + assert!(result.is_ok()); + let result = result.unwrap(); + assert_eq!(result.len(), 1); + } } diff --git a/tests/support/crl.pem b/tests/support/crl.pem new file mode 100644 index 000000000..190f2c7c6 --- /dev/null +++ b/tests/support/crl.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBnjCBhwIBATANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDDAJjYRcNMjQwOTI2 +MDA0MjU1WhcNMjQxMDI2MDA0MjU1WjAUMBICAQEXDTI0MDkyNjAwNDI0NlqgMDAu +MB8GA1UdIwQYMBaAFDxOaZI8zUaGX7mXAZ9Zd8jhyC3sMAsGA1UdFAQEAgIQATAN +BgkqhkiG9w0BAQsFAAOCAQEAsqBa289UYKAOaH2gp3yC7YBF7uVZ25i3WV/InKjK +zT/fFzZ9rL87ofl0VuR0GPAfwLXFQ96vYUg/nrlxF/A6FmQKf9JSlVBIVXaS2uyk +fmdVX8fdU13uD2uKThT5Fojk5nKAeui0xwjTHqe9BjyDscQ5d5pkLIJUj/JbQmRF +D/OtEpYQZMAdHLDF0a/9v69g/evlPlpTcikAU+T8rXp45rrsuuUgyhJ00UnE41j8 +MmMi3cn23JjFTyOrYx5g/0VFUNcwZpgZSnxNvFbcoh9oHHqS+UDESrwQmkmwrVvH +a7PEJq5ZPtjUPa0i7oFNa9cC+11Doo5bxkpCWhypvgTUzw== +-----END X509 CRL-----