Add rustls support (#390)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user