Skip to content

Commit 3148b19

Browse files
committed
feat(hpx): add OpenSSL TLS backend with full feature parity to BoringSSL
Add a complete OpenSSL TLS backend as an alternative to BoringSSL, enabling TLS support on platforms where BoringSSL is unavailable. The backend mirrors the existing BoringSSL implementation with session caching, SNI, ALPN, hostname verification, mTLS, and keylog support. Key changes: - New openssl module with SslConnector-based TLS setup, sharded LRU session cache, and Tower Service implementations for Uri, ConnectRequest, and EstablishedConn - TlsVersion Hash impl uses discriminant matching instead of format!() to avoid heap allocation on the hot path - setup_ssl now applies tls_sni and verify_hostname settings for Service<Uri> - set_bool! macro explicitly discards set_options return value - unsafe block in setup_ssl2 annotated with # Safety comment - CertificateCompressionAlgorithm placeholder derives Debug/Clone/Copy with clarifying docs - HandshakeConfigBuilder methods marked #[must_use] - Cert store rebuild documented with API limitation rationale - Identity add_to_tls implemented for openssl::ssl::SslConnectorBuilder - PEM extraction helpers shared between boring and openssl backends - Integration test for mTLS with combined PEM identity
1 parent ad9f31f commit 3148b19

18 files changed

Lines changed: 1586 additions & 63 deletions

File tree

Cargo.toml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["bin/*", "crates/*"]
33
resolver = "3"
44

55
[workspace.package]
6-
version = "2.4.32"
6+
version = "2.4.33"
77
edition = "2024"
88
authors = ["Akagi201 <akagi201@gmail.com>"]
99
license = "Apache-2.0"
@@ -184,13 +184,13 @@ all = "warn"
184184

185185
[workspace.dependencies]
186186
# local crates
187-
hpx = { path = "crates/hpx", version = "2.4.32" }
187+
hpx = { path = "crates/hpx", version = "2.4.33" }
188188

189-
hpx-browser = { path = "crates/hpx-browser", version = "2.4.32" }
189+
hpx-browser = { path = "crates/hpx-browser", version = "2.4.33" }
190190

191-
hpx-dl = { path = "crates/hpx-dl", version = "2.4.32" }
191+
hpx-dl = { path = "crates/hpx-dl", version = "2.4.33" }
192192

193-
hpx-yawc = { path = "crates/yawc", version = "2.4.32" }
193+
hpx-yawc = { path = "crates/yawc", version = "2.4.33" }
194194

195195
# external crates
196196
ahash = "0.8.12"
@@ -242,6 +242,7 @@ md-5 = "0.11.0"
242242
memchr = "2.8.2"
243243
mime = "0.3.17"
244244
mime_guess = "2.0.5"
245+
openssl = "0.10"
245246
parking_lot = "0.12.3"
246247
percent-encoding = "2.3.2"
247248
pin-project = "1.1.11"
@@ -287,6 +288,7 @@ tempfile = "3.27.0"
287288
thiserror = "2.0.18"
288289
tokio = "1.52.1"
289290
tokio-boring = "5.0.0"
291+
tokio-openssl = "0.6"
290292
tokio-rustls = "0.26"
291293
tokio-socks = "0.5.3"
292294
tokio-test = "0.4.5"

crates/hpx/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ stealth = [
148148
"ws-yawc",
149149
"zstd",
150150
]
151+
# Enable OpenSSL support.
152+
openssl-tls = ["dep:openssl", "dep:tokio-openssl"]
153+
154+
# Enable vendored OpenSSL (static linking).
155+
openssl-vendored = ["openssl-tls", "openssl/vendored"]
151156

152157
## macOS system proxy
153158
[target.'cfg(target_os = "macos")'.dependencies]
@@ -264,8 +269,10 @@ hotpath = { workspace = true, optional = true }
264269

265270
## tracing
266271
base64 = { workspace = true }
272+
openssl = { workspace = true, optional = true }
267273
sha1 = { workspace = true, optional = true }
268274
sha2 = { workspace = true, optional = true }
275+
tokio-openssl = { workspace = true, optional = true }
269276
tracing = { workspace = true, features = ["std"], optional = true }
270277

271278
[dev-dependencies]

