Add rustls support (#390)

This commit is contained in:
quininer
2018-12-18 03:57:43 +08:00
committed by Sean McArthur
parent 9cbd8c41ad
commit 37039760f8
9 changed files with 482 additions and 206 deletions

View File

@@ -15,6 +15,14 @@ matrix:
- rust: stable
env: FEATURES="--no-default-features"
# rustls-tls
- rust: stable
env: FEATURES="--no-default-features --features rustls-tls"
# default-tls and rustls-tls
- rust: stable
env: FEATURES="--features rustls-tls"
- rust: stable
env: FEATURES="--features hyper-011"

View File

@@ -30,6 +30,10 @@ tokio = "0.1.7"
tokio-io = "0.1"
url = "1.2"
uuid = { version = "0.7", features = ["v4"] }
hyper-rustls = { version = "0.15", optional = true }
tokio-rustls = { version = "0.8", optional = true }
webpki-roots = { version = "0.15", optional = true }
rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true }
[dev-dependencies]
env_logger = "0.6"
@@ -37,8 +41,10 @@ serde_derive = "1.0"
[features]
default = ["default-tls"]
tls = []
hyper-011 = ["hyper-old-types"]
default-tls = ["hyper-tls", "native-tls"]
default-tls = ["hyper-tls", "native-tls", "tls"]
rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"]
native-tls-vendored = ["native-tls/vendored"]
[package.metadata.docs.rs]

View File

