Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: upgrade to rustls 0.20 #1367

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,12 @@ native-tls-crate = { version = "0.2.8", optional = true, package = "native-tls"
tokio-native-tls = { version = "0.3.0", optional = true }

# rustls-tls
hyper-rustls = { version = "0.22.1", default-features = false, optional = true }
rustls = { version = "0.19", features = ["dangerous_configuration"], optional = true }
tokio-rustls = { version = "0.22", optional = true }
hyper-rustls = { version = "0.23", default-features = false, optional = true }
rustls = { version = "0.20", features = ["dangerous_configuration"], optional = true }
tokio-rustls = { version = "0.23", optional = true }
webpki-roots = { version = "0.21", optional = true }
rustls-native-certs = { version = "0.5", optional = true }
rustls-native-certs = { version = "0.6.0", optional = true }
rustls-pemfile = { version = "0.2.1", optional = true }
satyarohith marked this conversation as resolved.
Show resolved Hide resolved

## cookies
cookie_crate = { version = "0.15", package = "cookie", optional = true }
Expand Down
116 changes: 63 additions & 53 deletions src/async_impl/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ use hyper::client::ResponseFuture;
#[cfg(feature = "native-tls-crate")]
use native_tls_crate::TlsConnector;
use pin_project_lite::pin_project;
#[cfg(feature = "rustls-tls-native-roots")]
use rustls::RootCertStore;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
Expand Down Expand Up @@ -322,68 +320,86 @@ impl ClientBuilder {
TlsBackend::Rustls => {
use crate::tls::NoVerifier;

let mut tls = rustls::ClientConfig::new();
match config.http_version_pref {
HttpVersionPref::Http1 => {
tls.set_protocols(&["http/1.1".into()]);
}
HttpVersionPref::Http2 => {
tls.set_protocols(&["h2".into()]);
}
HttpVersionPref::All => {
tls.set_protocols(&["h2".into(), "http/1.1".into()]);
}
// Set root certificates.
let mut root_store = rustls::RootCertStore::empty();
for cert in config.root_certs {
cert.add_to_rustls(&mut root_store)?;
}
#[cfg(feature = "rustls-tls-webpki-roots")]
if config.tls_built_in_root_certs {
tls.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
use rustls::OwnedTrustAnchor;
let mut trust_anchors =
Vec::with_capacity(webpki_roots::TLS_SERVER_ROOTS.0.len());
for cert in webpki_roots::TLS_SERVER_ROOTS.0 {
trust_anchors.push(
OwnedTrustAnchor::from_subject_spki_name_constraints(
cert.subject,
cert.spki,
cert.name_constraints,
),
);
}
root_store.add_server_trust_anchors(trust_anchors.into_iter());
}
#[cfg(feature = "rustls-tls-native-roots")]
if config.tls_built_in_root_certs {
let roots_slice = NATIVE_ROOTS.as_ref().unwrap().roots.as_slice();
tls.root_store.roots.extend_from_slice(roots_slice);
}

if !config.certs_verification {
tls.dangerous()
.set_certificate_verifier(Arc::new(NoVerifier));
}

for cert in config.root_certs {
cert.add_to_rustls(&mut tls)?;
}

if let Some(id) = config.identity {
id.add_to_rustls(&mut tls)?;
for cert in rustls_native_certs::load_native_certs().unwrap() {
root_store
.add(&rustls::Certificate(cert.0))
.map_err(|e| crate::error::builder(e))?
}
}

// rustls does not support TLS versions <1.2 and this is unlikely to change.
// https://github.com/rustls/rustls/issues/33

// As of writing, TLS 1.2 and 1.3 are the only implemented versions and are both
// enabled by default.
// rustls 0.20 will add ALL_VERSIONS and DEFAULT_VERSIONS. That will enable a more
// sophisticated approach.
// For now we assume the default tls.versions matches the future ALL_VERSIONS and
// act based on that.

// Set TLS versions.
let mut versions = rustls::ALL_VERSIONS.to_vec();
if let Some(min_tls_version) = config.min_tls_version {
tls.versions
.retain(|&version| match tls::Version::from_rustls(version) {
versions.retain(|&supported_version| {
match tls::Version::from_rustls(supported_version.version) {
Some(version) => version >= min_tls_version,
// Assume it's so new we don't know about it, allow it
// (as of writing this is unreachable)
None => true,
});
}
});
}

if let Some(max_tls_version) = config.max_tls_version {
tls.versions
.retain(|&version| match tls::Version::from_rustls(version) {
versions.retain(|&supported_version| {
match tls::Version::from_rustls(supported_version.version) {
Some(version) => version <= max_tls_version,
None => false,
});
}
});
}

let config_builder = rustls::ClientConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_protocol_versions(&versions)
.unwrap()
.with_root_certificates(root_store);

let mut tls = if let Some(id) = config.identity {
let (key, certs) = id.get_pem()?;
config_builder.with_single_cert(certs, key).unwrap()
} else {
config_builder.with_no_client_auth()
};

if !config.certs_verification {
tls.dangerous()
.set_certificate_verifier(Arc::new(NoVerifier));
}

match config.http_version_pref {
HttpVersionPref::Http1 => {
tls.alpn_protocols = vec!["http/1.1".into()];
}
HttpVersionPref::Http2 => {
tls.alpn_protocols = vec!["h2".into()];
}
HttpVersionPref::All => {
tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
}
}

Connector::new_rustls_tls(
Expand Down Expand Up @@ -1848,12 +1864,6 @@ fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &dyn cookie::CookieS
}
}

#[cfg(feature = "rustls-tls-native-roots")]
lazy_static! {
static ref NATIVE_ROOTS: std::io::Result<RootCertStore> =
rustls_native_certs::load_native_certs().map_err(|e| e.1);
}

#[cfg(test)]
mod tests {
#[tokio::test]
Expand Down
22 changes: 11 additions & 11 deletions src/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,13 @@ impl Connector {
#[cfg(feature = "__rustls")]
Inner::RustlsTls { tls_proxy, .. } => {
if dst.scheme() == Some(&Scheme::HTTPS) {
use tokio_rustls::webpki::DNSNameRef;
use tokio_rustls::webpki::DnsNameRef;
use tokio_rustls::TlsConnector as RustlsConnector;

let tls = tls_proxy.clone();
let host = dst.host().ok_or("no host in url")?.to_string();
let conn = socks::connect(proxy, dst, dns).await?;
let dnsname = DNSNameRef::try_from_ascii_str(&host)
let dnsname = DnsNameRef::try_from_ascii_str(&host)
satyarohith marked this conversation as resolved.
Show resolved Hide resolved
.map(|dnsname| dnsname.to_owned())
.map_err(|_| "Invalid DNS Name")?;
let io = RustlsConnector::from(tls)
Expand Down Expand Up @@ -479,23 +479,24 @@ impl Connector {
tls_proxy,
} => {
if dst.scheme() == Some(&Scheme::HTTPS) {
use tokio_rustls::webpki::DNSNameRef;
use std::convert::TryFrom;
use tokio_rustls::TlsConnector as RustlsConnector;

let host = dst.host().ok_or("no host in url")?.to_string();
let host = dst.host().ok_or("no host in url")?;
let port = dst.port().map(|r| r.as_u16()).unwrap_or(443);
let http = http.clone();
let mut http = hyper_rustls::HttpsConnector::from((http, tls_proxy.clone()));
let tls = tls.clone();
let conn = http.call(proxy_dst).await?;
log::trace!("tunneling HTTPS over proxy");
let maybe_dnsname = DNSNameRef::try_from_ascii_str(&host)
.map(|dnsname| dnsname.to_owned())
let maybe_server_name = rustls::ServerName::try_from(host)
.map(|serve_name| serve_name)
.map_err(|_| "Invalid DNS Name");
let tunneled = tunnel(conn, host, port, self.user_agent.clone(), auth).await?;
let dnsname = maybe_dnsname?;
let tunneled =
tunnel(conn, host.to_string(), port, self.user_agent.clone(), auth).await?;
let serve_name = maybe_server_name?;
let io = RustlsConnector::from(tls)
.connect(dnsname.as_ref(), tunneled)
.connect(serve_name, tunneled)
.await?;

return Ok(Conn {
Expand Down Expand Up @@ -820,7 +821,6 @@ mod native_tls_conn {
mod rustls_tls_conn {
use hyper::client::connect::{Connected, Connection};
use pin_project_lite::pin_project;
use rustls::Session;
use std::{
io::{self, IoSlice},
pin::Pin,
Expand All @@ -837,7 +837,7 @@ mod rustls_tls_conn {

impl<T: Connection + AsyncRead + AsyncWrite + Unpin> Connection for RustlsTlsConn<T> {
fn connected(&self) -> Connected {
if self.inner.get_ref().1.get_alpn_protocol() == Some(b"h2") {
if self.inner.get_ref().1.alpn_protocol() == Some(b"h2") {
self.inner.get_ref().0.connected().negotiated_h2()
} else {
self.inner.get_ref().0.connected()
Expand Down
83 changes: 44 additions & 39 deletions src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@

#[cfg(feature = "__rustls")]
use rustls::{
internal::msgs::handshake::DigitallySignedStruct, HandshakeSignatureValid, RootCertStore,
ServerCertVerified, ServerCertVerifier, TLSError,
client::HandshakeSignatureValid, client::ServerCertVerifier,
internal::msgs::handshake::DigitallySignedStruct,
};
use std::fmt;
#[cfg(feature = "__rustls")]
use tokio_rustls::webpki::DNSNameRef;

/// Represents a server X509 certificate.
#[derive(Clone)]
Expand Down Expand Up @@ -111,26 +109,24 @@ impl Certificate {
}

#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> {
use rustls::internal::pemfile;
pub(crate) fn add_to_rustls(self, root_store: &mut rustls::RootCertStore) -> crate::Result<()> {
use std::io::Cursor;

match self.original {
Cert::Der(buf) => tls
.root_store
.add(&::rustls::Certificate(buf))
.map_err(|e| crate::error::builder(TLSError::WebPKIError(e)))?,
Cert::Der(buf) => root_store
.add(&rustls::Certificate(buf))
.map_err(|e| crate::error::builder(e))?,
Cert::Pem(buf) => {
let mut pem = Cursor::new(buf);
let certs = pemfile::certs(&mut pem).map_err(|_| {
crate::error::builder(TLSError::General(String::from(
let certs = rustls_pemfile::certs(&mut pem).map_err(|_| {
crate::error::builder(rustls::Error::General(String::from(
"No valid certificate was found",
)))
})?;
for c in certs {
tls.root_store
.add(&c)
.map_err(|e| crate::error::builder(TLSError::WebPKIError(e)))?;
root_store
.add(&rustls::Certificate(c))
.map_err(|e| crate::error::builder(e.to_string()))?;
}
}
}
Expand Down Expand Up @@ -207,33 +203,44 @@ impl Identity {
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
use rustls::internal::pemfile;
use std::io::Cursor;

let (key, certs) = {
let mut pem = Cursor::new(buf);
let certs = pemfile::certs(&mut pem)
.map_err(|_| TLSError::General(String::from("No valid certificate was found")))
.map_err(crate::error::builder)?;
let mut certs = Vec::new();
for cert in rustls_pemfile::certs(&mut pem)
.map_err(|_| rustls::Error::General(String::from("No valid certificate was found")))
.map_err(crate::error::builder)?
{
certs.push(rustls::Certificate(cert));
}
pem.set_position(0);
let mut sk = pemfile::pkcs8_private_keys(&mut pem)
let mut sks = Vec::new();
for sk in rustls_pemfile::pkcs8_private_keys(&mut pem)
.and_then(|pkcs8_keys| {
if pkcs8_keys.is_empty() {
Err(())
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No valid private key was found",
))
} else {
Ok(pkcs8_keys)
}
})
.or_else(|_| {
pem.set_position(0);
pemfile::rsa_private_keys(&mut pem)
rustls_pemfile::rsa_private_keys(&mut pem)
})
.map_err(|_| TLSError::General(String::from("No valid private key was found")))
.map_err(crate::error::builder)?;
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
.map_err(|_| rustls::Error::General(String::from("No valid private key was found")))
.map_err(crate::error::builder)?
{
sks.push(rustls::PrivateKey(sk));
}

if let (Some(sk), false) = (sks.pop(), certs.is_empty()) {
(sk, certs)
} else {
return Err(crate::error::builder(TLSError::General(String::from(
return Err(crate::error::builder(rustls::Error::General(String::from(
"private key or certificate not found",
))));
}
Expand All @@ -260,13 +267,9 @@ impl Identity {
}

#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> {
pub(crate) fn get_pem(self) -> crate::Result<(rustls::PrivateKey, Vec<rustls::Certificate>)> {
match self.inner {
ClientCert::Pem { key, certs } => {
tls.set_single_client_cert(certs, key)
.map_err(|e| crate::error::builder(e))?;
Ok(())
}
ClientCert::Pem { key, certs } => Ok((key, certs)),
#[cfg(feature = "native-tls")]
ClientCert::Pkcs12(..) => Err(crate::error::builder("incompatible TLS identity type")),
}
Expand Down Expand Up @@ -385,20 +388,22 @@ pub(crate) struct NoVerifier;
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_roots: &RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: DNSNameRef,
_end_entity: &rustls::Certificate,
_intermediates: &[rustls::Certificate],
_server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion())
_now: std::time::SystemTime,
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
Ok(rustls::client::ServerCertVerified::assertion())
}

fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::Certificate,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}

Expand All @@ -407,7 +412,7 @@ impl ServerCertVerifier for NoVerifier {
_message: &[u8],
_cert: &rustls::Certificate,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
) -> Result<HandshakeSignatureValid, rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
}
Expand Down