diff --git a/Cargo.toml b/Cargo.toml index fff11c5..2294a39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ rustls-tls-native-roots = ["rustls-native-certs", "__rustls"] blocking = ["futures-util/io", "tokio/rt-multi-thread", "tokio/sync"] -cookies = ["cookie_crate", "cookie_store"] +cookies = ["cookie_crate", "cookie_store", "proc-macro-hack"] gzip = ["async-compression", "async-compression/gzip", "tokio-util"] @@ -70,7 +70,7 @@ __tls = [] # Enables common rustls code. # Equivalent to rustls-tls-manual-roots but shorter :) -__rustls = ["hyper-rustls", "tokio-rustls", "rustls", "__tls"] +__rustls = ["hyper-rustls", "tokio-rustls", "rustls", "__tls", "rustls-pemfile"] # When enabled, disable using the cached SYS_PROXIES. __internal_proxy_sys_no_cache = [] @@ -112,15 +112,17 @@ 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", optional = true } +rustls-pemfile = { version = "0.2", optional = true } ## cookies cookie_crate = { version = "0.15", package = "cookie", optional = true } cookie_store = { version = "0.15", optional = true } +proc-macro-hack = { version = "0.5.19", optional = true } ## compression async-compression = { version = "0.3.7", default-features = false, features = ["tokio"], optional = true } diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 1da6961..d1526d9 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -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}; @@ -322,68 +320,94 @@ 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()]); - } - } - #[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); - } - #[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); + // Set root certificates. + let mut root_cert_store = rustls::RootCertStore::empty(); + for cert in config.root_certs { + cert.add_to_rustls(&mut root_cert_store)?; } + #[cfg(feature = "rustls-tls-webpki-roots")] + if config.tls_built_in_root_certs { + use rustls::OwnedTrustAnchor; + + let trust_anchors = + webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|trust_anchor| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + trust_anchor.subject, + trust_anchor.spki, + trust_anchor.name_constraints, + ) + }); + + root_cert_store.add_server_trust_anchors(trust_anchors); + } + + #[cfg(feature = "rustls-tls-native-roots")] + if config.tls_built_in_root_certs { + for cert in rustls_native_certs::load_native_certs() + .map_err(crate::error::builder)? + { + root_cert_store + .add(&rustls::Certificate(cert.0)) + .map_err(crate::error::builder)? + } + } + + // Set TLS versions. + let mut versions = rustls::ALL_VERSIONS.to_vec(); + + if let Some(min_tls_version) = config.min_tls_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 { + versions.retain(|&supported_version| { + match tls::Version::from_rustls(supported_version.version) { + Some(version) => version <= max_tls_version, + None => false, + } + }); + } + + // Build TLS config + let config_builder = rustls::ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_protocol_versions(&versions) + .map_err(crate::error::builder)? + .with_root_certificates(root_cert_store); + + // Finalize TLS config + let mut tls = if let Some(id) = config.identity { + id.add_to_rustls(config_builder)? + } else { + config_builder.with_no_client_auth() + }; + + // Certificate verifier 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)?; - } - - // 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. - - if let Some(min_tls_version) = config.min_tls_version { - tls.versions - .retain(|&version| match tls::Version::from_rustls(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) { - Some(version) => version <= max_tls_version, - None => false, - }); + // ALPN protocol + 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( @@ -1848,12 +1872,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 = - rustls_native_certs::load_native_certs().map_err(|e| e.1); -} - #[cfg(test)] mod tests { #[tokio::test] diff --git a/src/connect.rs b/src/connect.rs index 7662a47..4f2c3db 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -325,17 +325,16 @@ impl Connector { #[cfg(feature = "__rustls")] Inner::RustlsTls { tls_proxy, .. } => { if dst.scheme() == Some(&Scheme::HTTPS) { - use tokio_rustls::webpki::DNSNameRef; + use std::convert::TryFrom; 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) - .map(|dnsname| dnsname.to_owned()) - .map_err(|_| "Invalid DNS Name")?; + let server_name = rustls::ServerName::try_from(host.as_str()) + .map_err(|_| "Invalid Server Name")?; let io = RustlsConnector::from(tls) - .connect(dnsname.as_ref(), conn) + .connect(server_name, conn) .await?; return Ok(Conn { inner: self.verbose.wrap(RustlsTlsConn { inner: io }), @@ -479,7 +478,8 @@ impl Connector { tls_proxy, } => { if dst.scheme() == Some(&Scheme::HTTPS) { - use tokio_rustls::webpki::DNSNameRef; + use rustls::ServerName; + use std::convert::TryFrom; use tokio_rustls::TlsConnector as RustlsConnector; let host = dst.host().ok_or("no host in url")?.to_string(); @@ -489,13 +489,12 @@ impl Connector { 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()) - .map_err(|_| "Invalid DNS Name"); + let maybe_server_name = + ServerName::try_from(host.as_str()).map_err(|_| "Invalid Server Name"); let tunneled = tunnel(conn, host, port, self.user_agent.clone(), auth).await?; - let dnsname = maybe_dnsname?; + let server_name = maybe_server_name?; let io = RustlsConnector::from(tls) - .connect(dnsname.as_ref(), tunneled) + .connect(server_name, tunneled) .await?; return Ok(Conn { @@ -820,7 +819,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, @@ -837,7 +835,7 @@ mod rustls_tls_conn { impl Connection for RustlsTlsConn { 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() diff --git a/src/tls.rs b/src/tls.rs index 4a92dfa..ce921f6 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -13,12 +13,10 @@ #[cfg(feature = "__rustls")] use rustls::{ - internal::msgs::handshake::DigitallySignedStruct, HandshakeSignatureValid, RootCertStore, - ServerCertVerified, ServerCertVerifier, TLSError, + client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier, + internal::msgs::handshake::DigitallySignedStruct, Error as TLSError, ServerName, }; use std::fmt; -#[cfg(feature = "__rustls")] -use tokio_rustls::webpki::DNSNameRef; /// Represents a server X509 certificate. #[derive(Clone)] @@ -111,26 +109,27 @@ 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_cert_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_cert_store + .add(&rustls::Certificate(buf)) + .map_err(crate::error::builder)?, Cert::Pem(buf) => { let mut pem = Cursor::new(buf); - let certs = pemfile::certs(&mut pem).map_err(|_| { + let certs = rustls_pemfile::certs(&mut pem).map_err(|_| { crate::error::builder(TLSError::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_cert_store + .add(&rustls::Certificate(c)) + .map_err(crate::error::builder)?; } } } @@ -207,29 +206,37 @@ impl Identity { /// This requires the `rustls-tls(-...)` Cargo feature enabled. #[cfg(feature = "__rustls")] pub fn from_pem(buf: &[u8]) -> crate::Result { - use rustls::internal::pemfile; use std::io::Cursor; let (key, certs) = { let mut pem = Cursor::new(buf); - let certs = pemfile::certs(&mut pem) + let certs = rustls_pemfile::certs(&mut pem) .map_err(|_| TLSError::General(String::from("No valid certificate was found"))) - .map_err(crate::error::builder)?; + .map_err(crate::error::builder)? + .into_iter() + .map(rustls::Certificate) + .collect::>(); pem.set_position(0); - let mut sk = pemfile::pkcs8_private_keys(&mut pem) + let mut sk = 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)?; + .map_err(crate::error::builder)? + .into_iter() + .map(rustls::PrivateKey) + .collect::>(); if let (Some(sk), false) = (sk.pop(), certs.is_empty()) { (sk, certs) } else { @@ -260,13 +267,17 @@ impl Identity { } #[cfg(feature = "__rustls")] - pub(crate) fn add_to_rustls(self, tls: &mut rustls::ClientConfig) -> crate::Result<()> { + pub(crate) fn add_to_rustls( + self, + config_builder: rustls::ConfigBuilder< + rustls::ClientConfig, + rustls::client::WantsTransparencyPolicyOrClientCert, + >, + ) -> crate::Result { 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 } => config_builder + .with_single_cert(certs, key) + .map_err(crate::error::builder), #[cfg(feature = "native-tls")] ClientCert::Pkcs12(..) => Err(crate::error::builder("incompatible TLS identity type")), } @@ -385,10 +396,12 @@ 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: &ServerName, + _scts: &mut dyn Iterator, _ocsp_response: &[u8], + _now: std::time::SystemTime, ) -> Result { Ok(ServerCertVerified::assertion()) } diff --git a/tests/client.rs b/tests/client.rs index 6219d03..8a663fd 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -246,7 +246,11 @@ fn use_preconfigured_native_tls_default() { fn use_preconfigured_rustls_default() { extern crate rustls; - let tls = rustls::ClientConfig::new(); + let root_cert_store = rustls::RootCertStore::empty(); + let tls = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_cert_store) + .with_no_client_auth(); reqwest::Client::builder() .use_preconfigured_tls(tls)