Skip to content

Commit aba01ff

Browse files
feat: Add support for Certificate Revocation Lists (#2433)
Currently requires rustls backend.
1 parent 3ad6e02 commit aba01ff

File tree

4 files changed

+202
-4
lines changed

4 files changed

+202
-4
lines changed

src/async_impl/client.rs

+59-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolv
3939
use crate::error;
4040
use crate::into_url::try_uri;
4141
use crate::redirect::{self, remove_sensitive_headers};
42+
#[cfg(feature = "__rustls")]
43+
use crate::tls::CertificateRevocationList;
4244
#[cfg(feature = "__tls")]
4345
use crate::tls::{self, TlsBackend};
4446
#[cfg(feature = "__tls")]
@@ -118,6 +120,8 @@ struct Config {
118120
tls_built_in_certs_webpki: bool,
119121
#[cfg(feature = "rustls-tls-native-roots")]
120122
tls_built_in_certs_native: bool,
123+
#[cfg(feature = "__rustls")]
124+
crls: Vec<CertificateRevocationList>,
121125
#[cfg(feature = "__tls")]
122126
min_tls_version: Option<tls::Version>,
123127
#[cfg(feature = "__tls")]
@@ -217,6 +221,8 @@ impl ClientBuilder {
217221
tls_built_in_certs_native: true,
218222
#[cfg(any(feature = "native-tls", feature = "__rustls"))]
219223
identity: None,
224+
#[cfg(feature = "__rustls")]
225+
crls: vec![],
220226
#[cfg(feature = "__tls")]
221227
min_tls_version: None,
222228
#[cfg(feature = "__tls")]
@@ -588,9 +594,10 @@ impl ClientBuilder {
588594

589595
// Build TLS config
590596
let signature_algorithms = provider.signature_verification_algorithms;
591-
let config_builder = rustls::ClientConfig::builder_with_provider(provider)
592-
.with_protocol_versions(&versions)
593-
.map_err(|_| crate::error::builder("invalid TLS versions"))?;
597+
let config_builder =
598+
rustls::ClientConfig::builder_with_provider(provider.clone())
599+
.with_protocol_versions(&versions)
600+
.map_err(|_| crate::error::builder("invalid TLS versions"))?;
594601

595602
let config_builder = if !config.certs_verification {
596603
config_builder
@@ -604,7 +611,26 @@ impl ClientBuilder {
604611
signature_algorithms,
605612
)))
606613
} else {
607-
config_builder.with_root_certificates(root_cert_store)
614+
if config.crls.is_empty() {
615+
config_builder.with_root_certificates(root_cert_store)
616+
} else {
617+
let crls = config
618+
.crls
619+
.iter()
620+
.map(|e| e.as_rustls_crl())
621+
.collect::<Vec<_>>();
622+
let verifier =
623+
rustls::client::WebPkiServerVerifier::builder_with_provider(
624+
Arc::new(root_cert_store),
625+
provider,
626+
)
627+
.with_crls(crls)
628+
.build()
629+
.map_err(|_| {
630+
crate::error::builder("invalid TLS verification settings")
631+
})?;
632+
config_builder.with_webpki_verifier(verifier)
633+
}
608634
};
609635

610636
// Finalize TLS config
@@ -1406,6 +1432,35 @@ impl ClientBuilder {
14061432
self
14071433
}
14081434

1435+
/// Add a certificate revocation list.
1436+
///
1437+
///
1438+
/// # Optional
1439+
///
1440+
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
1441+
#[cfg(feature = "__rustls")]
1442+
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
1443+
pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder {
1444+
self.config.crls.push(crl);
1445+
self
1446+
}
1447+
1448+
/// Add multiple certificate revocation lists.
1449+
///
1450+
///
1451+
/// # Optional
1452+
///
1453+
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
1454+
#[cfg(feature = "__rustls")]
1455+
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
1456+
pub fn add_crls(
1457+
mut self,
1458+
crls: impl IntoIterator<Item = CertificateRevocationList>,
1459+
) -> ClientBuilder {
1460+
self.config.crls.extend(crls);
1461+
self
1462+
}
1463+
14091464
/// Controls the use of built-in/preloaded certificates during certificate validation.
14101465
///
14111466
/// Defaults to `true` -- built-in system certs will be used.

src/blocking/client.rs

+29
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use super::wait;
1919
use crate::dns::Resolve;
2020
#[cfg(feature = "__tls")]
2121
use crate::tls;
22+
#[cfg(feature = "__rustls")]
23+
use crate::tls::CertificateRevocationList;
2224
#[cfg(feature = "__tls")]
2325
use crate::Certificate;
2426
#[cfg(any(feature = "native-tls", feature = "__rustls"))]
@@ -606,6 +608,33 @@ impl ClientBuilder {
606608
self.with_inner(move |inner| inner.add_root_certificate(cert))
607609
}
608610

611+
/// Add a certificate revocation list.
612+
///
613+
///
614+
/// # Optional
615+
///
616+
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
617+
#[cfg(feature = "__rustls")]
618+
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
619+
pub fn add_crl(mut self, crl: CertificateRevocationList) -> ClientBuilder {
620+
self.with_inner(move |inner| inner.add_crl(crl))
621+
}
622+
623+
/// Add multiple certificate revocation lists.
624+
///
625+
///
626+
/// # Optional
627+
///
628+
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
629+
#[cfg(feature = "__rustls")]
630+
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
631+
pub fn add_crls(
632+
mut self,
633+
crls: impl IntoIterator<Item = CertificateRevocationList>,
634+
) -> ClientBuilder {
635+
self.with_inner(move |inner| inner.add_crls(crls))
636+
}
637+
609638
/// Controls the use of built-in system certificates during certificate validation.
610639
///
611640
/// Defaults to `true` -- built-in system certs will be used.

src/tls.rs

+103
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ use std::{
5858
io::{BufRead, BufReader},
5959
};
6060

61+
/// Represents a X509 certificate revocation list.
62+
#[cfg(feature = "__rustls")]
63+
pub struct CertificateRevocationList {
64+
#[cfg(feature = "__rustls")]
65+
inner: rustls_pki_types::CertificateRevocationListDer<'static>,
66+
}
67+
6168
/// Represents a server X509 certificate.
6269
#[derive(Clone)]
6370
pub struct Certificate {
@@ -409,6 +416,75 @@ impl Identity {
409416
}
410417
}
411418

419+
#[cfg(feature = "__rustls")]
420+
impl CertificateRevocationList {
421+
/// Parses a PEM encoded CRL.
422+
///
423+
/// # Examples
424+
///
425+
/// ```
426+
/// # use std::fs::File;
427+
/// # use std::io::Read;
428+
/// # fn crl() -> Result<(), Box<dyn std::error::Error>> {
429+
/// let mut buf = Vec::new();
430+
/// File::open("my_crl.pem")?
431+
/// .read_to_end(&mut buf)?;
432+
/// let crl = reqwest::tls::CertificateRevocationList::from_pem(&buf)?;
433+
/// # drop(crl);
434+
/// # Ok(())
435+
/// # }
436+
/// ```
437+
///
438+
/// # Optional
439+
///
440+
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
441+
#[cfg(feature = "__rustls")]
442+
pub fn from_pem(pem: &[u8]) -> crate::Result<CertificateRevocationList> {
443+
Ok(CertificateRevocationList {
444+
#[cfg(feature = "__rustls")]
445+
inner: rustls_pki_types::CertificateRevocationListDer::from(pem.to_vec()),
446+
})
447+
}
448+
449+
/// Creates a collection of `CertificateRevocationList`s from a PEM encoded CRL bundle.
450+
/// Example byte sources may be `.crl` or `.pem` files.
451+
///
452+
/// # Examples
453+
///
454+
/// ```
455+
/// # use std::fs::File;
456+
/// # use std::io::Read;
457+
/// # fn crls() -> Result<(), Box<dyn std::error::Error>> {
458+
/// let mut buf = Vec::new();
459+
/// File::open("crl-bundle.crl")?
460+
/// .read_to_end(&mut buf)?;
461+
/// let crls = reqwest::tls::CertificateRevocationList::from_pem_bundle(&buf)?;
462+
/// # drop(crls);
463+
/// # Ok(())
464+
/// # }
465+
/// ```
466+
///
467+
/// # Optional
468+
///
469+
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
470+
#[cfg(feature = "__rustls")]
471+
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<CertificateRevocationList>> {
472+
let mut reader = BufReader::new(pem_bundle);
473+
474+
rustls_pemfile::crls(&mut reader)
475+
.map(|result| match result {
476+
Ok(crl) => Ok(CertificateRevocationList { inner: crl }),
477+
Err(_) => Err(crate::error::builder("invalid crl encoding")),
478+
})
479+
.collect::<crate::Result<Vec<CertificateRevocationList>>>()
480+
}
481+
482+
#[cfg(feature = "__rustls")]
483+
pub(crate) fn as_rustls_crl<'a>(&self) -> rustls_pki_types::CertificateRevocationListDer<'a> {
484+
self.inner.clone()
485+
}
486+
}
487+
412488
impl fmt::Debug for Certificate {
413489
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
414490
f.debug_struct("Certificate").finish()
@@ -421,6 +497,13 @@ impl fmt::Debug for Identity {
421497
}
422498
}
423499

500+
#[cfg(feature = "__rustls")]
501+
impl fmt::Debug for CertificateRevocationList {
502+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
503+
f.debug_struct("CertificateRevocationList").finish()
504+
}
505+
}
506+
424507
/// A TLS protocol version.
425508
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
426509
pub struct Version(InnerVersion);
@@ -736,4 +819,24 @@ mod tests {
736819

737820
assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok())
738821
}
822+
823+
#[cfg(feature = "__rustls")]
824+
#[test]
825+
fn crl_from_pem() {
826+
let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n";
827+
828+
CertificateRevocationList::from_pem(pem).unwrap();
829+
}
830+
831+
#[cfg(feature = "__rustls")]
832+
#[test]
833+
fn crl_from_pem_bundle() {
834+
let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap();
835+
836+
let result = CertificateRevocationList::from_pem_bundle(&pem_bundle);
837+
838+
assert!(result.is_ok());
839+
let result = result.unwrap();
840+
assert_eq!(result.len(), 1);
841+
}
739842
}