crates/hpx/src/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub(crate) use self::conn::{Connected, Connection};
2222
pub use self::core::http1;
2323
#[cfg(feature = "http2")]
2424
pub use self::core::http2;
25-
#[cfg(feature = "boring")]
25+
#[cfg(any(feature = "boring", feature = "openssl-tls"))]
2626
pub(crate) use self::http::ConnectIdentity;
2727
#[allow(deprecated)]
2828
pub use self::http::{

crates/hpx/src/client/conn/conn.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ impl Connection for TlsConn<TcpStream> {
140140
connected
141141
}
142142
}
143+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
144+
{
145+
let connected = self.inner.get_ref().connected();
146+
if self.inner.ssl().selected_alpn_protocol() == Some(b"h2") {
147+
connected.negotiated_h2()
148+
} else {
149+
connected
150+
}
151+
}
143152
#[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
144153
{
145154
let (io, session) = self.inner.get_ref();
@@ -150,7 +159,7 @@ impl Connection for TlsConn<TcpStream> {
150159
connected
151160
}
152161
}
153-
#[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
162+
#[cfg(not(any(feature = "boring", feature = "openssl-tls", feature = "rustls-tls")))]
154163
{
155164
self.inner.get_ref().connected()
156165
}
@@ -168,6 +177,15 @@ impl Connection for TlsConn<MaybeHttpsStream<TcpStream>> {
168177
connected
169178
}
170179
}
180+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
181+
{
182+
let connected = self.inner.get_ref().connected();
183+
if self.inner.ssl().selected_alpn_protocol() == Some(b"h2") {
184+
connected.negotiated_h2()
185+
} else {
186+
connected
187+
}
188+
}
171189
#[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
172190
{
173191
let (io, session) = self.inner.get_ref();
@@ -178,7 +196,7 @@ impl Connection for TlsConn<MaybeHttpsStream<TcpStream>> {
178196
connected
179197
}
180198
}
181-
#[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
199+
#[cfg(not(any(feature = "boring", feature = "openssl-tls", feature = "rustls-tls")))]
182200
{
183201
self.inner.get_ref().connected()
184202
}
@@ -199,6 +217,15 @@ impl Connection for TlsConn<UnixStream> {
199217
connected
200218
}
201219
}
220+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
221+
{
222+
let connected = self.inner.get_ref().connected();
223+
if self.inner.ssl().selected_alpn_protocol() == Some(b"h2") {
224+
connected.negotiated_h2()
225+
} else {
226+
connected
227+
}
228+
}
202229
#[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
203230
{
204231
let (io, session) = self.inner.get_ref();
@@ -209,7 +236,7 @@ impl Connection for TlsConn<UnixStream> {
209236
connected
210237
}
211238
}
212-
#[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
239+
#[cfg(not(any(feature = "boring", feature = "openssl-tls", feature = "rustls-tls")))]
213240
{
214241
self.inner.get_ref().connected()
215242
}
@@ -228,6 +255,15 @@ impl Connection for TlsConn<MaybeHttpsStream<UnixStream>> {
228255
connected
229256
}
230257
}
258+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
259+
{
260+
let connected = self.inner.get_ref().connected();
261+
if self.inner.ssl().selected_alpn_protocol() == Some(b"h2") {
262+
connected.negotiated_h2()
263+
} else {
264+
connected
265+
}
266+
}
231267
#[cfg(all(feature = "rustls-tls", not(feature = "boring")))]
232268
{
233269
let (io, session) = self.inner.get_ref();
@@ -238,7 +274,7 @@ impl Connection for TlsConn<MaybeHttpsStream<UnixStream>> {
238274
connected
239275
}
240276
}
241-
#[cfg(not(any(feature = "boring", feature = "rustls-tls")))]
277+
#[cfg(not(any(feature = "boring", feature = "openssl-tls", feature = "rustls-tls")))]
242278
{
243279
self.inner.get_ref().connected()
244280
}

crates/hpx/src/client/conn/tls_info.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
#[cfg(any(feature = "boring", feature = "rustls-tls"))]
1+
#[cfg(any(feature = "boring", feature = "openssl-tls", feature = "rustls-tls"))]
22
use bytes::Bytes;
33
use tokio::net::TcpStream;
44
#[cfg(unix)]
55
use tokio::net::UnixStream;
66
#[cfg(feature = "boring")]
77
use tokio_boring::SslStream;
8+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
9+
use tokio_openssl::SslStream;
810
#[cfg(feature = "rustls-tls")]
911
use tokio_rustls::client::TlsStream;
1012

