upgrade to native-tls 0.2 + invalid certs (#325)

- Bumps `native-tls` dependency to 0.2 and adapt code accordingly
- Import code used from `tokio-tls` into `connect_async` and adapt dependencies accordinlgy
- Add an option for using `danger_accept_invalid_certs` inside the `Config` struct
This commit is contained in:
Yannick Heinrich
2018-08-08 22:14:36 +02:00
committed by Sean McArthur
parent a25f62f4cb
commit 11f8588989
7 changed files with 212 additions and 47 deletions

View File

@@ -18,18 +18,17 @@ encoding_rs = "0.7"
futures = "0.1.21" futures = "0.1.21"
http = "0.1.5" http = "0.1.5"
hyper = "0.12.2" hyper = "0.12.2"
hyper-tls = "0.2.1" hyper-tls = "0.3"
libflate = "0.1.11" libflate = "0.1.11"
log = "0.4" log = "0.4"
mime = "0.3.7" mime = "0.3.7"
mime_guess = "2.0.0-alpha.4" mime_guess = "2.0.0-alpha.4"
native-tls = "0.1.5" native-tls = "0.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.5" serde_urlencoded = "0.5"
tokio = "0.1.7" tokio = "0.1.7"
tokio-io = "0.1" tokio-io = "0.1"
tokio-tls = "0.1"
url = "1.2" url = "1.2"
uuid = { version = "0.6", features = ["v4"] } uuid = { version = "0.6", features = ["v4"] }

View File

@@ -45,6 +45,7 @@ struct Config {
gzip: bool, gzip: bool,
headers: HeaderMap, headers: HeaderMap,
hostname_verification: bool, hostname_verification: bool,
certs_verification: bool,
proxies: Vec<Proxy>, proxies: Vec<Proxy>,
redirect_policy: RedirectPolicy, redirect_policy: RedirectPolicy,
referer: bool, referer: bool,
@@ -56,31 +57,24 @@ struct Config {
impl ClientBuilder { impl ClientBuilder {
/// Constructs a new `ClientBuilder` /// Constructs a new `ClientBuilder`
pub fn new() -> ClientBuilder { pub fn new() -> ClientBuilder {
match TlsConnector::builder() { let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
Ok(tls_connector_builder) => { headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT));
let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2); headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"));
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 { ClientBuilder {
config: Some(Config { config: Some(Config {
gzip: true, gzip: true,
headers: headers, headers: headers,
hostname_verification: true, hostname_verification: true,
proxies: Vec::new(), certs_verification: true,
redirect_policy: RedirectPolicy::default(), proxies: Vec::new(),
referer: true, redirect_policy: RedirectPolicy::default(),
timeout: None, referer: true,
tls: tls_connector_builder, timeout: None,
dns_threads: 4, tls: TlsConnector::builder(),
}), dns_threads: 4,
err: None, }),
} err: None,
},
Err(e) => ClientBuilder {
config: None,
err: Some(::error::from(e)),
}
} }
} }
@@ -98,18 +92,18 @@ impl ClientBuilder {
if let Some(err) = self.err.take() { if let Some(err) = self.err.take() {
return Err(err); return Err(err);
} }
let config = self.config let mut config = self.config
.take() .take()
.expect("ClientBuilder cannot be reused after building a Client"); .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 tls = try_!(config.tls.build());
let proxies = Arc::new(config.proxies); let proxies = Arc::new(config.proxies);
let mut connector = Connector::new(config.dns_threads, tls, proxies.clone()); 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() let hyper_client = ::hyper::Client::builder()
.build(connector); .build(connector);
@@ -133,9 +127,7 @@ impl ClientBuilder {
pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut ClientBuilder { pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut ClientBuilder {
if let Some(config) = config_mut(&mut self.config, &self.err) { if let Some(config) = config_mut(&mut self.config, &self.err) {
let cert = ::tls::cert(cert); let cert = ::tls::cert(cert);
if let Err(e) = config.tls.add_root_certificate(cert) { config.tls.add_root_certificate(cert);
self.err = Some(::error::from(e));
}
} }
self self
} }
@@ -144,9 +136,7 @@ impl ClientBuilder {
pub fn identity(&mut self, identity: Identity) -> &mut ClientBuilder { pub fn identity(&mut self, identity: Identity) -> &mut ClientBuilder {
if let Some(config) = config_mut(&mut self.config, &self.err) { if let Some(config) = config_mut(&mut self.config, &self.err) {
let pkcs12 = ::tls::pkcs12(identity); let pkcs12 = ::tls::pkcs12(identity);
if let Err(e) = config.tls.identity(pkcs12) { config.tls.identity(pkcs12);
self.err = Some(::error::from(e));
}
} }
self self
} }
@@ -177,6 +167,31 @@ impl ClientBuilder {
self 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. /// Sets the default headers for every request.
#[inline] #[inline]
pub fn default_headers(&mut self, headers: HeaderMap) -> &mut ClientBuilder { pub fn default_headers(&mut self, headers: HeaderMap) -> &mut ClientBuilder {

View File

@@ -167,6 +167,29 @@ impl ClientBuilder {
self 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. /// Sets the default headers for every request.
/// ///
/// # Example /// # Example

View File

@@ -6,7 +6,7 @@ use hyper::client::connect::{Connect, Connected, Destination};
use hyper_tls::{HttpsConnector, MaybeHttpsStream}; use hyper_tls::{HttpsConnector, MaybeHttpsStream};
use native_tls::TlsConnector; use native_tls::TlsConnector;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tls::{TlsConnectorExt, TlsStream}; use connect_async::{TlsConnectorExt, TlsStream};
use std::io::{self, Cursor, Read, Write}; use std::io::{self, Cursor, Read, Write};
use std::sync::Arc; use std::sync::Arc;
@@ -33,10 +33,6 @@ impl Connector {
tls: tls, tls: tls,
} }
} }
pub fn danger_disable_hostname_verification(&mut self) {
self.https.danger_disable_hostname_verification(true);
}
} }
impl Connect for Connector { impl Connect for Connector {

131
src/connect_async.rs Normal file
View File

@@ -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<S>` 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<S> {
inner: native_tls::TlsStream<S>,
}
/// Future returned from `TlsConnectorExt::connect_async` which will resolve
/// once the connection handshake has finished.
pub struct ConnectAsync<S> {
inner: MidHandshake<S>,
}
struct MidHandshake<S> {
inner: Option<Result<native_tls::TlsStream<S>, HandshakeError<S>>>,
}
/// 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<S>` 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<S>(&self, domain: &str, stream: S) -> ConnectAsync<S>
where S: Read + Write; // TODO: change to AsyncRead + AsyncWrite
}
mod sealed {
pub trait Sealed {}
}
impl<S: Read + Write> Read for TlsStream<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<S: Read + Write> Write for TlsStream<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<S: AsyncRead + AsyncWrite> AsyncRead for TlsStream<S> {
}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for TlsStream<S> {
fn shutdown(&mut self) -> Poll<(), io::Error> {
try_nb!(self.inner.shutdown());
self.inner.get_mut().shutdown()
}
}
impl TlsConnectorExt for TlsConnector {
fn connect_async<S>(&self, domain: &str, stream: S) -> ConnectAsync<S>
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<S: Read + Write> Future for ConnectAsync<S> {
type Item = TlsStream<S>;
type Error = Error;
fn poll(&mut self) -> Poll<TlsStream<S>, Error> {
self.inner.poll()
}
}
// TODO: change this to AsyncRead/AsyncWrite on next major version
impl<S: Read + Write> Future for MidHandshake<S> {
type Item = TlsStream<S>;
type Error = Error;
fn poll(&mut self) -> Poll<TlsStream<S>, 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)
}
}
}
}
}
}

