diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 9a34f3fb6..ceb325bb4 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -45,6 +45,8 @@ use crate::tls::{self, TlsBackend}; use crate::Certificate; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; +#[cfg(feature = "__rustls")] +use crate::Crl; use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; use log::debug; #[cfg(feature = "http3")] @@ -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,7 +594,9 @@ impl ClientBuilder { // Build TLS config let signature_algorithms = provider.signature_verification_algorithms; - let config_builder = rustls::ClientConfig::builder_with_provider(provider) + let config_builder = rustls::ClientConfig::builder_with_provider( + provider.clone() + ) .with_protocol_versions(&versions) .map_err(|_| crate::error::builder("invalid TLS versions"))?; @@ -604,7 +612,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 +1433,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..870200391 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,78 @@ 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 +500,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 +822,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-----