diff --git a/Cargo.toml b/Cargo.toml index 85490ac..c5c718e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,18 +18,17 @@ encoding_rs = "0.7" futures = "0.1.21" http = "0.1.5" hyper = "0.12.2" -hyper-tls = "0.2.1" +hyper-tls = "0.3" libflate = "0.1.11" log = "0.4" mime = "0.3.7" mime_guess = "2.0.0-alpha.4" -native-tls = "0.1.5" +native-tls = "0.2" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5" tokio = "0.1.7" tokio-io = "0.1" -tokio-tls = "0.1" url = "1.2" uuid = { version = "0.6", features = ["v4"] } diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index e7bd3bd..fbb19b6 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -45,6 +45,7 @@ struct Config { gzip: bool, headers: HeaderMap, hostname_verification: bool, + certs_verification: bool, proxies: Vec, redirect_policy: RedirectPolicy, referer: bool, @@ -56,31 +57,24 @@ struct Config { impl ClientBuilder { /// Constructs a new `ClientBuilder` pub fn new() -> ClientBuilder { - match TlsConnector::builder() { - Ok(tls_connector_builder) => { - let mut headers: HeaderMap = HeaderMap::with_capacity(2); - headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT)); - headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime")); + let mut headers: HeaderMap = HeaderMap::with_capacity(2); + headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT)); + headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime")); - ClientBuilder { - config: Some(Config { - gzip: true, - headers: headers, - hostname_verification: true, - proxies: Vec::new(), - redirect_policy: RedirectPolicy::default(), - referer: true, - timeout: None, - tls: tls_connector_builder, - dns_threads: 4, - }), - err: None, - } - }, - Err(e) => ClientBuilder { - config: None, - err: Some(::error::from(e)), - } + ClientBuilder { + config: Some(Config { + gzip: true, + headers: headers, + hostname_verification: true, + certs_verification: true, + proxies: Vec::new(), + redirect_policy: RedirectPolicy::default(), + referer: true, + timeout: None, + tls: TlsConnector::builder(), + dns_threads: 4, + }), + err: None, } } @@ -98,19 +92,19 @@ impl ClientBuilder { if let Some(err) = self.err.take() { return Err(err); } - let config = self.config + let mut config = self.config .take() .expect("ClientBuilder cannot be reused after building a Client"); + config.tls.danger_accept_invalid_hostnames(!config.hostname_verification); + config.tls.danger_accept_invalid_certs(!config.certs_verification); + let tls = try_!(config.tls.build()); let proxies = Arc::new(config.proxies); let mut connector = Connector::new(config.dns_threads, tls, proxies.clone()); - if !config.hostname_verification { - connector.danger_disable_hostname_verification(); - } - + let hyper_client = ::hyper::Client::builder() .build(connector); @@ -133,9 +127,7 @@ impl ClientBuilder { pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut ClientBuilder { if let Some(config) = config_mut(&mut self.config, &self.err) { let cert = ::tls::cert(cert); - if let Err(e) = config.tls.add_root_certificate(cert) { - self.err = Some(::error::from(e)); - } + config.tls.add_root_certificate(cert); } self } @@ -144,9 +136,7 @@ impl ClientBuilder { pub fn identity(&mut self, identity: Identity) -> &mut ClientBuilder { if let Some(config) = config_mut(&mut self.config, &self.err) { let pkcs12 = ::tls::pkcs12(identity); - if let Err(e) = config.tls.identity(pkcs12) { - self.err = Some(::error::from(e)); - } + config.tls.identity(pkcs12); } self } @@ -177,6 +167,31 @@ impl ClientBuilder { self } + /// Disable certs 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. + #[inline] + pub fn danger_disable_certs_verification(&mut self) -> &mut ClientBuilder { + if let Some(config) = config_mut(&mut self.config, &self.err) { + config.certs_verification = false; + } + self + } + + /// Enable certs verification. + #[inline] + pub fn enable_certs_verification(&mut self) -> &mut ClientBuilder { + if let Some(config) = config_mut(&mut self.config, &self.err) { + config.certs_verification = true; + } + self + } + /// Sets the default headers for every request. #[inline] pub fn default_headers(&mut self, headers: HeaderMap) -> &mut ClientBuilder { diff --git a/src/client.rs b/src/client.rs index 28320c0..07951fb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -167,6 +167,29 @@ impl ClientBuilder { self } + /// Disable certs 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. + #[inline] + pub fn danger_disable_certs_verification(&mut self) -> &mut ClientBuilder { + self.inner.danger_disable_certs_verification(); + self + } + + /// Enable certs verification. + /// + /// Default is enabled. + #[inline] + pub fn enable_certs_verification(&mut self) -> &mut ClientBuilder { + self.inner.enable_certs_verification(); + self + } + /// Sets the default headers for every request. /// /// # Example diff --git a/src/connect.rs b/src/connect.rs index b0d6eb4..f41eb67 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -6,7 +6,7 @@ use hyper::client::connect::{Connect, Connected, Destination}; use hyper_tls::{HttpsConnector, MaybeHttpsStream}; use native_tls::TlsConnector; use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_tls::{TlsConnectorExt, TlsStream}; +use connect_async::{TlsConnectorExt, TlsStream}; use std::io::{self, Cursor, Read, Write}; use std::sync::Arc; @@ -33,10 +33,6 @@ impl Connector { tls: tls, } } - - pub fn danger_disable_hostname_verification(&mut self) { - self.https.danger_disable_hostname_verification(true); - } } impl Connect for Connector { diff --git a/src/connect_async.rs b/src/connect_async.rs new file mode 100644 index 0000000..f870fcf --- /dev/null +++ b/src/connect_async.rs @@ -0,0 +1,131 @@ +use std::io::{self, Read, Write}; + +use futures::{Poll, Future, Async}; +use native_tls; +use native_tls::{HandshakeError, Error, TlsConnector}; +use tokio_io::{AsyncRead, AsyncWrite}; + +/// A wrapper around an underlying raw stream which implements the TLS or SSL +/// protocol. +/// +/// A `TlsStream` represents a handshake that has been completed successfully +/// and both the server and the client are ready for receiving and sending +/// data. Bytes read from a `TlsStream` are decrypted from `S` and bytes written +/// to a `TlsStream` are encrypted when passing through to `S`. +#[derive(Debug)] +pub struct TlsStream { + inner: native_tls::TlsStream, +} + +/// Future returned from `TlsConnectorExt::connect_async` which will resolve +/// once the connection handshake has finished. +pub struct ConnectAsync { + inner: MidHandshake, +} + +struct MidHandshake { + inner: Option, HandshakeError>>, +} + +/// Extension trait for the `TlsConnector` type in the `native_tls` crate. +pub trait TlsConnectorExt: sealed::Sealed { + /// Connects the provided stream with this connector, assuming the provided + /// domain. + /// + /// This function will internally call `TlsConnector::connect` to connect + /// the stream and returns a future representing the resolution of the + /// connection operation. The returned future will resolve to either + /// `TlsStream` or `Error` depending if it's successful or not. + /// + /// This is typically used for clients who have already established, for + /// example, a TCP connection to a remote server. That stream is then + /// provided here to perform the client half of a connection to a + /// TLS-powered server. + /// + /// # Compatibility notes + /// + /// Note that this method currently requires `S: Read + Write` but it's + /// highly recommended to ensure that the object implements the `AsyncRead` + /// and `AsyncWrite` traits as well, otherwise this function will not work + /// properly. + fn connect_async(&self, domain: &str, stream: S) -> ConnectAsync + where S: Read + Write; // TODO: change to AsyncRead + AsyncWrite +} + +mod sealed { + pub trait Sealed {} +} + +impl Read for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl Write for TlsStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + + +impl AsyncRead for TlsStream { +} + +impl AsyncWrite for TlsStream { + fn shutdown(&mut self) -> Poll<(), io::Error> { + try_nb!(self.inner.shutdown()); + self.inner.get_mut().shutdown() + } +} + +impl TlsConnectorExt for TlsConnector { + fn connect_async(&self, domain: &str, stream: S) -> ConnectAsync + where S: Read + Write, + { + ConnectAsync { + inner: MidHandshake { + inner: Some(self.connect(domain, stream)), + }, + } + } +} + +impl sealed::Sealed for TlsConnector {} + +// TODO: change this to AsyncRead/AsyncWrite on next major version +impl Future for ConnectAsync { + type Item = TlsStream; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + self.inner.poll() + } +} + +// TODO: change this to AsyncRead/AsyncWrite on next major version +impl Future for MidHandshake { + type Item = TlsStream; + type Error = Error; + + fn poll(&mut self) -> Poll, Error> { + match self.inner.take().expect("cannot poll MidHandshake twice") { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => Err(e), + Err(HandshakeError::WouldBlock(s)) => { + match s.handshake() { + Ok(stream) => Ok(TlsStream { inner: stream }.into()), + Err(HandshakeError::Failure(e)) => Err(e), + Err(HandshakeError::WouldBlock(s)) => { + self.inner = Some(Err(HandshakeError::WouldBlock(s))); + Ok(Async::NotReady) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 899cae5..6d62558 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,8 +145,8 @@ extern crate serde_derive; extern crate serde_json; extern crate serde_urlencoded; extern crate tokio; +#[macro_use] extern crate tokio_io; -extern crate tokio_tls; extern crate url; extern crate uuid; @@ -173,6 +173,7 @@ mod error; mod async_impl; mod connect; +mod connect_async; mod body; mod client; mod into_url; diff --git a/src/tls.rs b/src/tls.rs index e699f2f..fe3570c 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -66,7 +66,7 @@ impl fmt::Debug for Certificate { /// Represent a private key and X509 cert as a client certificate. -pub struct Identity(native_tls::Pkcs12); +pub struct Identity(native_tls::Identity); impl Identity { /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. @@ -101,7 +101,7 @@ impl Identity { /// /// If the provided buffer is not valid DER, an error will be returned. pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result { - let inner = try_!(native_tls::Pkcs12::from_der(der, password)); + let inner = try_!(native_tls::Identity::from_pkcs12(der, password)); Ok(Identity(inner)) } } @@ -119,6 +119,6 @@ pub fn cert(cert: Certificate) -> native_tls::Certificate { cert.0 } -pub fn pkcs12(identity: Identity) -> native_tls::Pkcs12 { +pub fn pkcs12(identity: Identity) -> native_tls::Identity { identity.0 }