diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 004b50b9c..9b8e36318 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,7 @@ jobs: - windows / stable-i686-gnu - "feat.: default-tls disabled" - "feat.: rustls-tls" + - "feat.: native-tls" - "feat.: default-tls and rustls-tls" - "feat.: cookies" - "feat.: blocking" @@ -100,6 +101,8 @@ jobs: features: "--no-default-features" - name: "feat.: rustls-tls" features: "--no-default-features --features rustls-tls" + - name: "feat.: native-tls" + features: "--features native-tls" - name: "feat.: default-tls and rustls-tls" features: "--features rustls-tls" - name: "feat.: cookies" diff --git a/Cargo.toml b/Cargo.toml index bef47582b..233f78ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,15 @@ all-features = true [features] default = ["default-tls"] -tls = [] +# Note: this doesn't enable the 'native-tls' feature, which adds specific +# functionality for it. +default-tls = ["hyper-tls", "native-tls-crate", "__tls", "tokio-tls"] -default-tls = ["hyper-tls", "native-tls", "tls", "tokio-tls"] -default-tls-vendored = ["default-tls", "native-tls/vendored"] +# Enables native-tls specific functionality not available by default. +native-tls = ["default-tls"] +native-tls-vendored = ["native-tls", "native-tls-crate/vendored"] -rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"] +rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "__tls"] blocking = ["futures-channel", "futures-util/io", "tokio/rt-threaded", "tokio/rt-core"] @@ -40,6 +43,9 @@ stream = [] # Internal (PRIVATE!) features used to aid testing. # Don't rely on these whatsoever. They may disappear at anytime. +# Enables common types used for TLS. Useless on its own. +__tls = [] + # When enabled, disable using the cached SYS_PROXIES. __internal_proxy_sys_no_cache = [] @@ -74,7 +80,7 @@ serde_urlencoded = "0.6.1" ## default-tls hyper-tls = { version = "0.4", optional = true } -native-tls = { version = "0.2", optional = true } +native-tls-crate = { version = "0.2", optional = true, package = "native-tls" } tokio-tls = { version = "0.3.0", optional = true } # rustls-tls diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index c305d231b..a578eab06 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -13,8 +13,8 @@ use http::header::{ use http::Uri; use http::uri::Scheme; use hyper::client::ResponseFuture; -#[cfg(feature = "default-tls")] -use native_tls::TlsConnector; +#[cfg(feature = "native-tls-crate")] +use native_tls_crate::TlsConnector; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; @@ -30,9 +30,9 @@ use crate::connect::Connector; use crate::cookie; use crate::into_url::{expect_uri, try_uri}; use crate::redirect::{self, remove_sensitive_headers}; -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] use crate::tls::TlsBackend; -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] use crate::{Certificate, Identity}; use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; @@ -60,22 +60,22 @@ struct Config { // NOTE: When adding a new field, update `fmt::Debug for ClientBuilder` gzip: bool, headers: HeaderMap, - #[cfg(feature = "default-tls")] + #[cfg(feature = "native-tls")] hostname_verification: bool, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] certs_verification: bool, connect_timeout: Option, max_idle_per_host: usize, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] identity: Option, proxies: Vec, auto_sys_proxy: bool, redirect_policy: redirect::Policy, referer: bool, timeout: Option, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] root_certs: Vec, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] tls: TlsBackend, http2_only: bool, http1_title_case_headers: bool, @@ -106,9 +106,9 @@ impl ClientBuilder { config: Config { gzip: cfg!(feature = "gzip"), headers, - #[cfg(feature = "default-tls")] + #[cfg(feature = "native-tls")] hostname_verification: true, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] certs_verification: true, connect_timeout: None, max_idle_per_host: std::usize::MAX, @@ -117,11 +117,11 @@ impl ClientBuilder { redirect_policy: redirect::Policy::default(), referer: true, timeout: None, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] root_certs: Vec::new(), - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] identity: None, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] tls: TlsBackend::default(), http2_only: false, http1_title_case_headers: false, @@ -150,25 +150,34 @@ impl ClientBuilder { let proxies = Arc::new(proxies); let mut connector = { - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] fn user_agent(headers: &HeaderMap) -> HeaderValue { headers[USER_AGENT].clone() } - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] match config.tls { #[cfg(feature = "default-tls")] TlsBackend::Default => { let mut tls = TlsConnector::builder(); - tls.danger_accept_invalid_hostnames(!config.hostname_verification); + + #[cfg(feature = "native-tls")] + { + tls.danger_accept_invalid_hostnames(!config.hostname_verification); + } + tls.danger_accept_invalid_certs(!config.certs_verification); for cert in config.root_certs { cert.add_to_native_tls(&mut tls); } - if let Some(id) = config.identity { - id.add_to_native_tls(&mut tls)?; + + #[cfg(feature = "native-tls")] + { + if let Some(id) = config.identity { + id.add_to_native_tls(&mut tls)?; + } } Connector::new_default_tls( @@ -215,7 +224,7 @@ impl ClientBuilder { } } - #[cfg(not(feature = "tls"))] + #[cfg(not(feature = "__tls"))] Connector::new(proxies.clone(), config.local_address, config.nodelay)? }; @@ -511,9 +520,9 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` or `rustls-tls` feature to be - /// enabled. - #[cfg(feature = "tls")] + /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls` + /// feature to be enabled. + #[cfg(feature = "__tls")] pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder { self.config.root_certs.push(cert); self @@ -523,9 +532,9 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` or `rustls-tls` feature to be + /// This requires the optional `native-tls` or `rustls-tls` feature to be /// enabled. - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] pub fn identity(mut self, identity: Identity) -> ClientBuilder { self.config.identity = Some(identity); self @@ -544,8 +553,8 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` feature to be enabled. - #[cfg(feature = "default-tls")] + /// This requires the optional `native-tls` feature to be enabled. + #[cfg(feature = "native-tls")] pub fn danger_accept_invalid_hostnames( mut self, accept_invalid_hostname: bool, @@ -568,9 +577,9 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` or `rustls-tls` feature to be - /// enabled. - #[cfg(feature = "tls")] + /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls` + /// feature to be enabled. + #[cfg(feature = "__tls")] pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder { self.config.certs_verification = !accept_invalid_certs; self @@ -583,9 +592,9 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` feature to be enabled. - #[cfg(feature = "default-tls")] - pub fn use_default_tls(mut self) -> ClientBuilder { + /// This requires the optional `native-tls` feature to be enabled. + #[cfg(feature = "native-tls")] + pub fn use_native_tls(mut self) -> ClientBuilder { self.config.tls = TlsBackend::Default; self } @@ -888,21 +897,21 @@ impl Config { f.field("tcp_nodelay", &true); } - #[cfg(feature = "default-tls")] + #[cfg(feature = "native-tls")] { if !self.hostname_verification { f.field("danger_accept_invalid_hostnames", &true); } } - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] { if !self.certs_verification { f.field("danger_accept_invalid_certs", &true); } } - #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] + #[cfg(all(feature = "native-tls-crate", feature = "rustls-tls"))] { f.field("tls_backend", &self.tls); } diff --git a/src/blocking/client.rs b/src/blocking/client.rs index 1d80c8b3e..9aa457c5f 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -14,7 +14,7 @@ use super::request::{Request, RequestBuilder}; use super::response::Response; use super::wait; use crate::{async_impl, header, IntoUrl, Method, Proxy, redirect}; -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] use crate::{Certificate, Identity}; /// A `Client` to make Requests with. @@ -331,45 +331,15 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` or `rustls-tls` feature to be - /// enabled. - #[cfg(feature = "tls")] + /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls` + /// feature to be enabled. + #[cfg(feature = "__tls")] pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder { self.with_inner(move |inner| inner.add_root_certificate(cert)) } /// Sets the identity to be used for client certificate authentication. - /// - /// # Example - /// - /// ``` - /// # use std::fs::File; - /// # use std::io::Read; - /// # fn build_client() -> Result<(), Box> { - /// // read a local PKCS12 bundle - /// let mut buf = Vec::new(); - /// - /// #[cfg(feature = "default-tls")] - /// File::open("my-ident.pfx")?.read_to_end(&mut buf)?; - /// #[cfg(feature = "rustls-tls")] - /// File::open("my-ident.pem")?.read_to_end(&mut buf)?; - /// - /// #[cfg(feature = "default-tls")] - /// // create an Identity from the PKCS#12 archive - /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; - /// #[cfg(feature = "rustls-tls")] - /// // create an Identity from the PEM file - /// let pkcs12 = reqwest::Identity::from_pem(&buf)?; - /// - /// // get a client builder - /// let client = reqwest::blocking::Client::builder() - /// .identity(pkcs12) - /// .build()?; - /// # drop(client); - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] pub fn identity(self, identity: Identity) -> ClientBuilder { self.with_inner(move |inner| inner.identity(identity)) } @@ -384,7 +354,11 @@ impl ClientBuilder { /// hostname verification is not used, any valid certificate for any /// site will be trusted for use from any other. This introduces a /// significant vulnerability to man-in-the-middle attacks. - #[cfg(feature = "default-tls")] + /// + /// # Optional + /// + /// This requires the optional `native-tls` feature to be enabled. + #[cfg(feature = "native-tls")] pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostname: bool) -> ClientBuilder { self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname)) } @@ -400,7 +374,7 @@ impl ClientBuilder { /// will be trusted for use. This includes expired certificates. This /// introduces significant vulnerabilities, and should only be used /// as a last resort. - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder { self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs)) } @@ -412,10 +386,10 @@ impl ClientBuilder { /// /// # Optional /// - /// This requires the optional `default-tls` feature to be enabled. - #[cfg(feature = "default-tls")] - pub fn use_default_tls(self) -> ClientBuilder { - self.with_inner(move |inner| inner.use_default_tls()) + /// This requires the optional `native-tls` feature to be enabled. + #[cfg(feature = "native-tls")] + pub fn use_native_tls(self) -> ClientBuilder { + self.with_inner(move |inner| inner.use_native_tls()) } /// Force using the Rustls TLS backend. diff --git a/src/connect.rs b/src/connect.rs index d8e2fe1ac..a0cf8675e 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -3,9 +3,9 @@ use http::uri::{Scheme, Authority}; use http::Uri; use hyper::client::connect::{Connected, Connection}; use tokio::io::{AsyncRead, AsyncWrite}; -#[cfg(feature = "default-tls")] -use native_tls::{TlsConnector, TlsConnectorBuilder}; -#[cfg(feature = "tls")] +#[cfg(feature = "native-tls-crate")] +use native_tls_crate::{TlsConnector, TlsConnectorBuilder}; +#[cfg(feature = "__tls")] use http::header::HeaderValue; use bytes::{Buf, BufMut}; @@ -38,15 +38,15 @@ pub(crate) struct Connector { inner: Inner, proxies: Arc>, timeout: Option, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] nodelay: bool, - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] user_agent: HeaderValue, } #[derive(Clone)] enum Inner { - #[cfg(not(feature = "tls"))] + #[cfg(not(feature = "__tls"))] Http(HttpConnector), #[cfg(feature = "default-tls")] DefaultTls(HttpConnector, TlsConnector), @@ -59,7 +59,7 @@ enum Inner { } impl Connector { - #[cfg(not(feature = "tls"))] + #[cfg(not(feature = "__tls"))] pub(crate) fn new( proxies: Arc>, local_addr: T, @@ -199,7 +199,7 @@ impl Connector { Ok((Box::new(io) as Conn, connected)) } } - #[cfg(not(feature = "tls"))] + #[cfg(not(feature = "__tls"))] Inner::Http(_) => socks::connect(proxy, dst, dns), } } @@ -210,7 +210,7 @@ impl Connector { is_proxy: bool, ) -> Result { match self.inner { - #[cfg(not(feature = "tls"))] + #[cfg(not(feature = "__tls"))] Inner::Http(mut http) => { let io = http.call(dst).await?; Ok(Conn { @@ -281,7 +281,7 @@ impl Connector { }; - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] let auth = _auth; match &self.inner { @@ -352,7 +352,7 @@ impl Connector { }); } } - #[cfg(not(feature = "tls"))] + #[cfg(not(feature = "__tls"))] Inner::Http(_) => (), } @@ -510,7 +510,7 @@ impl AsyncWrite for Conn { pub(crate) type Connecting = Pin> + Send>>; -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] async fn tunnel( mut conn: T, host: String, @@ -586,7 +586,7 @@ where } } -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] fn tunnel_eof() -> io::Error { io::Error::new( io::ErrorKind::UnexpectedEof, @@ -834,7 +834,7 @@ mod socks { } } -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] #[cfg(test)] mod tests { use super::tunnel; diff --git a/src/lib.rs b/src/lib.rs index 43f295262..3d6a9219f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,9 +159,11 @@ //! The following are a list of [Cargo features][cargo-features] that can be //! enabled or disabled: //! -//! - **default-tls** *(enabled by default)*: Provides TLS support via the -//! `native-tls` library to connect over HTTPS. -//! - **default-tls-vendored**: Enables the `vendored` feature of `native-tls`. +//! - **default-tls** *(enabled by default)*: Provides TLS support to connect +//! over HTTPS. +//! - **native-tls**: Enables TLS functionality provided by `native-tls`. +//! - **native-tls-vendored**: Enables the `vendored` feature of `native-tls`. +//! - **rustls-tls**: Enables TLS functionality provided by `rustls`. //! - **blocking**: Provides the [blocking][] client API. //! - **cookies**: Provides cookie session support. //! - **gzip**: Provides response body gzip decompression. @@ -179,7 +181,6 @@ //! [Proxy]: ./struct.Proxy.html //! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section -////! - **rustls-tls**: Provides TLS support via the `rustls` library. ////! - **socks**: Provides SOCKS5 proxy support. ////! - **trust-dns**: Enables a trust-dns async resolver instead of default ////! threadpool using `getaddrinfo`. @@ -278,7 +279,7 @@ if_hyper! { multipart, Body, Client, ClientBuilder, Request, RequestBuilder, Response, ResponseBuilderExt, }; pub use self::proxy::Proxy; - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] pub use self::tls::{Certificate, Identity}; @@ -292,7 +293,7 @@ if_hyper! { //mod dns; mod proxy; pub mod redirect; - #[cfg(feature = "tls")] + #[cfg(feature = "__tls")] mod tls; } diff --git a/src/tls.rs b/src/tls.rs index 53d405772..fa57acc61 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -4,11 +4,11 @@ use std::fmt; #[cfg(feature = "rustls-tls")] use tokio_rustls::webpki::DNSNameRef; -/// Represent a server X509 certificate. +/// Represents a server X509 certificate. #[derive(Clone)] pub struct Certificate { - #[cfg(feature = "default-tls")] - native: native_tls::Certificate, + #[cfg(feature = "native-tls-crate")] + native: native_tls_crate::Certificate, #[cfg(feature = "rustls-tls")] original: Cert, } @@ -20,14 +20,18 @@ enum Cert { Pem(Vec), } -/// Represent a private key and X509 cert as a client certificate. +/// Represents a private key and X509 cert as a client certificate. pub struct Identity { + #[cfg_attr( + not(any(feature = "native-tls", feature = "rustls-tls")), + allow(unused) + )] inner: ClientCert, } enum ClientCert { - #[cfg(feature = "default-tls")] - Pkcs12(native_tls::Identity), + #[cfg(feature = "native-tls")] + Pkcs12(native_tls_crate::Identity), #[cfg(feature = "rustls-tls")] Pem { key: rustls::PrivateKey, @@ -54,8 +58,8 @@ impl Certificate { /// ``` pub fn from_der(der: &[u8]) -> crate::Result { Ok(Certificate { - #[cfg(feature = "default-tls")] - native: native_tls::Certificate::from_der(der).map_err(crate::error::builder)?, + #[cfg(feature = "native-tls-crate")] + native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?, #[cfg(feature = "rustls-tls")] original: Cert::Der(der.to_owned()), }) @@ -79,15 +83,15 @@ impl Certificate { /// ``` pub fn from_pem(pem: &[u8]) -> crate::Result { Ok(Certificate { - #[cfg(feature = "default-tls")] - native: native_tls::Certificate::from_pem(pem).map_err(crate::error::builder)?, + #[cfg(feature = "native-tls-crate")] + native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?, #[cfg(feature = "rustls-tls")] original: Cert::Pem(pem.to_owned()), }) } - #[cfg(feature = "default-tls")] - pub(crate) fn add_to_native_tls(self, tls: &mut native_tls::TlsConnectorBuilder) { + #[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); } @@ -147,11 +151,15 @@ impl Identity { /// # Ok(()) /// # } /// ``` - #[cfg(feature = "default-tls")] + /// + /// # Optional + /// + /// This requires the `native-tls` Cargo feature enabled. + #[cfg(feature = "native-tls")] pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result { Ok(Identity { inner: ClientCert::Pkcs12( - native_tls::Identity::from_pkcs12(der, password).map_err(crate::error::builder)?, + native_tls_crate::Identity::from_pkcs12(der, password).map_err(crate::error::builder)?, ), }) } @@ -175,6 +183,10 @@ impl Identity { /// # Ok(()) /// # } /// ``` + /// + /// # Optional + /// + /// This requires the `rustls-tls` Cargo feature enabled. #[cfg(feature = "rustls-tls")] pub fn from_pem(buf: &[u8]) -> crate::Result { use rustls::internal::pemfile; @@ -214,10 +226,10 @@ impl Identity { }) } - #[cfg(feature = "default-tls")] + #[cfg(feature = "native-tls")] pub(crate) fn add_to_native_tls( self, - tls: &mut native_tls::TlsConnectorBuilder, + tls: &mut native_tls_crate::TlsConnectorBuilder, ) -> crate::Result<()> { match self.inner { ClientCert::Pkcs12(id) => { @@ -236,7 +248,7 @@ impl Identity { tls.set_single_client_cert(certs, key); Ok(()) } - #[cfg(feature = "default-tls")] + #[cfg(feature = "native-tls")] ClientCert::Pkcs12(..) => Err(crate::error::builder("incompatible TLS identity type")), } } @@ -308,7 +320,7 @@ mod tests { Certificate::from_pem(b"not pem").unwrap_err(); } - #[cfg(feature = "default-tls")] + #[cfg(feature = "native-tls")] #[test] fn identity_from_pkcs12_der_invalid() { Identity::from_pkcs12_der(b"not der", "nope").unwrap_err(); diff --git a/tests/badssl.rs b/tests/badssl.rs index d3c285067..4052c8ff3 100644 --- a/tests/badssl.rs +++ b/tests/badssl.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] #[tokio::test] async fn test_badssl_modern() { let text = reqwest::get("https://mozilla-modern.badssl.com/") @@ -29,7 +29,7 @@ async fn test_rustls_badssl_modern() { assert!(text.contains("mozilla-modern.badssl.com")); } -#[cfg(feature = "tls")] +#[cfg(feature = "__tls")] #[tokio::test] async fn test_badssl_self_signed() { let text = reqwest::Client::builder() @@ -47,7 +47,7 @@ async fn test_badssl_self_signed() { assert!(text.contains("self-signed.badssl.com")); } -#[cfg(feature = "default-tls")] +#[cfg(feature = "native-tls")] #[tokio::test] async fn test_badssl_wrong_host() { let text = reqwest::Client::builder()