From 3eacfa60e9738a0a48f1e14d5bb538284b58b1db Mon Sep 17 00:00:00 2001 From: Rajeev Massand Date: Fri, 26 Feb 2021 05:20:39 +0000 Subject: [PATCH 1/2] tokio-openssl support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding support for performing TLS handshakes using OpenSSL engines, which are supported by the openssl crate’s TLS connector. OpenSSL engines are commonly used when crypto operations (during TLS handshakes) require access to private keys stored in HSMs. A new feature 'openssl-tls' binds the proxy connector and underlying stream operations to the openssl (and tokio-openssl) crate. --- Cargo.toml | 16 +++++++------ src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++++++-------- src/stream.rs | 33 +++++++++++++++++++++++---- 3 files changed, 91 insertions(+), 20 deletions(-) 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..9b08bca 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")))] //! # 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), } } } From 3341a3073f4d7b67a5bdd34ff5f9dcc829cbfeb5 Mon Sep 17 00:00:00 2001 From: Rajeev Massand Date: Fri, 26 Feb 2021 05:29:46 +0000 Subject: [PATCH 2/2] minor doc fix --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9b08bca..a27b641 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ //! 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", feature = "openssl")))] +//! # #[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", feature = "openssl"))] //! let proxy_connector = ProxyConnector::from_proxy(connector, proxy).unwrap();