Skip to content

Commit

Permalink
Updates for released versions of rustls 0.20 and rustls-native-certs 0.6
Browse files Browse the repository at this point in the history
Convenience functions for rustls client configuration are now in a
ConfigBuilderExt trait extending rustls::ConfigBuilder.

Disables sct validation with certificate transparency logs, which can't
be enabled (in a way that would be as compatible as chromium) without a
bunch of intrusive policies to deal with validity/expiration.

Parts of ConfigBuilderExt::with_native_roots come from
rustls::RootCertStore::add_parsable_certificates, which cannot be
used directly due to a newtype in rustls-native-certs.
  • Loading branch information
g2p committed Nov 5, 2021
1 parent f795f11 commit f0ffcc6
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 74 deletions.
17 changes: 6 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,32 @@ repository = "https://github.com/ctz/hyper-rustls"

[dependencies]
log = "0.4.4"
ct-logs = { version = "^0.9", optional = true }
hyper = { version = "0.14", default-features = false, features = ["client", "http1"] }
rustls = { git = "https://github.com/ctz/rustls" }
rustls-native-certs = { git = "https://github.com/djc/rustls-native-certs", rev = "6116ef59f5825b0ec74a38807635a70433d68c27", optional = true }
rustls-pemfile = { version = "0.2.1" }
rustls = "0.20"
rustls-native-certs = { version = "0.6", optional = true }
tokio = "1.0"
tokio-rustls = { version = "0.23", git = "https://github.com/tokio-rs/tls", rev = "b433932bf1025960e5b99f353cf8eee4ce2f08f3" }
webpki = "0.22.0"
tokio-rustls = "0.23"
webpki-roots = { version = "0.22", optional = true }

[dev-dependencies]
async-stream = "0.3.0"
tokio = { version = "1.0", features = ["io-std", "macros", "net", "rt-multi-thread"] }
hyper = { version = "0.14", features = ["full"] }
futures-util = { version = "0.3.1", default-features = false }
rustls-pemfile = "0.2.1"

[features]
default = ["native-tokio", "http1"]
http1 = ["hyper/http1"]
http2 = ["hyper/http2"]
webpki-tokio = ["tokio-runtime", "webpki-roots"]
native-tokio = ["tokio-runtime", "rustls-native-certs"]
tokio-runtime = ["hyper/runtime", "ct-logs"]
tokio-runtime = ["hyper/runtime"]

[[example]]
name = "client"
path = "examples/client.rs"
required-features = ["native-tokio", "tokio-runtime"]
required-features = ["native-tokio", "http1"]

[[example]]
name = "server"
Expand All @@ -48,6 +46,3 @@ required-features = ["tokio-runtime"]
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[patch."crates-io"]
rustls = { git = "https://github.com/ctz/rustls" }
28 changes: 16 additions & 12 deletions examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! First parameter is the mandatory URL to GET.
//! Second parameter is an optional path to CA store.
use hyper::{body::to_bytes, client, Body, Uri};
use hyper_rustls::ConfigBuilderExt;
use rustls::RootCertStore;

use std::str::FromStr;
Expand Down Expand Up @@ -42,29 +43,32 @@ async fn run_client() -> io::Result<()> {
None => None,
};

// Prepare the HTTPS connector.
let https = match ca {
// Prepare the TLS client config
let tls = match ca {
Some(ref mut rd) => {
// Build an HTTP connector which supports HTTPS too.
let mut http = client::HttpConnector::new();
http.enforce_http(false);
// Read trust roots
let certs = rustls_pemfile::certs(rd)
.map_err(|_| error("failed to load custom CA store".into()))?;
let mut roots = RootCertStore::empty();
roots.add_parsable_certificates(&certs);
// Build a TLS client, using the custom CA store for lookups.
let tls = rustls::ClientConfig::builder()
rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots, &ct_logs::LOGS)
.with_no_client_auth();
// Join the above part into an HTTPS connector.
hyper_rustls::HttpsConnector::from((http, tls))
.with_root_certificates(roots)
.with_no_client_auth()
}
// Default HTTPS connector.
None => hyper_rustls::HttpsConnector::with_native_roots(),
None => rustls::ClientConfig::builder()
.with_safe_defaults()
.with_native_roots(),
};

// Build an HTTP connector which supports HTTPS too.
let mut http = client::HttpConnector::new();
http.enforce_http(false);

// Join the above parts into an HTTPS connector.
let https = hyper_rustls::HttpsConnector::from((http, tls));

// Build the hyper client from the HTTPS connector.
let client: client::Client<_, hyper::Body> = client::Client::builder().build(https);

