diff --git a/.travis.yml b/.travis.yml index df01ee4..e72721b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,5 @@ script: - if [[ $(rustup show active-toolchain) == stable* ]]; then cargo fmt -- --check; fi; - cargo test --features tls - cargo test --features rustls --no-default-features + - cargo test --features rustls-webpki --no-default-features - cargo test --no-default-features diff --git a/Cargo.toml b/Cargo.toml index f060115..7f848c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ hyper-rustls = { version="0.19", optional=true } webpki = { version = "0.21", optional = true } rustls-native-certs = { version = "0.1.0", optional = true } +webpki-roots = { version = "0.20.0", optional = true } typed-headers = "0.2" [dev-dependencies] @@ -36,5 +37,9 @@ tokio = { version = "0.2.4", features = ["full"] } [features] tls = ["tokio-tls", "hyper-tls", "native-tls"] -rustls = ["tokio-rustls", "hyper-rustls", "webpki", "rustls-native-certs"] +# note that `rustls-base` is not a valid feature on its own - it will configure rustls without root +# certificates! +rustls-base = ["tokio-rustls", "hyper-rustls", "webpki"] +rustls = ["rustls-base", "rustls-native-certs"] +rustls-webpki = ["rustls-base", "webpki-roots"] default = ["tls"] diff --git a/README.md b/README.md index 06c6396..30055bd 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,19 @@ async fn main() -> Result<(), Box> { } ``` +## Features + +`hyper-proxy` exposes three main Cargo features, to configure which TLS implementation it uses to +connect to a proxy. It can also be configured without TLS support, by compiling without default +features entirely. The supported list of configurations is: + +1. No TLS support (`default-features = false`) +2. TLS support via `native-tls` to link against the operating system's native TLS implementation + (default) +3. TLS support via `rustls` (`default-features = false, features = ["rustls"]`) +4. TLS support via `rustls`, using a statically-compiled set of CA certificates to bypass the + operating system's default store (`default-features = false, features = ["rustls-webpki"]`) + ## Credits Large part of the code comes from [reqwest][2]. diff --git a/src/lib.rs b/src/lib.rs index 2d171ea..4a0f26a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,9 @@ //! let mut proxy = Proxy::new(Intercept::All, proxy_uri); //! proxy.set_authorization(Credentials::basic("John Doe", "Agent1234").unwrap()); //! let connector = HttpConnector::new(); -//! # #[cfg(not(any(feature = "tls", feature = "rustls")))] +//! # #[cfg(not(any(feature = "tls", feature = "rustls-base")))] //! # let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy); -//! # #[cfg(any(feature = "tls", feature = "rustls"))] +//! # #[cfg(any(feature = "tls", feature = "rustls-base"))] //! let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap(); //! proxy_connector //! }; @@ -71,12 +71,12 @@ use tokio::io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls")] use native_tls::TlsConnector as NativeTlsConnector; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] use tokio_rustls::TlsConnector; #[cfg(feature = "tls")] use tokio_tls::TlsConnector; use typed_headers::{Authorization, Credentials, HeaderMapExt, ProxyAuthorization}; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] use webpki::DNSNameRef; type BoxError = Box; @@ -229,10 +229,10 @@ pub struct ProxyConnector { #[cfg(feature = "tls")] tls: Option, - #[cfg(feature = "rustls")] + #[cfg(feature = "rustls-base")] tls: Option, - #[cfg(not(any(feature = "tls", feature = "rustls")))] + #[cfg(not(any(feature = "tls", feature = "rustls-base")))] tls: Option<()>, } @@ -268,11 +268,21 @@ impl ProxyConnector { } /// Create a new secured Proxies - #[cfg(feature = "rustls")] + #[cfg(feature = "rustls-base")] pub fn new(connector: C) -> Result { let mut config = tokio_rustls::rustls::ClientConfig::new(); - config.root_store = rustls_native_certs::load_native_certs()?; + #[cfg(feature = "rustls")] + { + config.root_store = rustls_native_certs::load_native_certs()?; + } + + #[cfg(feature = "rustls-webpki")] + { + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + } let cfg = Arc::new(config); let tls = TlsConnector::from(cfg); @@ -294,7 +304,7 @@ impl ProxyConnector { } /// Create a proxy connector and attach a particular proxy - #[cfg(any(feature = "tls", feature = "rustls"))] + #[cfg(any(feature = "tls", feature = "rustls-base"))] pub fn from_proxy(connector: C, proxy: Proxy) -> Result { let mut c = ProxyConnector::new(connector)?; c.proxies.push(proxy); @@ -324,7 +334,7 @@ impl ProxyConnector { } /// Set or unset tls when tunneling - #[cfg(any(feature = "rustls"))] + #[cfg(any(feature = "rustls-base"))] pub fn set_tls(&mut self, tls: Option) { self.tls = tls; } @@ -415,7 +425,7 @@ where Ok(ProxyStream::Secured(secure_stream)) } - #[cfg(feature = "rustls")] + #[cfg(feature = "rustls-base")] Some(tls) => { let dnsref = mtry!(DNSNameRef::try_from_ascii_str(&host).map_err(io_err)); @@ -426,7 +436,7 @@ where Ok(ProxyStream::Secured(secure_stream)) } - #[cfg(not(any(feature = "tls", feature = "rustls")))] + #[cfg(not(any(feature = "tls", feature = "rustls-base")))] Some(_) => panic!("hyper-proxy was not built with TLS support"), None => Ok(ProxyStream::Regular(tunnel_stream)), diff --git a/src/stream.rs b/src/stream.rs index f40717e..f6269c5 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -5,7 +5,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite}; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] use tokio_rustls::client::TlsStream as RustlsStream; #[cfg(feature = "tls")] @@ -13,14 +13,14 @@ use tokio_tls::TlsStream; use hyper::client::connect::{Connected, Connection}; -#[cfg(feature = "rustls")] +#[cfg(feature = "rustls-base")] type TlsStream = RustlsStream; /// A Proxy Stream wrapper pub enum ProxyStream { NoProxy(R), Regular(R), - #[cfg(any(feature = "tls", feature = "rustls"))] + #[cfg(any(feature = "tls", feature = "rustls-base"))] Secured(TlsStream), } @@ -29,7 +29,7 @@ macro_rules! match_fn_pinned { match $self.get_mut() { ProxyStream::NoProxy(s) => Pin::new(s).$fn($ctx, $buf), ProxyStream::Regular(s) => Pin::new(s).$fn($ctx, $buf), - #[cfg(any(feature = "tls", feature = "rustls"))] + #[cfg(any(feature = "tls", feature = "rustls-base"))] ProxyStream::Secured(s) => Pin::new(s).$fn($ctx, $buf), } }; @@ -38,7 +38,7 @@ macro_rules! match_fn_pinned { match $self.get_mut() { ProxyStream::NoProxy(s) => Pin::new(s).$fn($ctx), ProxyStream::Regular(s) => Pin::new(s).$fn($ctx), - #[cfg(any(feature = "tls", feature = "rustls"))] + #[cfg(any(feature = "tls", feature = "rustls-base"))] ProxyStream::Secured(s) => Pin::new(s).$fn($ctx), } }; @@ -51,7 +51,7 @@ impl AsyncRead for ProxyStream { ProxyStream::Regular(ref s) => s.prepare_uninitialized_buffer(buf), - #[cfg(any(feature = "tls", feature = "rustls"))] + #[cfg(any(feature = "tls", feature = "rustls-base"))] ProxyStream::Secured(ref s) => s.prepare_uninitialized_buffer(buf), } } @@ -111,7 +111,7 @@ impl Connection for ProxyStream< #[cfg(feature = "tls")] ProxyStream::Secured(s) => s.get_ref().connected().proxy(true), - #[cfg(feature = "rustls")] + #[cfg(feature = "rustls-base")] ProxyStream::Secured(s) => s.get_ref().0.connected().proxy(true), } }