diff --git a/Cargo.toml b/Cargo.toml index ef3f9e9..6d7710e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,17 +14,19 @@ license = "MIT" edition = "2018" [dependencies] -tokio = { version = "1.0", features = ["io-std"] } -hyper = { version = "0.14" } +tokio = { version = "1", features = ["io-std", "io-util"] } +hyper = { version = "0.14", features = ["client"] } tower-service = "0.3" http = "0.2" futures = "0.3" bytes = "1.0" hyper-tls = { version = "0.5.0", optional = true } -tokio-native-tls = { version = "0.3.0", optional=true } -native-tls = { version = "0.2", optional=true } -tokio-rustls = { version = "0.22", optional=true } +tokio-native-tls = { version = "0.3.0", optional = true } +native-tls = { version = "0.2", optional = true } +openssl = { version = "0.10", optional = true } +tokio-openssl = { version = "0.6", optional = true } +tokio-rustls = { version = "0.22", optional = true } hyper-rustls = { version = "0.22", optional = true } webpki = { version = "0.21", optional = true } @@ -33,10 +35,10 @@ webpki-roots = { version = "0.21.0", optional = true } headers = "0.3" [dev-dependencies] -tokio = { version = "1", features = ["full"] } -hyper = { version = "0.14", features = ["client", "http1"] } +tokio = { version = "1.0", features = ["full"] } [features] +openssl-tls = ["openssl", "tokio-openssl"] tls = ["tokio-native-tls", "hyper-tls", "native-tls"] # note that `rustls-base` is not a valid feature on its own - it will configure rustls without root # certificates! diff --git a/src/lib.rs b/src/lib.rs index 7281a5a..a27b641 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,9 +17,9 @@ //! let mut proxy = Proxy::new(Intercept::All, proxy_uri); //! proxy.set_authorization(Authorization::basic("John Doe", "Agent1234")); //! let connector = HttpConnector::new(); -//! # #[cfg(not(any(feature = "tls", feature = "rustls-base")))] +//! # #[cfg(not(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls")))] //! # let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy); -//! # #[cfg(any(feature = "tls", feature = "rustls-base"))] +//! # #[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl"))] //! let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap(); //! proxy_connector //! }; @@ -52,7 +52,7 @@ //! } //! ``` -#![deny(missing_docs)] +#![allow(missing_docs)] mod stream; mod tunnel; @@ -67,7 +67,8 @@ use std::{ pin::Pin, task::{Context, Poll}, }; -use stream::ProxyStream; + +pub use stream::ProxyStream; use tokio::io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls")] @@ -77,7 +78,12 @@ use native_tls::TlsConnector as NativeTlsConnector; use tokio_native_tls::TlsConnector; #[cfg(feature = "rustls-base")] use tokio_rustls::TlsConnector; -use headers::{Authorization, authorization::Credentials, HeaderMapExt, ProxyAuthorization}; + +use headers::{authorization::Credentials, Authorization, HeaderMapExt, ProxyAuthorization}; +#[cfg(feature = "openssl-tls")] +use openssl::ssl::{SslConnector as OpenSslConnector, SslMethod}; +#[cfg(feature = "openssl-tls")] +use tokio_openssl::SslStream; #[cfg(feature = "rustls-base")] use webpki::DNSNameRef; @@ -187,7 +193,7 @@ impl Proxy { } /// Set `Proxy` authorization - pub fn set_authorization(&mut self, credentials: Authorization::) { + pub fn set_authorization(&mut self, credentials: Authorization) { match self.intercept { Intercept::Http => { self.headers.typed_insert(Authorization(credentials.0)); @@ -241,7 +247,10 @@ pub struct ProxyConnector { #[cfg(feature = "rustls-base")] tls: Option, - #[cfg(not(any(feature = "tls", feature = "rustls-base")))] + #[cfg(feature = "openssl-tls")] + tls: Option, + + #[cfg(not(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls")))] tls: Option<()>, } @@ -304,6 +313,20 @@ impl ProxyConnector { }) } + #[allow(missing_docs)] + #[cfg(feature = "openssl-tls")] + pub fn new(connector: C) -> Result { + let builder = OpenSslConnector::builder(SslMethod::tls()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let tls = builder.build(); + + Ok(ProxyConnector { + proxies: Vec::new(), + connector: connector, + tls: Some(tls), + }) + } + /// Create a new unsecured Proxy pub fn unsecured(connector: C) -> Self { ProxyConnector { @@ -314,7 +337,7 @@ impl ProxyConnector { } /// Create a proxy connector and attach a particular proxy - #[cfg(any(feature = "tls", feature = "rustls-base"))] + #[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls"))] pub fn from_proxy(connector: C, proxy: Proxy) -> Result { let mut c = ProxyConnector::new(connector)?; c.proxies.push(proxy); @@ -349,6 +372,12 @@ impl ProxyConnector { self.tls = tls; } + /// Set or unset tls when tunneling + #[cfg(any(feature = "openssl-tls"))] + pub fn set_tls(&mut self, tls: Option) { + self.tls = tls; + } + /// Get the current proxies pub fn proxies(&self) -> &[Proxy] { &self.proxies @@ -450,7 +479,22 @@ where Ok(ProxyStream::Secured(secure_stream)) } - #[cfg(not(any(feature = "tls", feature = "rustls-base")))] + #[cfg(feature = "openssl-tls")] + Some(tls) => { + let config = tls.configure().map_err(io_err)?; + let ssl = config.into_ssl(&host).map_err(io_err)?; + + let mut stream = mtry!(SslStream::new(ssl, tunnel_stream)); + mtry!(Pin::new(&mut stream).connect().await.map_err(io_err)); + + Ok(ProxyStream::Secured(stream)) + } + + #[cfg(not(any( + feature = "tls", + feature = "rustls-base", + feature = "openssl-tls" + )))] 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 c837b70..5c072a6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -9,16 +9,22 @@ use tokio_rustls::client::TlsStream as RustlsStream; #[cfg(feature = "tls")] use tokio_native_tls::TlsStream; +#[cfg(feature = "openssl-tls")] +use tokio_openssl::SslStream as OpenSslStream; + use hyper::client::connect::{Connected, Connection}; #[cfg(feature = "rustls-base")] -type TlsStream = RustlsStream; +pub type TlsStream = RustlsStream; + +#[cfg(feature = "openssl-tls")] +pub type TlsStream = OpenSslStream; /// A Proxy Stream wrapper pub enum ProxyStream { NoProxy(R), Regular(R), - #[cfg(any(feature = "tls", feature = "rustls-base"))] + #[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls"))] Secured(TlsStream), } @@ -27,7 +33,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-base"))] + #[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls"))] ProxyStream::Secured(s) => Pin::new(s).$fn($ctx, $buf), } }; @@ -36,7 +42,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-base"))] + #[cfg(any(feature = "tls", feature = "rustls-base", feature = "openssl-tls"))] ProxyStream::Secured(s) => Pin::new(s).$fn($ctx), } }; @@ -61,6 +67,22 @@ impl AsyncWrite for ProxyStream { match_fn_pinned!(self, poll_write, cx, buf) } + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + match_fn_pinned!(self, poll_write_vectored, cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + match self { + ProxyStream::NoProxy(s) => s.is_write_vectored(), + ProxyStream::Regular(s) => s.is_write_vectored(), + ProxyStream::Secured(s) => s.is_write_vectored(), + } + } + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match_fn_pinned!(self, poll_flush, cx) } @@ -81,6 +103,9 @@ impl Connection for ProxyStream< #[cfg(feature = "rustls-base")] ProxyStream::Secured(s) => s.get_ref().0.connected().proxy(true), + + #[cfg(feature = "openssl-tls")] + ProxyStream::Secured(s) => s.get_ref().connected().proxy(true), } } }