From b8e75621bcb80bd6ece2987b8033abafb2b4e21f Mon Sep 17 00:00:00 2001 From: Ksenia Vazhdaeva Date: Thu, 26 Sep 2024 08:38:01 +0700 Subject: [PATCH 1/2] Add CRLs to rustls feature --- src/async_impl/client.rs | 60 +++++++++++++++++++++-- src/blocking/client.rs | 26 ++++++++++ src/lib.rs | 2 + src/tls.rs | 103 +++++++++++++++++++++++++++++++++++++++ tests/support/crl.pem | 11 +++++ 5 files changed, 198 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 9a34f3fb6..117e1c0c8 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -43,6 +43,8 @@ use crate::redirect::{self, remove_sensitive_headers}; use crate::tls::{self, TlsBackend}; #[cfg(feature = "__tls")] use crate::Certificate; +#[cfg(feature = "__rustls")] +use crate::Crl; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; @@ -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,32 @@ 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: Crl) -> 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, mut crls: Vec) -> ClientBuilder { + self.config.crls.append(&mut 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..389303300 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -21,6 +21,8 @@ use crate::dns::Resolve; use crate::tls; #[cfg(feature = "__tls")] use crate::Certificate; +#[cfg(feature = "__rustls")] +use crate::Crl; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy}; @@ -606,6 +608,30 @@ 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: Crl) -> 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, mut crls: Vec) -> 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/lib.rs b/src/lib.rs index cf3d39d0f..25a895a8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -349,6 +349,8 @@ if_hyper! { #[cfg(feature = "__tls")] // Re-exports, to be removed in a future release pub use tls::{Certificate, Identity}; + #[cfg(feature = "__rustls")] + pub use tls::Crl; #[cfg(feature = "multipart")] pub use self::async_impl::multipart; diff --git a/src/tls.rs b/src/tls.rs index 83f3feee8..2de78bfc8 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 Crl { + #[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 Crl { + /// 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::Crl::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(Crl { + #[cfg(feature = "__rustls")] + inner: rustls_pki_types::CertificateRevocationListDer::from(pem.to_vec()), + }) + } + + /// Creates a collection of `Crl`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::Crl::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(Crl { 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 Crl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Crl").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"; + + Crl::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 = Crl::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----- From 1e7300706875e227bd2851e0a0fa4cc1f99ba6c3 Mon Sep 17 00:00:00 2001 From: Ksenia Vazhdaeva Date: Wed, 2 Oct 2024 21:19:17 +0700 Subject: [PATCH 2/2] Fixes for certificate revocation lists ib rustls feature --- src/async_impl/client.rs | 15 +++++++++------ src/blocking/client.rs | 11 +++++++---- src/lib.rs | 2 -- src/tls.rs | 28 ++++++++++++++-------------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 117e1c0c8..376e92391 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -39,12 +39,12 @@ 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")] use crate::Certificate; -#[cfg(feature = "__rustls")] -use crate::Crl; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; @@ -121,7 +121,7 @@ struct Config { #[cfg(feature = "rustls-tls-native-roots")] tls_built_in_certs_native: bool, #[cfg(feature = "__rustls")] - crls: Vec, + crls: Vec, #[cfg(feature = "__tls")] min_tls_version: Option, #[cfg(feature = "__tls")] @@ -1440,7 +1440,7 @@ impl ClientBuilder { /// 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: Crl) -> ClientBuilder { + pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder { self.config.crls.push(crl); self } @@ -1453,8 +1453,11 @@ impl ClientBuilder { /// 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, mut crls: Vec) -> ClientBuilder { - self.config.crls.append(&mut crls); + pub fn add_crls( + mut self, + crls: impl IntoIterator, + ) -> ClientBuilder { + self.config.crls.extend(crls); self } diff --git a/src/blocking/client.rs b/src/blocking/client.rs index 389303300..9a447e2d5 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -19,10 +19,10 @@ 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(feature = "__rustls")] -use crate::Crl; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy}; @@ -616,7 +616,7 @@ impl ClientBuilder { /// 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: Crl) -> ClientBuilder { + pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder { self.with_inner(move |inner| inner.add_crl(crl)) } @@ -628,7 +628,10 @@ impl ClientBuilder { /// 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, mut crls: Vec) -> ClientBuilder { + pub fn add_crls( + mut self, + crls: impl IntoIterator, + ) -> ClientBuilder { self.with_inner(move |inner| inner.add_crls(crls)) } diff --git a/src/lib.rs b/src/lib.rs index 25a895a8f..cf3d39d0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -349,8 +349,6 @@ if_hyper! { #[cfg(feature = "__tls")] // Re-exports, to be removed in a future release pub use tls::{Certificate, Identity}; - #[cfg(feature = "__rustls")] - pub use tls::Crl; #[cfg(feature = "multipart")] pub use self::async_impl::multipart; diff --git a/src/tls.rs b/src/tls.rs index 2de78bfc8..e5480dc96 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -60,7 +60,7 @@ use std::{ /// Represents a X509 certificate revocation list. #[cfg(feature = "__rustls")] -pub struct Crl { +pub struct CertificateRevocationList { #[cfg(feature = "__rustls")] inner: rustls_pki_types::CertificateRevocationListDer<'static>, } @@ -417,7 +417,7 @@ impl Identity { } #[cfg(feature = "__rustls")] -impl Crl { +impl CertificateRevocationList { /// Parses a PEM encoded CRL. /// /// # Examples @@ -429,7 +429,7 @@ impl Crl { /// let mut buf = Vec::new(); /// File::open("my_crl.pem")? /// .read_to_end(&mut buf)?; - /// let crl = reqwest::Crl::from_pem(&buf)?; + /// let crl = reqwest::tls::CertificateRevocationList::from_pem(&buf)?; /// # drop(crl); /// # Ok(()) /// # } @@ -439,14 +439,14 @@ impl Crl { /// /// This requires the `rustls-tls(-...)` Cargo feature enabled. #[cfg(feature = "__rustls")] - pub fn from_pem(pem: &[u8]) -> crate::Result { - Ok(Crl { + 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 `Crl`s from a PEM encoded CRL bundle. + /// Creates a collection of `CertificateRevocationList`s from a PEM encoded CRL bundle. /// Example byte sources may be `.crl` or `.pem` files. /// /// # Examples @@ -458,7 +458,7 @@ impl Crl { /// let mut buf = Vec::new(); /// File::open("crl-bundle.crl")? /// .read_to_end(&mut buf)?; - /// let crls = reqwest::Crl::from_pem_bundle(&buf)?; + /// let crls = reqwest::tls::CertificateRevocationList::from_pem_bundle(&buf)?; /// # drop(crls); /// # Ok(()) /// # } @@ -468,15 +468,15 @@ impl Crl { /// /// This requires the `rustls-tls(-...)` Cargo feature enabled. #[cfg(feature = "__rustls")] - pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result> { + 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(Crl { inner: crl }), + Ok(crl) => Ok(CertificateRevocationList { inner: crl }), Err(_) => Err(crate::error::builder("invalid crl encoding")), }) - .collect::>>() + .collect::>>() } #[cfg(feature = "__rustls")] @@ -498,9 +498,9 @@ impl fmt::Debug for Identity { } #[cfg(feature = "__rustls")] -impl fmt::Debug for Crl { +impl fmt::Debug for CertificateRevocationList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Crl").finish() + f.debug_struct("CertificateRevocationList").finish() } } @@ -825,7 +825,7 @@ mod tests { fn crl_from_pem() { let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n"; - Crl::from_pem(pem).unwrap(); + CertificateRevocationList::from_pem(pem).unwrap(); } #[cfg(feature = "__rustls")] @@ -833,7 +833,7 @@ mod tests { fn crl_from_pem_bundle() { let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap(); - let result = Crl::from_pem_bundle(&pem_bundle); + let result = CertificateRevocationList::from_pem_bundle(&pem_bundle); assert!(result.is_ok()); let result = result.unwrap();