diff --git a/Cargo.toml b/Cargo.toml index 28addf257..e1c561a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,11 +70,11 @@ http3 = ["rustls-tls-manual-roots", "h3", "h3-quinn", "quinn", "futures-channel" # Don't rely on these whatsoever. They may disappear at anytime. # Enables common types used for TLS. Useless on its own. -__tls = [] +__tls = ["dep:rustls-pemfile"] # Enables common rustls code. # Equivalent to rustls-tls-manual-roots but shorter :) -__rustls = ["hyper-rustls", "tokio-rustls", "rustls", "__tls", "rustls-pemfile"] +__rustls = ["hyper-rustls", "tokio-rustls", "rustls", "__tls"] # When enabled, disable using the cached SYS_PROXIES. __internal_proxy_sys_no_cache = [] @@ -112,6 +112,7 @@ pin-project-lite = "0.2.0" ipnet = "2.3" # Optional deps... +rustls-pemfile = { version = "1.0", optional = true } ## default-tls hyper-tls = { version = "0.5", optional = true } @@ -124,7 +125,6 @@ rustls = { version = "0.21.6", features = ["dangerous_configuration"], optional tokio-rustls = { version = "0.24", optional = true } webpki-roots = { version = "0.25", optional = true } rustls-native-certs = { version = "0.6", optional = true } -rustls-pemfile = { version = "1.0", optional = true } ## cookies cookie_crate = { version = "0.17.0", package = "cookie", optional = true } diff --git a/src/tls.rs b/src/tls.rs index 781d9a6b2..530884b83 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -49,7 +49,10 @@ use rustls::{ client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier, DigitallySignedStruct, Error as TLSError, ServerName, }; -use std::fmt; +use std::{ + fmt, + io::{BufRead, BufReader}, +}; /// Represents a server X509 certificate. #[derive(Clone)] @@ -138,6 +141,32 @@ impl Certificate { }) } + /// Create a collection of `Certificate`s from a PEM encoded certificate bundle. + /// Example byte sources may be `.crt`, `.cer` or `.pem` files. + /// + /// # Examples + /// + /// ``` + /// # use std::fs::File; + /// # use std::io::Read; + /// # fn cert() -> Result<(), Box> { + /// let mut buf = Vec::new(); + /// File::open("ca-bundle.crt")? + /// .read_to_end(&mut buf)?; + /// let certs = reqwest::Certificate::from_pem_bundle(&buf)?; + /// # drop(certs); + /// # Ok(()) + /// # } + /// ``` + pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result> { + let mut reader = BufReader::new(pem_bundle); + + Self::read_pem_certs(&mut reader)? + .iter() + .map(|cert_vec| Certificate::from_pem(&cert_vec)) + .collect::>>() + } + #[cfg(feature = "native-tls-crate")] pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) { tls.add_root_certificate(self.native); @@ -155,12 +184,8 @@ impl Certificate { .add(&rustls::Certificate(buf)) .map_err(crate::error::builder)?, Cert::Pem(buf) => { - let mut pem = Cursor::new(buf); - let certs = rustls_pemfile::certs(&mut pem).map_err(|_| { - crate::error::builder(TLSError::General(String::from( - "No valid certificate was found", - ))) - })?; + let mut reader = Cursor::new(buf); + let certs = Self::read_pem_certs(&mut reader)?; for c in certs { root_cert_store .add(&rustls::Certificate(c)) @@ -170,6 +195,11 @@ impl Certificate { } Ok(()) } + + fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result>> { + rustls_pemfile::certs(reader) + .map_err(|_| crate::error::builder("invalid certificate encoding")) + } } impl Identity {