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

Rustls 0.20 and HttpsConnectorBuilder #156

Merged
merged 6 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
1 change: 0 additions & 1 deletion examples/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
//! Certificate and private key are hardcoded to sample files.
//! hyper will automatically use HTTP/2 if a client starts talking HTTP/2,
//! otherwise HTTP/1.1 will be used.
use std::pin::Pin;
use std::{env, fs, io, sync};

use async_stream::stream;
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()
g2p marked this conversation as resolved.
Show resolved Hide resolved
}

#[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()
g2p marked this conversation as resolved.
Show resolved Hide resolved
}
}
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