From cc2fa986a63daed886269be80fe1c9952d0df97b Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Sun, 1 Sep 2019 01:37:19 -0400 Subject: [PATCH] Add both rustls and openssl client tls implementation --- tonic-examples/src/helloworld/client.rs | 2 +- tonic-examples/src/routeguide/client.rs | 2 +- tonic-interop/src/bin/client.rs | 4 +- tonic-interop/src/client.rs | 14 ++- tonic/Cargo.toml | 14 ++- tonic/src/transport/client.rs | 116 ++++++++++++++++------ tonic/src/transport/mod.rs | 6 +- tonic/src/transport/openssl.rs | 21 ++-- tonic/src/transport/{tls.rs => rustls.rs} | 11 +- 9 files changed, 128 insertions(+), 62 deletions(-) rename tonic/src/transport/{tls.rs => rustls.rs} (89%) diff --git a/tonic-examples/src/helloworld/client.rs b/tonic-examples/src/helloworld/client.rs index 42cdb77..661f8a5 100644 --- a/tonic-examples/src/helloworld/client.rs +++ b/tonic-examples/src/helloworld/client.rs @@ -9,7 +9,7 @@ pub mod hello_world { async fn main() -> Result<(), Box> { let origin = http::Uri::from_static("http://[::1]:50051"); - let svc = Client::connect(origin)?; + let svc = Client::builder().build(origin)?; let mut client = hello_world::GreeterClient::new(svc); diff --git a/tonic-examples/src/routeguide/client.rs b/tonic-examples/src/routeguide/client.rs index c953a36..8f846f7 100644 --- a/tonic-examples/src/routeguide/client.rs +++ b/tonic-examples/src/routeguide/client.rs @@ -13,7 +13,7 @@ mod route_guide { async fn main() -> Result<(), Box> { let origin = http::Uri::from_static("http://[::1]:10000"); - let svc = Client::connect(origin)?; + let svc = Client::builder().build(origin)?; let mut client = route_guide::RouteGuideClient::new(svc); let start = Instant::now(); diff --git a/tonic-interop/src/bin/client.rs b/tonic-interop/src/bin/client.rs index 3b0069f..76f4b31 100644 --- a/tonic-interop/src/bin/client.rs +++ b/tonic-interop/src/bin/client.rs @@ -22,8 +22,8 @@ async fn main() -> Result<(), Box> { let test_cases = matches.test_case; - let addr = "localhost:8080"; - let origin = http::Uri::from_shared(format!("https://{}", addr).into()).unwrap(); + let addr = "localhost:10000"; + let origin = http::Uri::from_shared(format!("http://{}", addr).into()).unwrap(); let mut client = client::create(origin.clone()).await?; let mut unimplemented_client = client::create_unimplemented(origin).await?; diff --git a/tonic-interop/src/client.rs b/tonic-interop/src/client.rs index b8f09c7..77d4d50 100644 --- a/tonic-interop/src/client.rs +++ b/tonic-interop/src/client.rs @@ -22,7 +22,12 @@ const SPECIAL_TEST_STATUS_MESSAGE: &'static str = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"; pub async fn create(origin: http::Uri) -> Result> { - let svc = Client::connect_with_tls(origin, "tonic-interop/data/ca.pem").await?; + let ca = tokio::fs::read("tonic-interop/data/ca.pem").await?; + + let svc = Client::builder() + .tls(ca) + .tls_override_domain("foo.test.google.fr") + .build(origin)?; Ok(TestServiceClient::new(svc)) } @@ -30,7 +35,12 @@ pub async fn create(origin: http::Uri) -> Result Result> { - let svc = Client::connect(origin)?; + let ca = tokio::fs::read("tonic-interop/data/ca.pem").await?; + + let svc = Client::builder() + .tls(ca) + .tls_override_domain("foo.test.google.fr") + .build(origin)?; Ok(UnimplementedServiceClient::new(svc)) } diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index f75aca9..5c1daa5 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -26,12 +26,16 @@ pin-project = "0.4.0-alpha.2" hyper = { git = "https://github.com/hyperium/hyper", optional = true} tokio = { version = "=0.2.0-alpha.4", default-features = false, features = ["tcp"], optional = true } tower-make = "=0.1.0-alpha.2" -tokio-rustls = { path = "../../tokio-rustls", optional = true } -tokio-openssl = "=0.4.0-alpha.4" -openssl = "*" tower-reconnect = { path = "../../tower/tower-reconnect", optional = true } tower-buffer = { path = "../../tower/tower-buffer", optional = true } +# openssl +tokio-openssl = { version = "=0.4.0-alpha.4", optional = true } +openssl = { version = "0.10", optional = true } + +# rustls +tokio-rustls = { path = "../../tokio-rustls", optional = true } + [features] default = ["transport"] transport = [ @@ -39,5 +43,7 @@ transport = [ "tower-reconnect", "tower-buffer", "tokio", - "tokio-rustls", + "openssl-1", ] +openssl-1 = ["openssl", "tokio-openssl"] +rustls = ["tokio-rustls"] diff --git a/tonic/src/transport/client.rs b/tonic/src/transport/client.rs index ac95020..97cbf9b 100644 --- a/tonic/src/transport/client.rs +++ b/tonic/src/transport/client.rs @@ -1,16 +1,14 @@ -use super::tls::TlsConnector; use crate::{ body::BoxBody, service::{AddOrigin, BoxService, GrpcService}, }; use futures_util::try_future::{MapErr, TryFutureExt}; use http::Uri; -use hyper::client::conn::Builder; +use hyper::client::conn; use hyper::client::connect::HttpConnector; use hyper::client::service::Connect; use hyper::{Request, Response}; use std::future::Future; -use std::path::Path; use std::pin::Pin; use std::task::{Context, Poll}; use tower_buffer::{future::ResponseFuture, Buffer}; @@ -33,34 +31,8 @@ pub struct Client { } impl Client { - pub fn connect(addr: Uri) -> Result { - let settings = Builder::new().http2_only(true).clone(); - let maker = Connect::new(HttpConnector::new(), settings); - let svc = tower_reconnect::Reconnect::new(maker, addr.clone()); - - let svc = AddOrigin::new(svc, addr); - let svc = BoxService::new(svc); - - let svc = Buffer::new(Box::new(svc) as Inner, 100); - - Ok(Self { svc }) - } - - pub async fn connect_with_tls>(addr: Uri, ca: P) -> Result { - let settings = Builder::new().http2_only(true).clone(); - - // let tls_connector = TlsConnector::load(ca).await?; - let tls_connector = super::openssl::TlsConnector::load(ca).await?; - - let maker = Connect::new(tls_connector, settings); - let svc = tower_reconnect::Reconnect::new(maker, addr.clone()); - - let svc = AddOrigin::new(svc, addr); - let svc = BoxService::new(svc); - - let svc = Buffer::new(Box::new(svc) as Inner, 100); - - Ok(Self { svc }) + pub fn builder() -> Builder { + Builder::new() } } @@ -83,3 +55,85 @@ impl GrpcService for Client { .map_err(|e| super::Error::from((super::ErrorKind::Client, e))) } } + +#[derive(Debug)] +pub struct Builder { + ca: Option>, + override_domain: Option, + buffer_size: usize, +} + +impl Builder { + fn new() -> Self { + Self { + ca: None, + override_domain: None, + buffer_size: 1024, + } + } + + #[cfg(any(feature = "openssl-1", feature = "rustls"))] + pub fn tls(&mut self, ca: Vec) -> &mut Self { + self.ca = Some(ca); + self + } + + #[cfg(any(feature = "openssl-1", feature = "rustls"))] + pub fn tls_override_domain>(&mut self, domain: D) -> &mut Self { + self.override_domain = Some(domain.as_ref().into()); + self + } + + pub fn buffer(&mut self, size: usize) -> &mut Self { + self.buffer_size = size; + self + } + + pub fn build(&self, uri: T) -> Result + where + Uri: http::HttpTryFrom, + { + let uri: Uri = match http::HttpTryFrom::try_from(uri) { + Ok(u) => u, + Err(e) => panic!("Invalid uri: {}", e.into()), + }; + + let settings = conn::Builder::new().http2_only(true).clone(); + + let svc = if let Some(ca) = &self.ca { + let domain = self + .override_domain + .clone() + .unwrap_or_else(|| uri.to_string()); + + #[cfg(not(any(feature = "openssl-1", feature = "rustls")))] + panic!("tls configured when no tls implementation feature was selected!"); + + #[cfg(feature = "openssl-1")] + let connector = super::openssl::TlsConnector::new(ca.clone(), domain)?; + + #[cfg(feature = "rustls")] + #[cfg(not(feature = "openssl-1"))] + let connector = super::openssl::TlsConnector::new(ca.clone(), domain)?; + + let maker = Connect::new(connector, settings); + let svc = tower_reconnect::Reconnect::new(maker, uri.clone()); + + let svc = AddOrigin::new(svc, uri); + let svc = BoxService::new(svc); + Buffer::new(Box::new(svc) as Inner, 100) + } else { + let connector = HttpConnector::new(); + let maker = Connect::new(connector, settings); + let svc = tower_reconnect::Reconnect::new(maker, uri.clone()); + + let svc = AddOrigin::new(svc, uri); + let svc = BoxService::new(svc); + Buffer::new(Box::new(svc) as Inner, 100) + }; + // let connector = super::rustls::TlsConnector::load(ca).await?; + // let connector = super::openssl::TlsConnector::load(ca).await?; + + Ok(Client { svc }) + } +} diff --git a/tonic/src/transport/mod.rs b/tonic/src/transport/mod.rs index 7d5b22b..b9e6b6d 100644 --- a/tonic/src/transport/mod.rs +++ b/tonic/src/transport/mod.rs @@ -1,6 +1,8 @@ mod client; +#[cfg(feature = "openssl-1")] mod openssl; -mod tls; +#[cfg(feature = "rustls")] +mod rustls; pub use self::client::Client; @@ -14,7 +16,7 @@ pub struct Error { #[derive(Debug)] pub(crate) enum ErrorKind { Client, - UnableToNegotiateH2, // Server, + // Server, } impl From for Error { diff --git a/tonic/src/transport/openssl.rs b/tonic/src/transport/openssl.rs index e5b522b..5661f81 100644 --- a/tonic/src/transport/openssl.rs +++ b/tonic/src/transport/openssl.rs @@ -1,33 +1,33 @@ use http::Uri; use hyper::client::connect::HttpConnector; -use openssl::ssl::{ConnectConfiguration, SslConnector, SslMethod}; +use openssl::ssl::{SslConnector, SslMethod}; +use openssl::x509::X509; use std::{ future::Future, - path::Path, pin::Pin, - sync::Arc, task::{Context, Poll}, }; -use tokio::{fs, net::TcpStream}; +use tokio::net::TcpStream; use tokio_openssl::{connect, SslStream}; use tower_make::MakeConnection; use tower_service::Service; -const ALPN_H2: &str = "h2"; - #[derive(Clone)] pub struct TlsConnector { http: HttpConnector, config: SslConnector, + domain: String, } impl TlsConnector { - pub async fn load>(ca: P) -> Result { + pub fn new(ca: Vec, domain: String) -> Result { let mut config = SslConnector::builder(SslMethod::tls()).unwrap(); - config.set_alpn_protos(ALPN_H2.as_bytes()).unwrap(); + config.set_alpn_protos(b"\x02h2").unwrap(); - config.set_ca_file(ca).unwrap(); + let ca = X509::from_pem(&ca[..]).unwrap(); + + config.cert_store_mut().add_cert(ca).unwrap(); let config = config.build(); @@ -37,6 +37,7 @@ impl TlsConnector { Ok(Self { http, config, + domain, }) } } @@ -56,10 +57,10 @@ impl Service for TlsConnector { fn call(&mut self, uri: Uri) -> Self::Future { let config = self.config.configure().unwrap(); let tcp = self.http.make_connection(uri.clone()); + let domain = self.domain.clone(); let fut = async move { let io = tcp.await.unwrap(); - let domain = "foo.test.google.fr"; let tls = connect(config, &domain, io).await.unwrap(); Ok(tls) }; diff --git a/tonic/src/transport/tls.rs b/tonic/src/transport/rustls.rs similarity index 89% rename from tonic/src/transport/tls.rs rename to tonic/src/transport/rustls.rs index b950ec8..4f6d022 100644 --- a/tonic/src/transport/tls.rs +++ b/tonic/src/transport/rustls.rs @@ -17,7 +17,7 @@ use tokio_rustls::{ use tower_make::MakeConnection; use tower_service::Service; -const ALPN_H2: &str = "h2"; +const ALPN_H2: &str = "\x02h2"; #[derive(Clone)] pub struct TlsConnector { @@ -26,14 +26,7 @@ pub struct TlsConnector { } impl TlsConnector { - pub async fn load>(ca: P) -> Result { - let pem = fs::read(ca) - .await - .map_err(|e| super::Error::from((super::ErrorKind::Client, e.into())))?; - Ok(TlsConnector::new(pem)) - } - - fn new(ca: Vec) -> Self { + pub fn new(ca: Vec, domain: String) -> Self { let mut buf = std::io::Cursor::new(ca); let mut config = ClientConfig::new();