View File

@@ -145,8 +145,8 @@ extern crate serde_derive;
extern crate serde_json; extern crate serde_json;
extern crate serde_urlencoded; extern crate serde_urlencoded;
extern crate tokio; extern crate tokio;
#[macro_use]
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_tls;
extern crate url; extern crate url;
extern crate uuid; extern crate uuid;
@@ -173,6 +173,7 @@ mod error;
mod async_impl; mod async_impl;
mod connect; mod connect;
mod connect_async;
mod body; mod body;
mod client; mod client;
mod into_url; mod into_url;

View File

@@ -66,7 +66,7 @@ impl fmt::Debug for Certificate {
/// Represent a private key and X509 cert as a client 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 { impl Identity {
/// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. /// 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. /// If the provided buffer is not valid DER, an error will be returned.
pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result<Identity> { pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result<Identity> {
let inner = try_!(native_tls::Pkcs12::from_der(der, password)); let inner = try_!(native_tls::Identity::from_pkcs12(der, password));
Ok(Identity(inner)) Ok(Identity(inner))
} }
} }
@@ -119,6 +119,6 @@ pub fn cert(cert: Certificate) -> native_tls::Certificate {
cert.0 cert.0
} }
pub fn pkcs12(identity: Identity) -> native_tls::Pkcs12 { pub fn pkcs12(identity: Identity) -> native_tls::Identity {
identity.0 identity.0
} }