From 2bb928191440f4201ea858f3bc4383e0741fed51 Mon Sep 17 00:00:00 2001 From: netliomax25-code Date: Thu, 18 Jun 2026 15:25:39 +0530 Subject: [PATCH 1/2] fix tls server name extraction for ipv6 authorities --- crates/wasi-http/src/lib.rs | 45 ++++++++++++++++++++++++++++++ crates/wasi-http/src/p2/mod.rs | 4 +-- crates/wasi-http/src/p3/request.rs | 4 +-- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs index 0b40aa8694a2..bd1e1569264e 100644 --- a/crates/wasi-http/src/lib.rs +++ b/crates/wasi-http/src/lib.rs @@ -71,6 +71,51 @@ mod content_length_tests { } } +/// Extract the host portion of an `authority` for use as a TLS server name. +/// +/// `authority` is in `host` or `host:port` form, where an IPv6 `host` is +/// wrapped in brackets (for example `[::1]:443`). Splitting on the first `:` is +/// wrong for the bracketed IPv6 form, so handle it explicitly and return the +/// bare address without brackets, which is what `rustls`' `ServerName` expects. +#[cfg(all(feature = "default-send-request", any(feature = "p2", feature = "p3")))] +fn tls_server_name(authority: &str) -> &str { + if let Some(rest) = authority.strip_prefix('[') { + if let Some(end) = rest.find(']') { + return &rest[..end]; + } + } + match authority.split_once(':') { + Some((host, _port)) => host, + None => authority, + } +} + +#[cfg(all( + test, + feature = "default-send-request", + any(feature = "p2", feature = "p3") +))] +mod tls_server_name_tests { + use super::tls_server_name; + + #[test] + fn extracts_host_from_authority() { + assert_eq!(tls_server_name("example.com"), "example.com"); + assert_eq!(tls_server_name("example.com:443"), "example.com"); + assert_eq!(tls_server_name("127.0.0.1:80"), "127.0.0.1"); + + // An IPv6 literal is bracketed; the brackets must be dropped and the + // whole address kept rather than truncated at the first `:`. + assert_eq!(tls_server_name("[::1]:443"), "::1"); + assert_eq!(tls_server_name("[2001:db8::1]:8443"), "2001:db8::1"); + assert_eq!(tls_server_name("[::1]"), "::1"); + + // Matches what a parsed authority produces for an IPv6 host. + let authority = http::uri::Authority::from_static("[2001:db8::1]:8443"); + assert_eq!(tls_server_name(&authority.to_string()), "2001:db8::1"); + } +} + /// Set of [http::header::HeaderName], that are forbidden by default /// for requests and responses originating in the guest. pub const DEFAULT_FORBIDDEN_HEADERS: [HeaderName; 9] = [ diff --git a/crates/wasi-http/src/p2/mod.rs b/crates/wasi-http/src/p2/mod.rs index 0e6a4f268416..160e142c2b98 100644 --- a/crates/wasi-http/src/p2/mod.rs +++ b/crates/wasi-http/src/p2/mod.rs @@ -630,9 +630,7 @@ pub async fn default_send_request_handler( .with_root_certificates(root_cert_store) .with_no_client_auth(); let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config)); - let mut parts = authority.split(":"); - let host = parts.next().unwrap_or(&authority); - let domain = ServerName::try_from(host) + let domain = ServerName::try_from(crate::tls_server_name(&authority)) .map_err(|e| { tracing::warn!("dns lookup error: {e:?}"); dns_error("invalid dns name".to_string(), 0) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index aa3ed706abbb..53943ba8dac0 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -356,9 +356,7 @@ pub async fn default_send_request( .with_root_certificates(root_cert_store) .with_no_client_auth(); let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config)); - let mut parts = authority.split(":"); - let host = parts.next().unwrap_or(&authority); - let domain = ServerName::try_from(host) + let domain = ServerName::try_from(crate::tls_server_name(&authority)) .map_err(|e| { tracing::warn!("dns lookup error: {e:?}"); dns_error("invalid dns name".to_string(), 0) From 0351af0c533877c21d59a03aaf51a4851f93162b Mon Sep 17 00:00:00 2001 From: netliomax25-code Date: Fri, 19 Jun 2026 16:29:26 +0530 Subject: [PATCH 2/2] wasi-http: return ServerName directly from tls_server_name --- crates/wasi-http/src/lib.rs | 71 +++++++++++++++++++----------- crates/wasi-http/src/p2/mod.rs | 12 ++--- crates/wasi-http/src/p3/request.rs | 12 ++--- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs index bd1e1569264e..874ccbe5c3ed 100644 --- a/crates/wasi-http/src/lib.rs +++ b/crates/wasi-http/src/lib.rs @@ -71,23 +71,32 @@ mod content_length_tests { } } -/// Extract the host portion of an `authority` for use as a TLS server name. +/// Resolve the rustls [`ServerName`] used for TLS certificate verification from +/// an outbound request `authority`. /// -/// `authority` is in `host` or `host:port` form, where an IPv6 `host` is -/// wrapped in brackets (for example `[::1]:443`). Splitting on the first `:` is -/// wrong for the bracketed IPv6 form, so handle it explicitly and return the -/// bare address without brackets, which is what `rustls`' `ServerName` expects. +/// `authority` is in `host:port` form, where an IPv6 `host` is wrapped in +/// brackets (for example `[::1]:443`). An IP literal is recognized by parsing +/// the whole authority as a [`SocketAddr`]; this handles the bracketed IPv6 +/// form, which splitting on the first `:` would truncate. Anything else is +/// treated as a host name, with the port stripped off before it is handed to +/// rustls. +/// +/// [`ServerName`]: rustls::pki_types::ServerName +/// [`SocketAddr`]: std::net::SocketAddr #[cfg(all(feature = "default-send-request", any(feature = "p2", feature = "p3")))] -fn tls_server_name(authority: &str) -> &str { - if let Some(rest) = authority.strip_prefix('[') { - if let Some(end) = rest.find(']') { - return &rest[..end]; - } +fn tls_server_name( + authority: &str, +) -> Result, rustls::pki_types::InvalidDnsNameError> { + use rustls::pki_types::ServerName; + + if let Ok(addr) = authority.parse::() { + return Ok(ServerName::from(addr.ip())); } - match authority.split_once(':') { + let host = match authority.split_once(':') { Some((host, _port)) => host, None => authority, - } + }; + Ok(ServerName::try_from(host)?.to_owned()) } #[cfg(all( @@ -97,22 +106,34 @@ fn tls_server_name(authority: &str) -> &str { ))] mod tls_server_name_tests { use super::tls_server_name; + use rustls::pki_types::ServerName; #[test] - fn extracts_host_from_authority() { - assert_eq!(tls_server_name("example.com"), "example.com"); - assert_eq!(tls_server_name("example.com:443"), "example.com"); - assert_eq!(tls_server_name("127.0.0.1:80"), "127.0.0.1"); - - // An IPv6 literal is bracketed; the brackets must be dropped and the - // whole address kept rather than truncated at the first `:`. - assert_eq!(tls_server_name("[::1]:443"), "::1"); - assert_eq!(tls_server_name("[2001:db8::1]:8443"), "2001:db8::1"); - assert_eq!(tls_server_name("[::1]"), "::1"); + fn resolves_server_name_from_authority() { + // Host names keep their host and drop the port. + assert_eq!( + tls_server_name("example.com:443").unwrap(), + ServerName::try_from("example.com").unwrap() + ); + assert_eq!( + tls_server_name("example.com").unwrap(), + ServerName::try_from("example.com").unwrap() + ); - // Matches what a parsed authority produces for an IPv6 host. - let authority = http::uri::Authority::from_static("[2001:db8::1]:8443"); - assert_eq!(tls_server_name(&authority.to_string()), "2001:db8::1"); + // IP literals resolve to an `IpAddress` server name. The bracketed IPv6 + // form must not be truncated at the first `:`. + assert_eq!( + tls_server_name("127.0.0.1:80").unwrap(), + ServerName::from(std::net::Ipv4Addr::LOCALHOST) + ); + assert_eq!( + tls_server_name("[::1]:443").unwrap(), + ServerName::from(std::net::Ipv6Addr::LOCALHOST) + ); + assert_eq!( + tls_server_name("[2001:db8::1]:8443").unwrap(), + ServerName::from("2001:db8::1".parse::().unwrap()) + ); } } diff --git a/crates/wasi-http/src/p2/mod.rs b/crates/wasi-http/src/p2/mod.rs index 160e142c2b98..962c37291158 100644 --- a/crates/wasi-http/src/p2/mod.rs +++ b/crates/wasi-http/src/p2/mod.rs @@ -620,8 +620,6 @@ pub async fn default_send_request_handler( })?; let (mut sender, worker) = if use_tls { - use rustls::pki_types::ServerName; - // derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs let root_cert_store = rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.into(), @@ -630,12 +628,10 @@ pub async fn default_send_request_handler( .with_root_certificates(root_cert_store) .with_no_client_auth(); let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config)); - let domain = ServerName::try_from(crate::tls_server_name(&authority)) - .map_err(|e| { - tracing::warn!("dns lookup error: {e:?}"); - dns_error("invalid dns name".to_string(), 0) - })? - .to_owned(); + let domain = crate::tls_server_name(&authority).map_err(|e| { + tracing::warn!("dns lookup error: {e:?}"); + dns_error("invalid dns name".to_string(), 0) + })?; let stream = connector.connect(domain, tcp_stream).await.map_err(|e| { tracing::warn!("tls protocol error: {e:?}"); ErrorCode::TlsProtocolError diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 53943ba8dac0..8bdf9d4b7d8b 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -346,8 +346,6 @@ pub async fn default_send_request( Err(..) => return Err(ErrorCode::ConnectionTimeout), }; let stream = if use_tls { - use rustls::pki_types::ServerName; - // derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs let root_cert_store = rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.into(), @@ -356,12 +354,10 @@ pub async fn default_send_request( .with_root_certificates(root_cert_store) .with_no_client_auth(); let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config)); - let domain = ServerName::try_from(crate::tls_server_name(&authority)) - .map_err(|e| { - tracing::warn!("dns lookup error: {e:?}"); - dns_error("invalid dns name".to_string(), 0) - })? - .to_owned(); + let domain = crate::tls_server_name(&authority).map_err(|e| { + tracing::warn!("dns lookup error: {e:?}"); + dns_error("invalid dns name".to_string(), 0) + })?; let stream = connector.connect(domain, stream).await.map_err(|e| { tracing::warn!("tls protocol error: {e:?}"); ErrorCode::TlsProtocolError