Expand Down
63 changes: 63 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use rustls::{ClientConfig, ConfigBuilder, WantsVerifier};

/// Methods for configuring roots
///
/// This adds methods (gated by crate features) for easily configuring
/// TLS server roots a rustls ClientConfig will trust.
pub trait ConfigBuilderExt {
/// This configures the platform's trusted certs, as implemented by
/// rustls-native-certs
#[cfg(feature = "rustls-native-certs")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
fn with_native_roots(self) -> ClientConfig;

/// This configures the webpki roots, which are Mozilla's set of
/// trusted roots as packaged by webpki-roots.
#[cfg(feature = "webpki-roots")]
#[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
fn with_webpki_roots(self) -> ClientConfig;
}

impl ConfigBuilderExt for ConfigBuilder<ClientConfig, WantsVerifier> {
#[cfg(feature = "rustls-native-certs")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
fn with_native_roots(self) -> ClientConfig {
let mut roots = rustls::RootCertStore::empty();
let mut valid_count = 0;
let mut invalid_count = 0;

for cert in rustls_native_certs::load_native_certs().expect("could not load platform certs")
{
let cert = rustls::Certificate(cert.0);
match roots.add(&cert) {
Ok(_) => valid_count += 1,
Err(err) => {
log::trace!("invalid cert der {:?}", cert.0);
log::debug!("certificate parsing failed: {:?}", err);
invalid_count += 1
}
}
}
log::debug!(
"with_native_roots processed {} valid and {} invalid certs",
valid_count, invalid_count
);
assert!(!roots.is_empty(), "no CA certificates found");

self.with_root_certificates(roots).with_no_client_auth()
}

#[cfg(feature = "webpki-roots")]
#[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
fn with_webpki_roots(self) -> ClientConfig {
let mut roots = rustls::RootCertStore::empty();
roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
}));
self.with_root_certificates(roots).with_no_client_auth()
}
}
52 changes: 2 additions & 50 deletions src/connector.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::convert::TryFrom;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::{fmt, io};
use std::convert::TryFrom;

#[cfg(feature = "tokio-runtime")]
use hyper::client::connect::HttpConnector;
use hyper::{client::connect::Connection, service::Service, Uri};
use rustls::{ClientConfig, RootCertStore};
use rustls::ClientConfig;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_rustls::TlsConnector;

Expand All @@ -29,60 +29,12 @@ pub struct HttpsConnector<T> {
feature = "tokio-runtime"
))]
impl HttpsConnector<HttpConnector> {
/// Construct a new `HttpsConnector` using the OS root store
#[cfg(feature = "rustls-native-certs")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
pub fn with_native_roots() -> Self {
let certs = match rustls_native_certs::load_native_certs() {
Ok(certs) => certs,
Err(err) => Err(err).expect("cannot access native cert store"),
};

if certs.is_empty() {
panic!("no CA certificates found");
}

let mut roots = RootCertStore::empty();
for cert in certs {
roots.add_parsable_certificates(&[cert.0]);
}

Self::build(roots)
}

/// Construct a new `HttpsConnector` using the `webpki_roots`
#[cfg(feature = "webpki-roots")]
#[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
pub fn with_webpki_roots() -> Self {
let mut roots = rustls::RootCertStore::empty();
roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0);
Self::build(roots)
}

/// Force the use of HTTPS when connecting.
///
/// If a URL is not `https` when connecting, an error is returned. Disabled by default.
pub fn https_only(&mut self, enable: bool) {
self.force_https = enable;
}

fn build(mut config: ClientConfig) -> Self {
let mut http = HttpConnector::new();
http.enforce_http(false);

config.alpn_protocols.clear();
#[cfg(feature = "http2")]
{
config.alpn_protocols.push(b"h2".to_vec());
}

#[cfg(feature = "http1")]
{
config.alpn_protocols.push(b"http/1.1".to_vec());
}

(http, config).into()
}
}

impl<T> fmt::Debug for HttpsConnector<T> {
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@

#![cfg_attr(docsrs, feature(doc_cfg))]

mod config;
mod connector;
mod stream;

pub use crate::config::ConfigBuilderExt;
pub use crate::connector::HttpsConnector;
pub use crate::stream::MaybeHttpsStream;
1 change: 0 additions & 1 deletion src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use hyper::client::connect::{Connected, Connection};

use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio_rustls::client::TlsStream;
use tokio_rustls::rustls::{Connection as _};

/// A stream that might be protected with TLS.
pub enum MaybeHttpsStream<T> {
Expand Down

0 comments on commit f0ffcc6

Please sign in to comment.