rust: bssl-tls: Introduce hyper support Signed-off-by: Xiangfei Ding <xfding@google.com> Change-Id: I1987731cefdf073ea08fb9c698cfa71d6a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/100047 Reviewed-by: Adam Langley <agl@google.com> Reviewed-by: Rudolf Polzer <rpolzer@google.com>
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f24e291..c5e4524 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock
@@ -3,6 +3,12 @@ version = 4 [[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "bitflags" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -58,7 +64,10 @@ "bssl-tls", "bssl-x509", "futures", + "hyper", + "hyper-util", "tokio", + "tower", ] [[package]] @@ -100,6 +109,12 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -116,6 +131,12 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] name = "futures" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -215,6 +236,115 @@ ] [[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -495,11 +625,48 @@ ] [[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -552,6 +719,12 @@ ] [[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -570,6 +743,15 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/rust/bssl-tls-tokio/Cargo.toml b/rust/bssl-tls-tokio/Cargo.toml index 33af238..93dfac3 100644 --- a/rust/bssl-tls-tokio/Cargo.toml +++ b/rust/bssl-tls-tokio/Cargo.toml
@@ -5,11 +5,31 @@ publish = false license = "Apache-2.0" -[dependencies] -tokio = { version = "1.0", default-features = false, features = ["net"] } -bssl-tls = { path = "../bssl-tls" } +[dependencies.tokio] +version = "1.0" +default-features = false +features = ["net"] + +[dependencies.bssl-tls] +path = "../bssl-tls" +features = ["std"] + +[dependencies.hyper] +version = "1.0" +optional = true +features = ["client", "http1", "http2"] + +[dependencies.tower] +version = "0.4" +optional = true [dev-dependencies] tokio = { version = "1.0", features = ["full"] } futures = "0.3" bssl-x509 = { path = "../bssl-x509" } +hyper = { version = "1.0", features = ["client", "server", "http1", "http2"] } +hyper-util = { version = "0.1", features = ["tokio"] } + +[features] +# `hyper` enables interop with `hyper` 1.0 and `tower` +hyper = ["dep:hyper", "dep:tower"]
diff --git a/rust/bssl-tls-tokio/src/hyper.rs b/rust/bssl-tls-tokio/src/hyper.rs new file mode 100644 index 0000000..850d155 --- /dev/null +++ b/rust/bssl-tls-tokio/src/hyper.rs
@@ -0,0 +1,92 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Hyper support + +use crate::TlsConnector; + +use std::{ + error::Error, + fmt::Debug, + future::Future, + pin::Pin, + sync::Arc, + task::{ + Context, + Poll, // + }, // +}; + +use hyper::http; +use tower::Service; + +/// A connector for `hyper` using `bssl-tls`. +#[derive(Clone)] +pub struct HyperBsslConnector<Inner> { + inner: Inner, + connector: Arc<TlsConnector>, +} + +impl<Inner: Debug> std::fmt::Debug for HyperBsslConnector<Inner> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HyperBsslConnector") + .field("inner", &self.inner) + .finish() + } +} + +impl<Inner> HyperBsslConnector<Inner> { + /// Construct a new `HyperBsslConnector`. + pub fn new(inner: Inner, connector: TlsConnector) -> Self { + Self { + inner, + connector: Arc::new(connector), + } + } +} + +impl<Inner> Service<http::Uri> for HyperBsslConnector<Inner> +where + Inner: Service<http::Uri>, + Inner::Response: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + Sync + 'static, + Inner::Future: Send + 'static, + Inner::Error: Into<Box<dyn Error + Send + Sync>>, +{ + type Response = crate::TlsStream<bssl_tls::connection::Client, Inner::Response>; + type Error = Box<dyn Error + Send + Sync>; + type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { + self.inner.poll_ready(cx).map_err(Into::into) + } + + fn call(&mut self, uri: http::Uri) -> Self::Future { + let domain = uri + .host() + .unwrap_or("") + .trim_start_matches('[') + .trim_end_matches(']') + .to_string(); + if domain.is_empty() { + return Box::pin(std::future::ready(Err("empty domain".into()))); + } + let fut = self.inner.call(uri); + let connector = self.connector.clone(); + + Box::pin(async move { + let stream = fut.await.map_err(Into::into)?; + Ok(connector.connect(&domain, stream).await?) + }) + } +}
diff --git a/rust/bssl-tls-tokio/src/lib.rs b/rust/bssl-tls-tokio/src/lib.rs index 9656a83..c1fe4c2 100644 --- a/rust/bssl-tls-tokio/src/lib.rs +++ b/rust/bssl-tls-tokio/src/lib.rs
@@ -629,3 +629,6 @@ TlsAcceptor::new(self.build()) } } + +#[cfg(feature = "hyper")] +pub mod hyper;
diff --git a/rust/bssl-tls-tokio/src/tests.rs b/rust/bssl-tls-tokio/src/tests.rs index 0aa204e..408c67c 100644 --- a/rust/bssl-tls-tokio/src/tests.rs +++ b/rust/bssl-tls-tokio/src/tests.rs
@@ -19,3 +19,48 @@ const CA: &[u8] = include_bytes!("../../test-data/BoringSSLCATest.crt"); const RSA_SERVER_CERT: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.crt"); const RSA_SERVER_KEY: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.key"); + +use bssl_tls::{ + context::TlsContextBuilder, + credentials::{ + Certificate, + TlsCredentialBuilder, // + }, +}; +use bssl_x509::{ + certificates::X509Certificate, + keys::PrivateKey, + params::Trust, + store::X509StoreBuilder, // +}; + +/// Returns a `(server_ctx_builder, client_ctx_builder)` pair with certs/keys +/// pre-configured for TLS testing. +fn tls_ctx_builders() -> (TlsContextBuilder, TlsContextBuilder) { + let ca = Certificate::parse_one_from_pem(CA, None).unwrap(); + let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None).unwrap(); + let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!()).unwrap(); + + let mut server_builder = TlsContextBuilder::new_tls(); + let server_cred = { + let mut builder = TlsCredentialBuilder::new(); + builder + .with_certificate_chain(&[server_cert, ca]) + .unwrap() + .with_private_key(server_key) + .unwrap(); + builder.build().unwrap() + }; + server_builder.with_credential(server_cred).unwrap(); + + let mut client_builder = TlsContextBuilder::new_tls(); + let mut cert_store = X509StoreBuilder::new(); + cert_store + .set_trust(Trust::SslServer) + .unwrap() + .add_cert(X509Certificate::parse_one_from_pem(CA).unwrap()) + .unwrap(); + client_builder.with_certificate_store(&cert_store.build()); + + (server_builder, client_builder) +}
diff --git a/rust/bssl-tls-tokio/src/tests/convenience.rs b/rust/bssl-tls-tokio/src/tests/convenience.rs index 12a080f..7d459b0 100644 --- a/rust/bssl-tls-tokio/src/tests/convenience.rs +++ b/rust/bssl-tls-tokio/src/tests/convenience.rs
@@ -12,56 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use bssl_tls::{ - context::TlsContextBuilder, - credentials::{ - Certificate, - TlsCredentialBuilder, // - }, // -}; -use bssl_x509::{ - certificates::X509Certificate, - keys::PrivateKey, - params::Trust, - store::X509StoreBuilder, // -}; use tokio::io::{ AsyncReadExt, AsyncWriteExt, // }; -use super::{ - CA, - RSA_SERVER_CERT, - RSA_SERVER_KEY, // -}; use crate::TokioTlsExt; #[tokio::test] async fn high_level_tokio() -> Result<(), bssl_tls::errors::Error> { - let ca = Certificate::parse_one_from_pem(CA, None)?; - let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None)?; - let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!())?; - - let mut server_ctx_builder = TlsContextBuilder::new_tls(); - let server_cred = { - let mut builder = TlsCredentialBuilder::new(); - builder - .with_certificate_chain(&[server_cert, ca])? - .with_private_key(server_key)?; - builder.build() - }; - server_ctx_builder.with_credential(server_cred.unwrap())?; - let mut builder = TlsContextBuilder::new_tls(); - let ca = X509Certificate::parse_one_from_pem(CA)?; - let store = { - let mut store = X509StoreBuilder::new(); - store.set_trust(Trust::SslServer)?.add_cert(ca)?; - store.build() - }; - builder.with_certificate_store(&store); - let connector = builder.build_tokio_connector(); - let acceptor = server_ctx_builder.build_tokio_acceptor(); + let (server_builder, client_builder) = super::tls_ctx_builders(); + let connector = client_builder.build_tokio_connector(); + let acceptor = server_builder.build_tokio_acceptor(); let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap();
diff --git a/rust/bssl-tls-tokio/src/tests/datagram.rs b/rust/bssl-tls-tokio/src/tests/datagram.rs index 77a18e3..e88ea33 100644 --- a/rust/bssl-tls-tokio/src/tests/datagram.rs +++ b/rust/bssl-tls-tokio/src/tests/datagram.rs
@@ -47,39 +47,42 @@ new_std_datagram_with_tokio, // }; -fn dumb_dtls_server_client() -> Result< - ( - TlsConnection<Server, DtlsMode>, - TlsConnection<Client, DtlsMode>, - ), - Error, -> { - let ca = Certificate::parse_one_from_pem(CA, None)?; - let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None)?; - let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!())?; +fn dumb_dtls_server_client() -> ( + TlsConnection<Server, DtlsMode>, + TlsConnection<Client, DtlsMode>, +) { + let ca = Certificate::parse_one_from_pem(CA, None).unwrap(); + let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None).unwrap(); + let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!()).unwrap(); let mut server_ctx_builder = TlsContextBuilder::new_dtls(); let server_cred = { let mut builder = TlsCredentialBuilder::new(); builder - .with_certificate_chain(&[server_cert, ca])? - .with_private_key(server_key)?; - builder.build() + .with_certificate_chain(&[server_cert, ca]) + .unwrap() + .with_private_key(server_key) + .unwrap(); + builder.build().unwrap() }; - server_ctx_builder.with_credential(server_cred.unwrap())?; + server_ctx_builder.with_credential(server_cred).unwrap(); let server_ctx = server_ctx_builder.build(); let server_conn = server_ctx.new_server_connection().build(); let mut client_ctx_builder = TlsContextBuilder::new_dtls(); - let ca = X509Certificate::parse_one_from_pem(CA)?; + let ca = X509Certificate::parse_one_from_pem(CA).unwrap(); let mut cert_store = X509StoreBuilder::new(); - cert_store.set_trust(Trust::SslServer)?.add_cert(ca)?; + cert_store + .set_trust(Trust::SslServer) + .unwrap() + .add_cert(ca) + .unwrap(); let cert_store = cert_store.build(); client_ctx_builder.with_certificate_store(&cert_store); let client_ctx = client_ctx_builder.build(); let client_conn = client_ctx.new_client_connection().build(); - Ok((server_conn, client_conn)) + (server_conn, client_conn) } async fn async_ping_pong( @@ -146,7 +149,7 @@ #[tokio::test] #[ignore = "https://crbug.com/532601068"] async fn async_dtls() -> Result<(), Error> { - let (mut server_conn, mut client_conn) = dumb_dtls_server_client().unwrap(); + let (mut server_conn, mut client_conn) = dumb_dtls_server_client(); let (server_sock, client_sock) = tokio::net::UnixDatagram::pair().unwrap(); server_conn.set_io(TokioDatagramIo(server_sock)).unwrap(); client_conn.set_io(TokioDatagramIo(client_sock)).unwrap(); @@ -158,7 +161,7 @@ #[tokio::test] #[ignore = "https://crbug.com/532601068"] async fn async_dtls_over_fd() -> Result<(), Error> { - let (mut server_conn, mut client_conn) = dumb_dtls_server_client().unwrap(); + let (mut server_conn, mut client_conn) = dumb_dtls_server_client(); let (server_sock, client_sock) = std::os::unix::net::UnixDatagram::pair().unwrap(); server_sock.set_nonblocking(true).unwrap(); client_sock.set_nonblocking(true).unwrap();
diff --git a/rust/bssl-tls-tokio/src/tests/transport.rs b/rust/bssl-tls-tokio/src/tests/transport.rs index b094c55..5301825 100644 --- a/rust/bssl-tls-tokio/src/tests/transport.rs +++ b/rust/bssl-tls-tokio/src/tests/transport.rs
@@ -18,69 +18,30 @@ Server, TlsConnection, // }, - context::TlsContextBuilder, - credentials::{ - Certificate, - TlsCredentialBuilder, // - }, errors::Error, // }; -use bssl_x509::{ - certificates::X509Certificate, - keys::PrivateKey, - params::Trust, - store::X509StoreBuilder, // -}; use futures::future::FutureExt; use tokio::io::{ AsyncReadExt, AsyncWriteExt, // }; -use super::{ - CA, - RSA_SERVER_CERT, - RSA_SERVER_KEY, // -}; use crate::{ TokioIo, TokioTlsConnection, // }; -fn dumb_server_client() -> Result<(TlsConnection<Server>, TlsConnection<Client>), Error> { - let ca = Certificate::parse_one_from_pem(CA, None)?; - let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None)?; - let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!())?; - - let mut server_ctx_builder = TlsContextBuilder::new_tls(); - let server_cred = { - let mut builder = TlsCredentialBuilder::new(); - builder - .with_certificate_chain(&[server_cert, ca])? - .with_private_key(server_key)?; - builder.build() - }; - server_ctx_builder.with_credential(server_cred.unwrap())?; - let server_ctx = server_ctx_builder.build(); - let server_conn = server_ctx.new_server_connection().build(); - - let mut client_ctx_builder = TlsContextBuilder::new_tls(); - let mut cert_store = X509StoreBuilder::new(); - cert_store - .set_trust(Trust::SslServer)? - .add_cert(X509Certificate::parse_one_from_pem(&CA)?)?; - let cert_store = cert_store.build(); - client_ctx_builder.with_certificate_store(&cert_store); - let client_ctx = client_ctx_builder.build(); - let client_conn = client_ctx.new_client_connection().build(); - - Ok((server_conn, client_conn)) +fn dumb_server_client() -> (TlsConnection<Server>, TlsConnection<Client>) { + let (server_builder, client_builder) = super::tls_ctx_builders(); + let server_conn = server_builder.build().new_server_connection().build(); + let client_conn = client_builder.build().new_client_connection().build(); + (server_conn, client_conn) } #[cfg(unix)] #[tokio::test] async fn tokio_io() -> Result<(), Error> { - let (server_conn, client_conn) = dumb_server_client()?; + let (server_conn, client_conn) = dumb_server_client(); let (server_tx, server_rx) = tokio::net::unix::pipe::pipe().unwrap(); let (client_tx, client_rx) = tokio::net::unix::pipe::pipe().unwrap();
diff --git a/rust/bssl-tls-tokio/tests/hyper.rs b/rust/bssl-tls-tokio/tests/hyper.rs new file mode 100644 index 0000000..27b52a7 --- /dev/null +++ b/rust/bssl-tls-tokio/tests/hyper.rs
@@ -0,0 +1,184 @@ +// Copyright 2026 The BoringSSL Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "hyper")] + +use bssl_tls::alpn::H2; +use bssl_tls::context::TlsContextBuilder; +use bssl_tls::credentials::{Certificate, TlsCredentialBuilder}; +use bssl_tls_tokio::TokioTlsExt; +use bssl_tls_tokio::hyper::HyperBsslConnector; +use bssl_x509::{ + certificates::X509Certificate, keys::PrivateKey, params::Trust, store::X509StoreBuilder, +}; +use hyper::body::{Body, Bytes, Frame}; +use hyper::service::service_fn; +use hyper_util::rt::TokioIo as HyperTokioIo; +use std::convert::Infallible; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::net::{TcpListener, TcpStream}; +use tower::Service; + +const CA: &[u8] = include_bytes!("../../test-data/BoringSSLCATest.crt"); +const RSA_SERVER_CERT: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.crt"); +const RSA_SERVER_KEY: &[u8] = include_bytes!("../../test-data/BoringSSLServerTest-RSA.key"); + +/// A body that yields a single data frame, or is empty. +struct SimpleBody(Option<Bytes>); + +impl SimpleBody { + fn new(data: &'static str) -> Self { + Self(Some(Bytes::from(data))) + } + + fn empty() -> Self { + Self(None) + } +} + +impl Body for SimpleBody { + type Data = Bytes; + type Error = Infallible; + + fn poll_frame( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> { + match self.0.take() { + Some(data) => Poll::Ready(Some(Ok(Frame::data(data)))), + None => Poll::Ready(None), + } + } +} + +/// A mock TCP connector that connects to a fixed address, implementing +/// `tower::Service<Uri>` so it can be wrapped in [`HyperBsslConnector`]. +struct MockTcpConnector { + addr: std::net::SocketAddr, +} + +impl Service<hyper::http::Uri> for MockTcpConnector { + type Response = TcpStream; + type Error = std::io::Error; + type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _: hyper::http::Uri) -> Self::Future { + let addr = self.addr; + Box::pin(TcpStream::connect(addr)) + } +} + +/// Sends an HTTP/2 request over TLS using `HyperBsslConnector` and verifies +/// that a hyper HTTP/2 server receives and responds correctly. +#[tokio::test] +async fn test_hyper_h2_roundtrip() { + // Bind to an ephemeral port. + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let addr = listener.local_addr().unwrap(); + + // Set up the TLS server context. + let ca = Certificate::parse_one_from_pem(CA, None).unwrap(); + let server_cert = Certificate::parse_one_from_pem(RSA_SERVER_CERT, None).unwrap(); + let server_key = PrivateKey::from_pem(RSA_SERVER_KEY, || unreachable!()).unwrap(); + let mut server_ctx_builder = TlsContextBuilder::new_tls(); + let server_cred = { + let mut builder = TlsCredentialBuilder::new(); + builder + .with_certificate_chain(&[server_cert, ca]) + .unwrap() + .with_private_key(server_key) + .unwrap(); + builder.build().unwrap() + }; + server_ctx_builder.with_credential(server_cred).unwrap(); + server_ctx_builder.set_alpn_protocols([H2]).unwrap(); + let acceptor = server_ctx_builder.build_tokio_acceptor(); + + // Server: accept a connection, do TLS, then serve one HTTP/2 request. + let server_fut = async move { + let (stream, _) = listener.accept().await.unwrap(); + let tls_stream = acceptor.accept(stream).await.unwrap(); + + hyper::server::conn::http2::Builder::new(hyper_util::rt::TokioExecutor::new()) + .serve_connection( + HyperTokioIo::new(tls_stream), + service_fn(|_req| async { + Ok::<_, hyper::Error>(hyper::Response::new(SimpleBody::new( + "hello from h2 server", + ))) + }), + ) + .await + .unwrap(); + }; + + // Client: use HyperBsslConnector to establish TLS, then do HTTP/2. + let client_fut = async move { + let mock_connector = MockTcpConnector { addr }; + + let mut client_ctx_builder = TlsContextBuilder::new_tls(); + let mut cert_store = X509StoreBuilder::new(); + cert_store + .set_trust(Trust::SslServer) + .unwrap() + .add_cert(X509Certificate::parse_one_from_pem(CA).unwrap()) + .unwrap(); + client_ctx_builder.with_certificate_store(&cert_store.build()); + client_ctx_builder.set_alpn_protocols([H2]).unwrap(); + let connector = client_ctx_builder.build_tokio_connector(); + + let mut hyper_connector = HyperBsslConnector::new(mock_connector, connector); + let tls_stream = hyper_connector + .call("https://localhost/".parse().unwrap()) + .await + .unwrap(); + + let (mut sender, conn) = hyper::client::conn::http2::handshake( + hyper_util::rt::TokioExecutor::new(), + HyperTokioIo::new(tls_stream), + ) + .await + .unwrap(); + + // Drive the connection in the background. + tokio::spawn(async move { + conn.await.unwrap(); + }); + + let req = hyper::Request::get("/").body(SimpleBody::empty()).unwrap(); + let resp = sender.send_request(req).await.unwrap(); + assert_eq!(resp.status(), 200); + + // Read the response body frame by frame. + let body = resp.into_body(); + let mut body = std::pin::pin!(body); + let mut result = Vec::new(); + while let Some(frame) = futures::future::poll_fn(|cx| body.as_mut().poll_frame(cx)).await { + if let Ok(frame) = frame { + if let Some(data) = frame.data_ref() { + result.extend_from_slice(data); + } + } + } + assert_eq!(result, b"hello from h2 server"); + }; + + tokio::join!(server_fut, client_fut); +}