From d12d604e380b8f1ee8cc9e22fd218ce3d283aa4e Mon Sep 17 00:00:00 2001 From: Corentin Henry Date: Mon, 22 May 2017 07:52:24 -0700 Subject: [PATCH 1/3] tmp: use upstream git repo for hyper-native-tls --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f19ce77..b248964 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ categories = ["web-programming::http-client"] [dependencies] hyper = "0.10.2" -hyper-native-tls = "0.2" +hyper-native-tls = { git = "https://github.com/sfackler/hyper-native-tls" } log = "0.3" serde = "1.0" serde_json = "1.0" From 4b2eda7d293b64248260d510e75f3093cd84f107 Mon Sep 17 00:00:00 2001 From: Corentin Henry Date: Mon, 22 May 2017 08:06:01 -0700 Subject: [PATCH 2/3] add custom certificates & disabling hostname verif --- src/client.rs | 138 +++++++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 2 +- 2 files changed, 121 insertions(+), 19 deletions(-) diff --git a/src/client.rs b/src/client.rs index 400caba..adb58bb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,6 +11,8 @@ use hyper::status::StatusCode; use hyper::version::HttpVersion; use hyper::{Url}; +use hyper_native_tls::{self, NativeTlsClient, native_tls}; + use serde::Serialize; use serde_json; use serde_urlencoded; @@ -47,14 +49,101 @@ pub struct Client { inner: Arc, } -impl Client { - /// Constructs a new `Client`. - pub fn new() -> ::Result { - let mut client = try_!(new_hyper_client()); - client.set_redirect_policy(::hyper::client::RedirectPolicy::FollowNone); +/// Represent an X509 certificate. +pub struct Certificate(hyper_native_tls::Certificate); + +impl Certificate { + /// Create a `Certificate` from a binary DER encoded certificate + /// + /// # Examples + /// + /// ``` + /// # use std::fs::File; + /// # use std::io::Read; + /// # fn cert() -> Result<(), Box> { + /// let mut buf = Vec::new(); + /// File::open("my_cert.der")? + /// .read_to_end(&mut buf)?; + /// let cert = reqwest::Certificate::from_der(&buf)?; + /// # drop(cert); + /// # Ok(()) + /// # } + /// ``` + pub fn from_der(der: &[u8]) -> ::Result { + let inner = try_!( + hyper_native_tls::Certificate::from_der(der) + .map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); + Ok(Certificate(inner)) + } +} + +/// A `ClientBuilder` can be used to create a `Client` with a custom TLS configuration: +/// +/// - with hostname verification disabled +/// - with one or multiple custom certificates +/// +/// # Examples +/// +/// ``` +/// # use std::fs::File; +/// # use std::io::Read; +/// # fn build_client() -> Result<(), Box> { +/// // get a client builder +/// let mut client_builder = reqwest::ClientBuilder::new()?; +/// +/// // read a local binary DER encoded certificate +/// let mut buf = Vec::new(); +/// File::open("my-cert.der")?.read_to_end(&mut buf)?; +/// +/// // create a certificate +/// let cert = reqwest::Certificate::from_der(&buf)?; +/// +/// // add the certificate +/// client_builder.add_root_certificate(cert)?; +/// +/// // create the actual client +/// let client = client_builder.build()?; +/// # drop(client); +/// # Ok(()) +/// # } +/// ``` +pub struct ClientBuilder { + inner: native_tls::TlsConnectorBuilder, + hostname_verification: bool, +} + +impl ClientBuilder { + /// Constructs a new `ClientBuilder` + pub fn new() -> ::Result { + let tls_connector_builder = try_!( + native_tls::TlsConnector::builder() + .map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); + Ok(ClientBuilder { + inner: tls_connector_builder, + hostname_verification: true, + }) + } + + /// Returns a `Client` that uses this `ClientBuilder` configuration. + pub fn build(self) -> ::Result { + + let tls_connector = try_!( + self.inner.build().map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); + let mut tls_client = NativeTlsClient::from(tls_connector); + if ! self.hostname_verification { + tls_client.danger_disable_hostname_verification(true); + } + + let mut hyper_client = ::hyper::Client::with_connector( + ::hyper::client::Pool::with_connector( + Default::default(), + ::hyper::net::HttpsConnector::new(tls_client))); + + hyper_client.set_redirect_policy(::hyper::client::RedirectPolicy::FollowNone); + Ok(Client { inner: Arc::new(ClientRef { - hyper: RwLock::new(client), + hyper: RwLock::new(hyper_client), redirect_policy: Mutex::new(RedirectPolicy::default()), auto_referer: AtomicBool::new(true), auto_ungzip: AtomicBool::new(true), @@ -62,6 +151,31 @@ impl Client { }) } + /// Add a custom root certificate. This can be used to connect to a server that has a + /// self-signed certificate for example. + pub fn add_root_certificate(&mut self, cert: Certificate) -> ::Result<&mut ClientBuilder> { + try_!(self.inner.add_root_certificate(cert.0) + .map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); + Ok(self) + } + + /// Disable hostname verification + pub fn danger_disable_hostname_verification(&mut self) { + self.hostname_verification = false; + } + + /// Enable hostname verification + pub fn enable_hostname_verification(&mut self) { + self.hostname_verification = true; + } +} + +impl Client { + /// Constructs a new `Client`. + pub fn new() -> ::Result { + try_!(ClientBuilder::new()).build() + } + /// Enable auto gzip decompression by checking the ContentEncoding response header. /// /// Default is enabled. @@ -155,18 +269,6 @@ struct ClientRef { auto_ungzip: AtomicBool, } -fn new_hyper_client() -> ::Result<::hyper::Client> { - use hyper_native_tls::NativeTlsClient; - Ok(::hyper::Client::with_connector( - ::hyper::client::Pool::with_connector( - Default::default(), - ::hyper::net::HttpsConnector::new( - try_!(NativeTlsClient::new() - .map_err(|e| ::hyper::Error::Ssl(Box::new(e))))) - ) - )) -} - /// A builder to construct the properties of a `Request`. pub struct RequestBuilder { client: Arc, diff --git a/src/lib.rs b/src/lib.rs index 6a2c4d2..3b25fd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,7 +154,7 @@ pub use hyper::version::HttpVersion; pub use hyper::Url; pub use url::ParseError as UrlError; -pub use self::client::{Client, RequestBuilder}; +pub use self::client::{Certificate, Client, ClientBuilder, RequestBuilder}; pub use self::error::{Error, Result}; pub use self::body::Body; pub use self::redirect::RedirectPolicy; From 82a6eb49395b67bc8a4eb4c36bd3766051b364cb Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Wed, 31 May 2017 11:33:39 -0700 Subject: [PATCH 3/3] update ClientBuilder to match builder pattern --- Cargo.toml | 2 +- src/client.rs | 80 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b248964..ce325b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ categories = ["web-programming::http-client"] [dependencies] hyper = "0.10.2" -hyper-native-tls = { git = "https://github.com/sfackler/hyper-native-tls" } +hyper-native-tls = "0.2.3" log = "0.3" serde = "1.0" serde_json = "1.0" diff --git a/src/client.rs b/src/client.rs index adb58bb..56a10b0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,7 @@ use hyper::status::StatusCode; use hyper::version::HttpVersion; use hyper::{Url}; -use hyper_native_tls::{self, NativeTlsClient, native_tls}; +use hyper_native_tls::{NativeTlsClient, native_tls}; use serde::Serialize; use serde_json; @@ -50,7 +50,7 @@ pub struct Client { } /// Represent an X509 certificate. -pub struct Certificate(hyper_native_tls::Certificate); +pub struct Certificate(native_tls::Certificate); impl Certificate { /// Create a `Certificate` from a binary DER encoded certificate @@ -69,15 +69,19 @@ impl Certificate { /// # Ok(()) /// # } /// ``` + /// + /// # Errors + /// + /// If the provided buffer is not valid DER, an error will be returned. pub fn from_der(der: &[u8]) -> ::Result { let inner = try_!( - hyper_native_tls::Certificate::from_der(der) + native_tls::Certificate::from_der(der) .map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); Ok(Certificate(inner)) } } -/// A `ClientBuilder` can be used to create a `Client` with a custom TLS configuration: +/// A `ClientBuilder` can be used to create a `Client` with custom configuration: /// /// - with hostname verification disabled /// - with one or multiple custom certificates @@ -88,9 +92,6 @@ impl Certificate { /// # use std::fs::File; /// # use std::io::Read; /// # fn build_client() -> Result<(), Box> { -/// // get a client builder -/// let mut client_builder = reqwest::ClientBuilder::new()?; -/// /// // read a local binary DER encoded certificate /// let mut buf = Vec::new(); /// File::open("my-cert.der")?.read_to_end(&mut buf)?; @@ -98,18 +99,21 @@ impl Certificate { /// // create a certificate /// let cert = reqwest::Certificate::from_der(&buf)?; /// -/// // add the certificate -/// client_builder.add_root_certificate(cert)?; -/// -/// // create the actual client -/// let client = client_builder.build()?; +/// // get a client builder +/// let client = reqwest::ClientBuilder::new()? +/// .add_root_certificate(cert)? +/// .build()?; /// # drop(client); /// # Ok(()) /// # } /// ``` pub struct ClientBuilder { - inner: native_tls::TlsConnectorBuilder, + config: Option, +} + +struct Config { hostname_verification: bool, + tls: native_tls::TlsConnectorBuilder, } impl ClientBuilder { @@ -119,18 +123,26 @@ impl ClientBuilder { native_tls::TlsConnector::builder() .map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); Ok(ClientBuilder { - inner: tls_connector_builder, - hostname_verification: true, + config: Some(Config { + hostname_verification: true, + tls: tls_connector_builder, + }) }) } /// Returns a `Client` that uses this `ClientBuilder` configuration. - pub fn build(self) -> ::Result { + /// + /// # Note + /// + /// This consumes the internal state of the builder. Trying to use this + /// builder again after calling `build` will panic. + pub fn build(&mut self) -> ::Result { + let config = self.take_config(); let tls_connector = try_!( - self.inner.build().map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); + config.tls.build().map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); let mut tls_client = NativeTlsClient::from(tls_connector); - if ! self.hostname_verification { + if !config.hostname_verification { tls_client.danger_disable_hostname_verification(true); } @@ -151,22 +163,40 @@ impl ClientBuilder { }) } - /// Add a custom root certificate. This can be used to connect to a server that has a - /// self-signed certificate for example. + /// Add a custom root certificate. + /// + /// This can be used to connect to a server that has a self-signed + /// certificate for example. pub fn add_root_certificate(&mut self, cert: Certificate) -> ::Result<&mut ClientBuilder> { - try_!(self.inner.add_root_certificate(cert.0) + try_!(self.config_mut().tls.add_root_certificate(cert.0) .map_err(|e| ::hyper::Error::Ssl(Box::new(e)))); Ok(self) } - /// Disable hostname verification + /// Disable hostname verification. + /// + /// # Warning + /// + /// You should think very carefully before you use this method. If + /// 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. pub fn danger_disable_hostname_verification(&mut self) { - self.hostname_verification = false; + self.config_mut().hostname_verification = false; } - /// Enable hostname verification + /// Enable hostname verification. pub fn enable_hostname_verification(&mut self) { - self.hostname_verification = true; + self.config_mut().hostname_verification = true; + } + + // private + fn config_mut(&mut self) -> &mut Config { + self.config.as_mut().expect("ClientBuilder cannot be reused after building a Client") + } + + fn take_config(&mut self) -> Config { + self.config.take().expect("ClientBuilder cannot be reused after building a Client") } }