tests/support/crl.pem

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-----BEGIN X509 CRL-----
2+
MIIBnjCBhwIBATANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDDAJjYRcNMjQwOTI2
3+
MDA0MjU1WhcNMjQxMDI2MDA0MjU1WjAUMBICAQEXDTI0MDkyNjAwNDI0NlqgMDAu
4+
MB8GA1UdIwQYMBaAFDxOaZI8zUaGX7mXAZ9Zd8jhyC3sMAsGA1UdFAQEAgIQATAN
5+
BgkqhkiG9w0BAQsFAAOCAQEAsqBa289UYKAOaH2gp3yC7YBF7uVZ25i3WV/InKjK
6+
zT/fFzZ9rL87ofl0VuR0GPAfwLXFQ96vYUg/nrlxF/A6FmQKf9JSlVBIVXaS2uyk
7+
fmdVX8fdU13uD2uKThT5Fojk5nKAeui0xwjTHqe9BjyDscQ5d5pkLIJUj/JbQmRF
8+
D/OtEpYQZMAdHLDF0a/9v69g/evlPlpTcikAU+T8rXp45rrsuuUgyhJ00UnE41j8
9+
MmMi3cn23JjFTyOrYx5g/0VFUNcwZpgZSnxNvFbcoh9oHHqS+UDESrwQmkmwrVvH
10+
a7PEJq5ZPtjUPa0i7oFNa9cC+11Doo5bxkpCWhypvgTUzw==
11+
-----END X509 CRL-----

0 commit comments

Comments
 (0)