@@ -9,7 +9,7 @@ use header::{HeaderMap, HeaderValue, LOCATION, USER_AGENT, REFERER, ACCEPT,
ACCEPT_ENCODING, RANGE, TRANSFER_ENCODING, CONTENT_TYPE, CONTENT_LENGTH, CONTENT_ENCODING};
use mime::{self};
#[cfg(feature = "default-tls")]
use native_tls::{TlsConnector, TlsConnectorBuilder};
use native_tls::TlsConnector;
use super::request::{Request, RequestBuilder};
@@ -18,8 +18,10 @@ use connect::Connector;
use into_url::to_uri;
use redirect::{self, RedirectPolicy, remove_sensitive_headers};
use {IntoUrl, Method, Proxy, StatusCode, Url};
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
use {Certificate, Identity};
#[cfg(feature = "tls")]
use ::tls::{ TLSBackend, inner };
static DEFAULT_USER_AGENT: &'static str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
@@ -46,14 +48,18 @@ struct Config {
headers: HeaderMap,
#[cfg(feature = "default-tls")]
hostname_verification: bool,
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
certs_verification: bool,
proxies: Vec<Proxy>,
redirect_policy: RedirectPolicy,
referer: bool,
timeout: Option<Duration>,
#[cfg(feature = "default-tls")]
tls: TlsConnectorBuilder,
#[cfg(feature = "tls")]
root_certs: Vec<Certificate>,
#[cfg(feature = "tls")]
identity: Option<Identity>,
#[cfg(feature = "tls")]
tls: TLSBackend,
dns_threads: usize,
}
@@ -70,14 +76,18 @@ impl ClientBuilder {
headers: headers,
#[cfg(feature = "default-tls")]
hostname_verification: true,
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
certs_verification: true,
proxies: Vec::new(),
redirect_policy: RedirectPolicy::default(),
referer: true,
timeout: None,
#[cfg(feature = "default-tls")]
tls: TlsConnector::builder(),
#[cfg(feature = "tls")]
root_certs: Vec::new(),
#[cfg(feature = "tls")]
identity: None,
#[cfg(feature = "tls")]
tls: TLSBackend::default(),
dns_threads: 4,
},
}
@@ -87,32 +97,103 @@ impl ClientBuilder {
///
/// # Errors
///
/// This method fails if native TLS backend cannot be initialized.
/// This method fails if TLS backend cannot be initialized.
pub fn build(self) -> ::Result<Client> {
let config = self.config;
let proxies = Arc::new(config.proxies);
let connector = {
#[cfg(feature = "default-tls")]
{
let mut tls = config.tls;
tls.danger_accept_invalid_hostnames(!config.hostname_verification);
tls.danger_accept_invalid_certs(!config.certs_verification);
#[cfg(feature = "tls")]
match config.tls {
#[cfg(feature = "default-tls")]
TLSBackend::Default => {
let mut tls = TlsConnector::builder();
tls.danger_accept_invalid_hostnames(!config.hostname_verification);
tls.danger_accept_invalid_certs(!config.certs_verification);
let tls = try_!(tls.build());
for cert in config.root_certs {
let cert = match cert.inner {
inner::Certificate::Der(buf) =>
try_!(::native_tls::Certificate::from_der(&buf)),
inner::Certificate::Pem(buf) =>
try_!(::native_tls::Certificate::from_pem(&buf))
};
tls.add_root_certificate(cert);
}
let proxies = Arc::new(config.proxies);
if let Some(id) = config.identity {
let id = match id.inner {
inner::Identity::Pkcs12(buf, passwd) =>
try_!(::native_tls::Identity::from_pkcs12(&buf, &passwd)),
#[cfg(feature = "rustls-tls")]
_ => return Err(::error::from(::error::Kind::Incompatible))
};
tls.identity(id);
}
Connector::new(config.dns_threads, tls, proxies.clone())
Connector::new_default_tls(config.dns_threads, tls, proxies.clone())?
},
#[cfg(feature = "rustls-tls")]
TLSBackend::Rustls => {
use std::io::Cursor;
use rustls::TLSError;
use rustls::internal::pemfile;
use ::tls::NoVerifier;
let mut tls = ::rustls::ClientConfig::new();
tls.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
if !config.certs_verification {
tls.dangerous().set_certificate_verifier(Arc::new(NoVerifier));
}
for cert in config.root_certs {
match cert.inner {
inner::Certificate::Der(buf) => try_!(tls.root_store.add(&::rustls::Certificate(buf))
.map_err(TLSError::WebPKIError)),
inner::Certificate::Pem(buf) => {
let mut pem = Cursor::new(buf);
let mut certs = try_!(pemfile::certs(&mut pem)
.map_err(|_| TLSError::General(String::from("No valid certificate was found"))));
for c in certs {
try_!(tls.root_store.add(&c)
.map_err(TLSError::WebPKIError));
}
}
}
}
if let Some(id) = config.identity {
let (key, certs) = match id.inner {
inner::Identity::Pem(buf) => {
let mut pem = Cursor::new(buf);
let mut certs = try_!(pemfile::certs(&mut pem)
.map_err(|_| TLSError::General(String::from("No valid certificate was found"))));
pem.set_position(0);
let mut sk = try_!(pemfile::pkcs8_private_keys(&mut pem)
.or_else(|_| {
pem.set_position(0);
pemfile::rsa_private_keys(&mut pem)
})
.map_err(|_| TLSError::General(String::from("No valid private key was found"))));
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
(sk, certs)
} else {
return Err(::error::from(TLSError::General(String::from("private key or certificate not found"))));
}
},
#[cfg(feature = "default-tls")]
_ => return Err(::error::from(::error::Kind::Incompatible))
};
tls.set_single_client_cert(certs, key);
}
Connector::new_rustls_tls(config.dns_threads, tls, proxies.clone())?
}
}
#[cfg(not(feature = "default-tls"))]
{
let proxies = Arc::new(config.proxies);
#[cfg(not(feature = "tls"))]
Connector::new(config.dns_threads, proxies.clone())
}
};
let hyper_client = ::hyper::Client::builder()
@@ -129,20 +210,34 @@ impl ClientBuilder {
})
}
/// Use native TLS backend.
#[cfg(feature = "default-tls")]
pub fn use_default_tls(mut self) -> ClientBuilder {
self.config.tls = TLSBackend::Default;
self
}
/// Use rustls TLS backend.
#[cfg(feature = "rustls-tls")]
pub fn use_rustls_tls(mut self) -> ClientBuilder {
self.config.tls = TLSBackend::Rustls;
self
}
/// Add a custom root certificate.
///
/// This can be used to connect to a server that has a self-signed
/// certificate for example.
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder {
self.config.tls.add_root_certificate(cert.cert());
self.config.root_certs.push(cert);
self
}
/// Sets the identity to be used for client certificate authentication.
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub fn identity(mut self, identity: Identity) -> ClientBuilder {
self.config.tls.identity(identity.pkcs12());
self.config.identity = Some(identity);
self
}
@@ -162,7 +257,6 @@ impl ClientBuilder {
self
}
/// Controls the use of certificate validation.
///
/// Defaults to `false`.
@@ -174,7 +268,7 @@ impl ClientBuilder {
/// will be trusted for use. This includes expired certificates. This
/// introduces significant vulnerabilities, and should only be used
/// as a last resort.
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder {
self.config.certs_verification = !accept_invalid_certs;
self
@@ -196,9 +290,9 @@ impl ClientBuilder {
/// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
/// The body is **not** automatically inflated.
/// - When receiving a response, if it's headers contain a `Content-Encoding` value that
/// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the
/// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the
/// headers' set. The body is automatically deinflated.
///
///
/// Default is enabled.
pub fn gzip(mut self, enable: bool) -> ClientBuilder {
self.config.gzip = enable;
@@ -247,7 +341,7 @@ impl Client {
///
/// # Panics
///
/// This method panics if native TLS backend cannot be created or
/// This method panics if TLS backend cannot be created or
/// initialized. Use `Client::builder()` if you wish to handle the failure
/// as an `Error` instead of panicking.
pub fn new() -> Client {

View File

@@ -10,7 +10,7 @@ use futures::sync::{mpsc, oneshot};
use request::{Request, RequestBuilder};
use response::Response;
use {async_impl, header, Method, IntoUrl, Proxy, RedirectPolicy, wait};
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
use {Certificate, Identity};
/// A `Client` to make Requests with.
@@ -79,6 +79,18 @@ impl ClientBuilder {
})
}
/// Use native TLS backend.
#[cfg(feature = "default-tls")]
pub fn use_default_tls(self) -> ClientBuilder {
self.with_inner(move |inner| inner.use_default_tls())
}
/// Use rustls TLS backend.
#[cfg(feature = "rustls-tls")]
pub fn use_rustls_tls(self) -> ClientBuilder {
self.with_inner(move |inner| inner.use_rustls_tls())
}
/// Add a custom root certificate.
///
/// This can be used to connect to a server that has a self-signed
@@ -108,7 +120,7 @@ impl ClientBuilder {
/// # Errors
///
/// This method fails if adding root certificate was unsuccessful.
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder {
self.with_inner(move |inner| inner.add_root_certificate(cert))
}
@@ -123,10 +135,18 @@ impl ClientBuilder {
/// # fn build_client() -> Result<(), Box<std::error::Error>> {
/// // read a local PKCS12 bundle
/// let mut buf = Vec::new();
/// File::open("my-ident.pfx")?.read_to_end(&mut buf)?;
///
/// #[cfg(feature = "default-tls")]
/// File::open("my-ident.pfx")?.read_to_end(&mut buf)?;
/// #[cfg(feature = "rustls-tls")]
/// File::open("my-ident.pem")?.read_to_end(&mut buf)?;
///
/// #[cfg(feature = "default-tls")]
/// // create an Identity from the PKCS#12 archive
/// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
/// #[cfg(feature = "rustls-tls")]
/// // create an Identity from the PEM file
/// let pkcs12 = reqwest::Identity::from_pem(&buf)?;
///
/// // get a client builder
/// let client = reqwest::Client::builder()
@@ -136,7 +156,7 @@ impl ClientBuilder {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub fn identity(self, identity: Identity) -> ClientBuilder {
self.with_inner(move |inner| inner.identity(identity))
}
@@ -157,7 +177,6 @@ impl ClientBuilder {
self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname))
}
/// Controls the use of certificate validation.
///
/// Defaults to `false`.
@@ -169,7 +188,7 @@ impl ClientBuilder {
/// will be trusted for use. This includes expired certificates. This
/// introduces significant vulnerabilities, and should only be used
/// as a last resort.
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder {
self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs))
}
@@ -223,9 +242,9 @@ impl ClientBuilder {
/// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
/// The body is **not** automatically inflated.
/// - When receiving a response, if it's headers contain a `Content-Encoding` value that
/// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the
/// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the
/// headers' set. The body is automatically deinflated.
///
///
/// Default is enabled.
pub fn gzip(self, enable: bool) -> ClientBuilder {
self.with_inner(|inner| inner.gzip(enable))

View File

@@ -1,51 +1,70 @@
use bytes::{Buf, BufMut};
use futures::{Future, Poll};
use futures::Future;
use http::uri::Scheme;
use hyper::client::{HttpConnector};
use hyper::client::connect::{Connect, Connected, Destination};
#[cfg(feature = "default-tls")]
use hyper_tls::{HttpsConnector, MaybeHttpsStream};
#[cfg(feature = "default-tls")]
use native_tls::TlsConnector;
use tokio_io::{AsyncRead, AsyncWrite};
#[cfg(feature = "default-tls")]
use connect_async::{TlsConnectorExt, TlsStream};
use std::io::{self, Read, Write};
#[cfg(feature = "default-tls")]
use native_tls::{TlsConnector, TlsConnectorBuilder};
#[cfg(feature = "tls")]
use futures::Poll;
#[cfg(feature = "tls")]
use bytes::BufMut;
use std::io;
use std::sync::Arc;
use Proxy;
pub(crate) struct Connector {
#[cfg(feature = "default-tls")]
http: HttpsConnector<HttpConnector>,
#[cfg(not(feature = "default-tls"))]
http: HttpConnector,
proxies: Arc<Vec<Proxy>>,
inner: Inner
}
enum Inner {
#[cfg(not(feature = "tls"))]
Http(HttpConnector),
#[cfg(feature = "default-tls")]
tls: TlsConnector,
DefaultTls(::hyper_tls::HttpsConnector<HttpConnector>, TlsConnector),
#[cfg(feature = "rustls-tls")]
RustlsTls(::hyper_rustls::HttpsConnector<HttpConnector>, Arc<rustls::ClientConfig>)
}
impl Connector {
#[cfg(not(feature = "default-tls"))]
#[cfg(not(feature = "tls"))]
pub(crate) fn new(threads: usize, proxies: Arc<Vec<Proxy>>) -> Connector {
let http = HttpConnector::new(threads);
Connector {
http,
proxies,
inner: Inner::Http(http)
}
}
#[cfg(feature = "default-tls")]
pub(crate) fn new(threads: usize, tls: TlsConnector, proxies: Arc<Vec<Proxy>>) -> Connector {
pub(crate) fn new_default_tls(threads: usize, tls: TlsConnectorBuilder, proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> {
let tls = try_!(tls.build());
let mut http = HttpConnector::new(threads);
http.enforce_http(false);
let http = HttpsConnector::from((http, tls.clone()));
let http = ::hyper_tls::HttpsConnector::from((http, tls.clone()));
Connector {
http,
Ok(Connector {
proxies,
tls,
}
inner: Inner::DefaultTls(http, tls)
})
}
#[cfg(feature = "rustls-tls")]
pub(crate) fn new_rustls_tls(threads: usize, tls: rustls::ClientConfig, proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> {
let mut http = HttpConnector::new(threads);
http.enforce_http(false);
let http = ::hyper_rustls::HttpsConnector::from((http, tls.clone()));
Ok(Connector {
proxies,
inner: Inner::RustlsTls(http, Arc::new(tls))
})
}
}
@@ -55,6 +74,23 @@ impl Connect for Connector {
type Future = Connecting;
fn connect(&self, dst: Destination) -> Self::Future {
macro_rules! connect {
( $http:expr, $dst:expr, $proxy:expr ) => {
Box::new($http.connect($dst)
.map(|(io, connected)| (Box::new(io) as Conn, connected.proxy($proxy))))
};
( $dst:expr, $proxy:expr ) => {
match &self.inner {
#[cfg(not(feature = "tls"))]
Inner::Http(http) => connect!(http, $dst, $proxy),
#[cfg(feature = "default-tls")]
Inner::DefaultTls(http, _) => connect!(http, $dst, $proxy),
#[cfg(feature = "rustls-tls")]
Inner::RustlsTls(http, _) => connect!(http, $dst, $proxy)
}
};
}
for prox in self.proxies.iter() {
if let Some(puri) = prox.intercept(&dst) {
trace!("proxy({:?}) intercepts {:?}", puri, dst);
@@ -69,116 +105,70 @@ impl Connect for Connector {
ndst.set_host(puri.host().expect("proxy target should have host"))
.expect("proxy target host should be valid");
ndst.set_port(puri.port_part().map(|p| p.as_u16()));
ndst.set_port(puri.port_part().map(|port| port.as_u16()));
#[cfg(feature = "default-tls")]
{
if dst.scheme() == "https" {
let host = dst.host().to_owned();
let port = dst.port().unwrap_or(443);
let tls = self.tls.clone();
return Box::new(self.http.connect(ndst).and_then(move |(conn, connected)| {
trace!("tunneling HTTPS over proxy");
tunnel(conn, host.clone(), port)
.and_then(move |tunneled| {
tls.connect_async(&host, tunneled)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
})
.map(|io| (Conn::Proxied(io), connected.proxy(true)))
}));
match &self.inner {
#[cfg(feature = "default-tls")]
Inner::DefaultTls(http, tls) => if dst.scheme() == "https" {
#[cfg(feature = "default-tls")]
use connect_async::TlsConnectorExt;
let host = dst.host().to_owned();
let port = dst.port().unwrap_or(443);
let tls = tls.clone();
return Box::new(http.connect(ndst).and_then(move |(conn, connected)| {
trace!("tunneling HTTPS over proxy");
tunnel(conn, host.clone(), port)
.and_then(move |tunneled| {
tls.connect_async(&host, tunneled)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
})
.map(|io| (Box::new(io) as Conn, connected.proxy(true)))
}));
},
#[cfg(feature = "rustls-tls")]
Inner::RustlsTls(http, tls) => if dst.scheme() == "https" {
#[cfg(feature = "rustls-tls")]
use tokio_rustls::TlsConnector as RustlsConnector;
#[cfg(feature = "rustls-tls")]
use tokio_rustls::webpki::DNSNameRef;
let host = dst.host().to_owned();
let port = dst.port().unwrap_or(443);
let tls = tls.clone();
return Box::new(http.connect(ndst).and_then(move |(conn, connected)| {
trace!("tunneling HTTPS over proxy");
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
.map(|dnsname| dnsname.to_owned())
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Invalid DNS Name"));
tunnel(conn, host, port)
.and_then(move |tunneled| Ok((maybe_dnsname?, tunneled)))
.and_then(move |(dnsname, tunneled)| {
RustlsConnector::from(tls).connect(dnsname.as_ref(), tunneled)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
})
.map(|io| (Box::new(io) as Conn, connected.proxy(true)))
}));
},
#[cfg(not(feature = "tls"))]
Inner::Http(_) => ()
}
}
return Box::new(self.http.connect(ndst).map(|(io, connected)| (Conn::Normal(io), connected.proxy(true))));
return connect!(ndst, true);
}
}
Box::new(self.http.connect(dst).map(|(io, connected)| (Conn::Normal(io), connected)))
connect!(dst, false)
}
}
type HttpStream = <HttpConnector as Connect>::Transport;
#[cfg(feature = "default-tls")]
type HttpsStream = MaybeHttpsStream<HttpStream>;
pub(crate) trait AsyncConn: AsyncRead + AsyncWrite {}
impl<T: AsyncRead + AsyncWrite> AsyncConn for T {}
pub(crate) type Conn = Box<dyn AsyncConn + Send + Sync + 'static>;
pub(crate) type Connecting = Box<Future<Item=(Conn, Connected), Error=io::Error> + Send>;
pub(crate) enum Conn {
#[cfg(feature = "default-tls")]
Normal(HttpsStream),
#[cfg(not(feature = "default-tls"))]
Normal(HttpStream),
#[cfg(feature = "default-tls")]
Proxied(TlsStream<MaybeHttpsStream<HttpStream>>),
}
impl Read for Conn {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Conn::Normal(ref mut s) => s.read(buf),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref mut s) => s.read(buf),
}
}
}
impl Write for Conn {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
Conn::Normal(ref mut s) => s.write(buf),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref mut s) => s.write(buf),
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match *self {
Conn::Normal(ref mut s) => s.flush(),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref mut s) => s.flush(),
}
}
}
impl AsyncRead for Conn {
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
match *self {
Conn::Normal(ref s) => s.prepare_uninitialized_buffer(buf),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref s) => s.prepare_uninitialized_buffer(buf),
}
}
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
match *self {
Conn::Normal(ref mut s) => s.read_buf(buf),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref mut s) => s.read_buf(buf),
}
}
}
impl AsyncWrite for Conn {
fn shutdown(&mut self) -> Poll<(), io::Error> {
match *self {
Conn::Normal(ref mut s) => s.shutdown(),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref mut s) => s.shutdown(),
}
}
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
match *self {
Conn::Normal(ref mut s) => s.write_buf(buf),
#[cfg(feature = "default-tls")]
Conn::Proxied(ref mut s) => s.write_buf(buf),
}
}
}
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
fn tunnel<T>(conn: T, host: String, port: u16) -> Tunnel<T> {
let buf = format!("\
CONNECT {0}:{1} HTTP/1.1\r\n\
@@ -193,20 +183,20 @@ fn tunnel<T>(conn: T, host: String, port: u16) -> Tunnel<T> {
}
}
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
struct Tunnel<T> {
buf: io::Cursor<Vec<u8>>,
conn: Option<T>,
state: TunnelState,
}
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
enum TunnelState {
Writing,
Reading
}
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
impl<T> Future for Tunnel<T>
where T: AsyncRead + AsyncWrite {
type Item = T;
@@ -242,7 +232,7 @@ where T: AsyncRead + AsyncWrite {
}
}
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
#[inline]
fn tunnel_eof() -> io::Error {
io::Error::new(
@@ -251,7 +241,7 @@ fn tunnel_eof() -> io::Error {
)
}
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
#[cfg(test)]
mod tests {
use std::io::{Read, Write};

View File

@@ -135,8 +135,12 @@ impl Error {
Kind::Hyper(ref e) => Some(e),
Kind::Mime(ref e) => Some(e),
Kind::Url(ref e) => Some(e),
#[cfg(all(feature = "default-tls", feature = "rustls-tls"))]
Kind::Incompatible => None,
#[cfg(feature = "default-tls")]
Kind::Tls(ref e) => Some(e),
Kind::NativeTls(ref e) => Some(e),
#[cfg(feature = "rustls-tls")]
Kind::Rustls(ref e) => Some(e),
Kind::Io(ref e) => Some(e),
Kind::UrlEncoded(ref e) => Some(e),
Kind::Json(ref e) => Some(e),
@@ -225,8 +229,12 @@ impl fmt::Display for Error {
Kind::Mime(ref e) => fmt::Display::fmt(e, f),
Kind::Url(ref e) => fmt::Display::fmt(e, f),
Kind::UrlBadScheme => f.write_str("URL scheme is not allowed"),
#[cfg(all(feature = "default-tls", feature = "rustls-tls"))]
Kind::Incompatible => f.write_str("Incompatible identity type"),
#[cfg(feature = "default-tls")]
Kind::Tls(ref e) => fmt::Display::fmt(e, f),
Kind::NativeTls(ref e) => fmt::Display::fmt(e, f),
#[cfg(feature = "rustls-tls")]
Kind::Rustls(ref e) => fmt::Display::fmt(e, f),
Kind::Io(ref e) => fmt::Display::fmt(e, f),
Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f),
Kind::Json(ref e) => fmt::Display::fmt(e, f),
@@ -252,8 +260,12 @@ impl StdError for Error {
Kind::Mime(ref e) => e.description(),
Kind::Url(ref e) => e.description(),
Kind::UrlBadScheme => "URL scheme is not allowed",
#[cfg(all(feature = "default-tls", feature = "rustls-tls"))]
Kind::Incompatible => "Incompatible identity type",
#[cfg(feature = "default-tls")]
Kind::Tls(ref e) => e.description(),
Kind::NativeTls(ref e) => e.description(),
#[cfg(feature = "rustls-tls")]
Kind::Rustls(ref e) => e.description(),
Kind::Io(ref e) => e.description(),
Kind::UrlEncoded(ref e) => e.description(),
Kind::Json(ref e) => e.description(),
@@ -270,8 +282,12 @@ impl StdError for Error {
Kind::Hyper(ref e) => e.cause(),
Kind::Mime(ref e) => e.cause(),
Kind::Url(ref e) => e.cause(),
#[cfg(all(feature = "default-tls", feature = "rustls-tls"))]
Kind::Incompatible => None,
#[cfg(feature = "default-tls")]
Kind::Tls(ref e) => e.cause(),
Kind::NativeTls(ref e) => e.cause(),
#[cfg(feature = "rustls-tls")]
Kind::Rustls(ref e) => e.cause(),
Kind::Io(ref e) => e.cause(),
Kind::UrlEncoded(ref e) => e.cause(),
Kind::Json(ref e) => e.cause(),
@@ -291,8 +307,12 @@ pub(crate) enum Kind {
Mime(::mime::FromStrError),
Url(::url::ParseError),
UrlBadScheme,
#[cfg(all(feature = "default-tls", feature = "rustls-tls"))]
Incompatible,
#[cfg(feature = "default-tls")]
Tls(::native_tls::Error),
NativeTls(::native_tls::Error),
#[cfg(feature = "rustls-tls")]
Rustls(::rustls::TLSError),
Io(io::Error),
UrlEncoded(::serde_urlencoded::ser::Error),
Json(::serde_json::Error),
@@ -355,7 +375,14 @@ impl From<::serde_json::Error> for Kind {
#[cfg(feature = "default-tls")]
impl From<::native_tls::Error> for Kind {
fn from(err: ::native_tls::Error) -> Kind {
Kind::Tls(err)
Kind::NativeTls(err)
}
}
#[cfg(feature = "rustls-tls")]
impl From<::rustls::TLSError> for Kind {
fn from(err: ::rustls::TLSError) -> Kind {
Kind::Rustls(err)
}
}

View File

@@ -166,6 +166,15 @@ extern crate tokio_io;
extern crate url;
extern crate uuid;
#[cfg(feature = "rustls-tls")]
extern crate hyper_rustls;
#[cfg(feature = "rustls-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rustls-tls")]
extern crate webpki_roots;
#[cfg(feature = "rustls-tls")]
extern crate rustls;
pub use hyper::header;
pub use hyper::Method;
pub use hyper::{StatusCode, Version};
@@ -180,7 +189,7 @@ pub use self::proxy::Proxy;
pub use self::redirect::{RedirectAction, RedirectAttempt, RedirectPolicy};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
pub use self::tls::{Certificate, Identity};
@@ -199,7 +208,7 @@ mod proxy;
mod redirect;
mod request;
mod response;
#[cfg(feature = "default-tls")]
#[cfg(feature = "tls")]
mod tls;
mod wait;

View File

@@ -1,8 +1,32 @@
use std::fmt;
use native_tls;
#[cfg(feature = "rustls-tls")]
use rustls::{TLSError, ServerCertVerifier, RootCertStore, ServerCertVerified};
#[cfg(feature = "rustls-tls")]
use tokio_rustls::webpki::DNSNameRef;
/// Represent an X509 certificate.
pub struct Certificate(native_tls::Certificate);
pub struct Certificate {
pub(crate) inner: inner::Certificate
}
/// Represent a private key and X509 cert as a client certificate.
pub struct Identity {
pub(crate) inner: inner::Identity
}
pub(crate) mod inner {
pub(crate) enum Certificate {
Der(Vec<u8>),
Pem(Vec<u8>)
}
pub(crate) enum Identity {
#[cfg(feature = "default-tls")]
Pkcs12(Vec<u8>, String),
#[cfg(feature = "rustls-tls")]
Pem(Vec<u8>),
}
}
impl Certificate {
/// Create a `Certificate` from a binary DER encoded certificate
@@ -24,10 +48,11 @@ impl Certificate {
///
/// # Errors
///
/// If the provided buffer is not valid DER, an error will be returned.
/// It never returns error.
pub fn from_der(der: &[u8]) -> ::Result<Certificate> {
let inner = try_!(native_tls::Certificate::from_der(der));
Ok(Certificate(inner))
Ok(Certificate {
inner: inner::Certificate::Der(der.to_owned())
})
}
@@ -50,29 +75,14 @@ impl Certificate {
///
/// # Errors
///
/// If the provided buffer is not valid PEM, an error will be returned.
/// It never returns error.
pub fn from_pem(der: &[u8]) -> ::Result<Certificate> {
let inner = try_!(native_tls::Certificate::from_pem(der));
Ok(Certificate(inner))
}
pub(crate) fn cert(self) -> native_tls::Certificate {
self.0
}
}
impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Certificate")
.finish()
Ok(Certificate {
inner: inner::Certificate::Pem(der.to_owned())
})
}
}
/// Represent a private key and X509 cert as a client certificate.
pub struct Identity(native_tls::Identity);
impl Identity {
/// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
///
@@ -104,14 +114,49 @@ impl Identity {
///
/// # Errors
///
/// If the provided buffer is not valid DER, an error will be returned.
/// It never returns error.
#[cfg(feature = "default-tls")]
pub fn from_pkcs12_der(der: &[u8], password: &str) -> ::Result<Identity> {
let inner = try_!(native_tls::Identity::from_pkcs12(der, password));
Ok(Identity(inner))
Ok(Identity {
inner: inner::Identity::Pkcs12(der.to_owned(), password.to_owned())
})
}
pub(crate) fn pkcs12(self) -> native_tls::Identity {
self.0
/// Parses PEM encoded private key and certificate.
///
/// The input should contain a PEM encoded private key
/// and at least one PEM encoded certificate.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn pem() -> Result<(), Box<std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my-ident.pem")?
/// .read_to_end(&mut buf)?;
/// let id = reqwest::Identity::from_pem(&buf)?;
/// # drop(id);
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// It never returns error.
#[cfg(feature = "rustls-tls")]
pub fn from_pem(pem: &[u8]) -> ::Result<Identity> {
Ok(Identity {
inner: inner::Identity::Pem(pem.to_owned())
})
}
}
impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Certificate")
.finish()
}
}
@@ -122,3 +167,35 @@ impl fmt::Debug for Identity {
}
}
pub(crate) enum TLSBackend {
#[cfg(feature = "default-tls")]
Default,
#[cfg(feature = "rustls-tls")]
Rustls
}
impl Default for TLSBackend {
fn default() -> TLSBackend {
#[cfg(feature = "default-tls")]
{ TLSBackend::Default }
#[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))]
{ TLSBackend::Rustls }
}
}
#[cfg(feature = "rustls-tls")]
pub(crate) struct NoVerifier;
#[cfg(feature = "rustls-tls")]
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_roots: &RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: DNSNameRef,
_ocsp_response: &[u8]
) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion())
}
}

46
tests/badssl.rs Normal file
View File

@@ -0,0 +1,46 @@
extern crate reqwest;
#[cfg(feature = "tls")]
#[test]
fn test_badssl_modern() {
let text = reqwest::get("https://mozilla-modern.badssl.com/").unwrap()
.text().unwrap();
assert!(text.contains("<title>mozilla-modern.badssl.com</title>"));
}
#[cfg(feature = "tls")]
#[test]
fn test_badssl_self_signed() {
let text = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build().unwrap()
.get("https://self-signed.badssl.com/")
.send().unwrap()
.text().unwrap();
assert!(text.contains("<title>self-signed.badssl.com</title>"));
}
#[cfg(feature = "default-tls")]
#[test]
fn test_badssl_wrong_host() {
let text = reqwest::Client::builder()
.danger_accept_invalid_hostnames(true)
.build().unwrap()
.get("https://wrong.host.badssl.com/")
.send().unwrap()
.text().unwrap();
assert!(text.contains("<title>wrong.host.badssl.com</title>"));
let result = reqwest::Client::builder()
.danger_accept_invalid_hostnames(true)
.build().unwrap()
.get("https://self-signed.badssl.com/")
.send();
assert!(result.is_err());
}