@@ -36,6 +38,24 @@ fn extract_tls_info<S>(ssl_stream: &SslStream<S>) -> TlsInfo {
3638
}
3739
}
3840

41+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
42+
fn extract_tls_info<S>(ssl_stream: &SslStream<S>) -> TlsInfo {
43+
let ssl = ssl_stream.ssl();
44+
TlsInfo {
45+
peer_certificate: ssl
46+
.peer_certificate()
47+
.and_then(|cert| cert.to_der().ok())
48+
.map(Bytes::from),
49+
peer_certificate_chain: ssl.peer_cert_chain().map(|chain| {
50+
chain
51+
.iter()
52+
.filter_map(|cert| cert.to_der().ok())
53+
.map(Bytes::from)
54+
.collect()
55+
}),
56+
}
57+
}
58+
3959
// ===== impl TcpStream =====
4060

4161
impl TlsInfoFactory for TcpStream {
@@ -52,6 +72,14 @@ impl TlsInfoFactory for SslStream<TcpStream> {
5272
}
5373
}
5474

75+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
76+
impl TlsInfoFactory for SslStream<TcpStream> {
77+
#[inline]
78+
fn tls_info(&self) -> Option<TlsInfo> {
79+
Some(extract_tls_info(self))
80+
}
81+
}
82+
5583
#[cfg(feature = "rustls-tls")]
5684
impl TlsInfoFactory for TlsStream<TcpStream> {
5785
#[inline]
@@ -80,6 +108,14 @@ impl TlsInfoFactory for SslStream<MaybeHttpsStream<TcpStream>> {
80108
}
81109
}
82110

111+
#[cfg(all(feature = "openssl-tls", not(feature = "boring")))]
112+
impl TlsInfoFactory for SslStream<MaybeHttpsStream<TcpStream>> {
113+
#[inline]
114+
fn tls_info(&self) -> Option<TlsInfo> {
115+
Some(extract_tls_info(self))
116+
}
117+
}
118+
83119
#[cfg(feature = "rustls-tls")]
84120
impl TlsInfoFactory for TlsStream<MaybeHttpsStream<TcpStream>> {
85121
#[inline]
@@ -108,6 +144,14 @@ impl TlsInfoFactory for SslStream<UnixStream> {
108144
}
109145
}
110146

147+
#[cfg(all(unix, feature = "openssl-tls", not(feature = "boring")))]
148+
impl TlsInfoFactory for SslStream<UnixStream> {
149+
#[inline]
150+
fn tls_info(&self) -> Option<TlsInfo> {
151+
Some(extract_tls_info(self))
152+
}
153+
}
154+
111155
#[cfg(all(unix, feature = "rustls-tls"))]
112156
impl TlsInfoFactory for TlsStream<UnixStream> {
113157
#[inline]
@@ -137,6 +181,14 @@ impl TlsInfoFactory for SslStream<MaybeHttpsStream<UnixStream>> {
137181
}
138182
}
139183

184+
#[cfg(all(unix, feature = "openssl-tls", not(feature = "boring")))]
185+
impl TlsInfoFactory for SslStream<MaybeHttpsStream<UnixStream>> {
186+
#[inline]
187+
fn tls_info(&self) -> Option<TlsInfo> {
188+
Some(extract_tls_info(self))
189+
}
190+
}
191+
140192
#[cfg(all(unix, feature = "rustls-tls"))]
141193
impl TlsInfoFactory for TlsStream<MaybeHttpsStream<UnixStream>> {
142194
#[inline]

crates/hpx/src/client/http.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use tower::{
2222
util::{BoxCloneSyncService, BoxCloneSyncServiceLayer, MapErr, Oneshot},
2323
};
2424

25-
#[cfg(feature = "boring")]
25+
#[cfg(any(feature = "boring", feature = "openssl-tls"))]
2626
pub(crate) use self::client::extra::ConnectIdentity;
2727
pub(crate) use self::client::{ConnectRequest, HttpClient, extra::ConnectExtra};
2828
#[allow(deprecated)]

0 commit comments

Comments
 (0)