From 2519a4588bcbeb889fc9ac42448d065bbbc062fd Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sun, 7 Apr 2024 18:30:46 -0400 Subject: [PATCH 1/6] Support custom server name resolution --- src/connector.rs | 48 +++++++++++++++++++++---------------- src/connector/builder.rs | 32 +++++++++++++++++++------ src/lib.rs | 1 + src/server_name_resolver.rs | 44 ++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 27 deletions(-) create mode 100644 src/server_name_resolver.rs diff --git a/src/connector.rs b/src/connector.rs index 37864da..afb7599 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -12,6 +12,7 @@ use pki_types::ServerName; use tokio_rustls::TlsConnector; use tower_service::Service; +use crate::server_name_resolver::ResolveServerName; use crate::stream::MaybeHttpsStream; pub(crate) mod builder; @@ -24,7 +25,7 @@ pub struct HttpsConnector { force_https: bool, http: T, tls_config: Arc, - override_server_name: Option, + server_name_resolver: Option>, } impl HttpsConnector { @@ -90,24 +91,31 @@ where }; let cfg = self.tls_config.clone(); - let mut hostname = match self.override_server_name.as_deref() { - Some(h) => h, - None => dst.host().unwrap_or_default(), - }; - - // Remove square brackets around IPv6 address. - if let Some(trimmed) = hostname - .strip_prefix('[') - .and_then(|h| h.strip_suffix(']')) - { - hostname = trimmed; - } - - let hostname = match ServerName::try_from(hostname) { - Ok(dns_name) => dns_name.to_owned(), - Err(_) => { - let err = io::Error::new(io::ErrorKind::Other, "invalid dnsname"); - return Box::pin(async move { Err(Box::new(err).into()) }); + let hostname = match &self.server_name_resolver { + Some(server_name_resolver) => match server_name_resolver.resolve(&dst) { + Ok(hostname) => hostname, + Err(e) => { + return Box::pin(async move { Err(e) }); + } + }, + None => { + let mut hostname = dst.host().unwrap_or_default(); + + // Remove square brackets around IPv6 address. + if let Some(trimmed) = hostname + .strip_prefix('[') + .and_then(|h| h.strip_suffix(']')) + { + hostname = trimmed; + } + + match ServerName::try_from(hostname) { + Ok(dns_name) => dns_name.to_owned(), + Err(_) => { + let err = io::Error::new(io::ErrorKind::Other, "invalid dnsname"); + return Box::pin(async move { Err(Box::new(err).into()) }); + } + } } }; @@ -135,7 +143,7 @@ where force_https: false, http, tls_config: cfg.into(), - override_server_name: None, + server_name_resolver: None, } } } diff --git a/src/connector/builder.rs b/src/connector/builder.rs index b628b1a..aedae09 100644 --- a/src/connector/builder.rs +++ b/src/connector/builder.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use hyper_util::client::legacy::connect::HttpConnector; #[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))] use rustls::crypto::CryptoProvider; @@ -6,6 +8,7 @@ use rustls::ClientConfig; use super::HttpsConnector; #[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))] use crate::config::ConfigBuilderExt; +use crate::server_name_resolver::{FixedServerNameResolver, ResolveServerName}; /// A builder for an [`HttpsConnector`] /// @@ -153,7 +156,7 @@ impl ConnectorBuilder { ConnectorBuilder(WantsProtocols1 { tls_config: self.0.tls_config, https_only: true, - override_server_name: None, + server_name_resolver: None, }) } @@ -165,7 +168,7 @@ impl ConnectorBuilder { ConnectorBuilder(WantsProtocols1 { tls_config: self.0.tls_config, https_only: false, - override_server_name: None, + server_name_resolver: None, }) } } @@ -177,7 +180,7 @@ impl ConnectorBuilder { pub struct WantsProtocols1 { tls_config: ClientConfig, https_only: bool, - override_server_name: Option, + server_name_resolver: Option>, } impl WantsProtocols1 { @@ -186,7 +189,7 @@ impl WantsProtocols1 { force_https: self.https_only, http: conn, tls_config: std::sync::Arc::new(self.tls_config), - override_server_name: self.override_server_name, + server_name_resolver: self.server_name_resolver, } } @@ -237,6 +240,22 @@ impl ConnectorBuilder { }) } + /// Override server name for the TLS stack + /// + /// By default, for each connection hyper-rustls will extract host portion + /// of the destination URL and verify that server certificate contains + /// this value. + /// + /// If this method is called, hyper-rustls will instead use this resolver + /// to compute the value used to verify the server certificate. + pub fn with_server_name_resolver(mut self, resolver: T) -> Self + where + T: ResolveServerName + 'static + Sync + Send, + { + self.0.server_name_resolver = Some(Arc::new(resolver)); + self + } + /// Override server name for the TLS stack /// /// By default, for each connection hyper-rustls will extract host portion @@ -246,9 +265,8 @@ impl ConnectorBuilder { /// If this method is called, hyper-rustls will instead verify that server /// certificate contains `override_server_name`. Domain name included in /// the URL will not affect certificate validation. - pub fn with_server_name(mut self, override_server_name: String) -> Self { - self.0.override_server_name = Some(override_server_name); - self + pub fn with_server_name(self, override_server_name: String) -> Self { + self.with_server_name_resolver(FixedServerNameResolver::new(override_server_name)) } } diff --git a/src/lib.rs b/src/lib.rs index 7cfbe49..07f41ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,7 @@ mod config; mod connector; +mod server_name_resolver; mod stream; #[cfg(feature = "logging")] diff --git a/src/server_name_resolver.rs b/src/server_name_resolver.rs new file mode 100644 index 0000000..204a3f4 --- /dev/null +++ b/src/server_name_resolver.rs @@ -0,0 +1,44 @@ +use std::error::Error; + +use http::Uri; +use pki_types::ServerName; + +/// A trait implemented by types that can resolve a [`ServerName`] for a request. +pub trait ResolveServerName { + /// Maps a [`Uri`] into a [`ServerName`]. + fn resolve(&self, uri: &Uri) -> Result, Box>; +} + +impl ResolveServerName for F +where + F: Fn(&Uri) -> Result, E>, + E: Into>, +{ + fn resolve(&self, uri: &Uri) -> Result, Box> { + self(uri).map_err(Into::into) + } +} + +pub(crate) struct FixedServerNameResolver { + name: String, +} + +impl FixedServerNameResolver { + pub(crate) fn new(mut name: String) -> Self { + // Remove square brackets around IPv6 address. + if let Some(trimmed) = name + .strip_prefix('[') + .and_then(|h| h.strip_suffix(']')) + { + name = trimmed.to_string(); + } + + Self { name } + } +} + +impl ResolveServerName for FixedServerNameResolver { + fn resolve(&self, _: &Uri) -> Result, Box> { + ServerName::try_from(self.name.clone()).map_err(|e| Box::new(e) as _) + } +} From 93b06c64b0a219cf374b9699d248a285f0d70106 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sun, 7 Apr 2024 20:06:19 -0400 Subject: [PATCH 2/6] reexport --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 07f41ea..3ed7432 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ mod log { pub use crate::config::ConfigBuilderExt; pub use crate::connector::builder::ConnectorBuilder as HttpsConnectorBuilder; pub use crate::connector::HttpsConnector; +pub use crate::server_name_resolver::ResolveServerName; pub use crate::stream::MaybeHttpsStream; /// The various states of the [`HttpsConnectorBuilder`] From f46a93b13915f4b72d4f0a19beb7bce9a8b1c936 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Tue, 9 Apr 2024 10:59:23 -0400 Subject: [PATCH 3/6] review --- src/connector.rs | 107 ++++++++++++++++++++++++++---------- src/connector/builder.rs | 31 ++++++++--- src/lib.rs | 6 +- src/server_name_resolver.rs | 44 --------------- 4 files changed, 104 insertions(+), 84 deletions(-) delete mode 100644 src/server_name_resolver.rs diff --git a/src/connector.rs b/src/connector.rs index afb7599..2be36da 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -12,7 +12,6 @@ use pki_types::ServerName; use tokio_rustls::TlsConnector; use tower_service::Service; -use crate::server_name_resolver::ResolveServerName; use crate::stream::MaybeHttpsStream; pub(crate) mod builder; @@ -25,7 +24,7 @@ pub struct HttpsConnector { force_https: bool, http: T, tls_config: Arc, - server_name_resolver: Option>, + server_name_resolver: Arc, } impl HttpsConnector { @@ -91,31 +90,10 @@ where }; let cfg = self.tls_config.clone(); - let hostname = match &self.server_name_resolver { - Some(server_name_resolver) => match server_name_resolver.resolve(&dst) { - Ok(hostname) => hostname, - Err(e) => { - return Box::pin(async move { Err(e) }); - } - }, - None => { - let mut hostname = dst.host().unwrap_or_default(); - - // Remove square brackets around IPv6 address. - if let Some(trimmed) = hostname - .strip_prefix('[') - .and_then(|h| h.strip_suffix(']')) - { - hostname = trimmed; - } - - match ServerName::try_from(hostname) { - Ok(dns_name) => dns_name.to_owned(), - Err(_) => { - let err = io::Error::new(io::ErrorKind::Other, "invalid dnsname"); - return Box::pin(async move { Err(Box::new(err).into()) }); - } - } + let hostname = match self.server_name_resolver.resolve(&dst) { + Ok(hostname) => hostname, + Err(e) => { + return Box::pin(async move { Err(e) }); } }; @@ -143,7 +121,7 @@ where force_https: false, http, tls_config: cfg.into(), - server_name_resolver: None, + server_name_resolver: Arc::new(DefaultServerNameResolver::new()), } } } @@ -155,3 +133,76 @@ impl fmt::Debug for HttpsConnector { .finish() } } + +/// The default server name resolver, which uses the hostname in the URI. +#[derive(Default)] +pub struct DefaultServerNameResolver(()); + +impl DefaultServerNameResolver { + /// Creates a new resolver. + pub fn new() -> Self { + Self::default() + } +} + +impl ResolveServerName for DefaultServerNameResolver { + fn resolve( + &self, + uri: &Uri, + ) -> Result, Box> { + let mut hostname = uri.host().unwrap_or_default(); + + // Remove square brackets around IPv6 address. + if let Some(trimmed) = hostname + .strip_prefix('[') + .and_then(|h| h.strip_suffix(']')) + { + hostname = trimmed; + } + + ServerName::try_from(hostname.to_string()).map_err(|e| Box::new(e) as _) + } +} + +/// A server name resolver which always returns the same fixed name. +pub struct FixedServerNameResolver { + name: ServerName<'static>, +} + +impl FixedServerNameResolver { + /// Creates a new resolver returning the specified name. + pub fn new(name: ServerName<'static>) -> Self { + Self { name } + } +} + +impl ResolveServerName for FixedServerNameResolver { + fn resolve( + &self, + _: &Uri, + ) -> Result, Box> { + Ok(self.name.clone()) + } +} + +impl ResolveServerName for F +where + F: Fn(&Uri) -> Result, E>, + E: Into>, +{ + fn resolve( + &self, + uri: &Uri, + ) -> Result, Box> { + self(uri).map_err(Into::into) + } +} + +/// A trait implemented by types that can resolve a [`ServerName`] for a request. +pub trait ResolveServerName { + /// Maps a [`Uri`] into a [`ServerName`]. + fn resolve( + &self, + uri: &Uri, + ) -> Result, Box>; +} diff --git a/src/connector/builder.rs b/src/connector/builder.rs index aedae09..6a0e064 100644 --- a/src/connector/builder.rs +++ b/src/connector/builder.rs @@ -5,10 +5,10 @@ use hyper_util::client::legacy::connect::HttpConnector; use rustls::crypto::CryptoProvider; use rustls::ClientConfig; -use super::HttpsConnector; +use super::{DefaultServerNameResolver, HttpsConnector, ResolveServerName}; #[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))] use crate::config::ConfigBuilderExt; -use crate::server_name_resolver::{FixedServerNameResolver, ResolveServerName}; +use pki_types::ServerName; /// A builder for an [`HttpsConnector`] /// @@ -189,7 +189,9 @@ impl WantsProtocols1 { force_https: self.https_only, http: conn, tls_config: std::sync::Arc::new(self.tls_config), - server_name_resolver: self.server_name_resolver, + server_name_resolver: self + .server_name_resolver + .unwrap_or_else(|| Arc::new(DefaultServerNameResolver::new())), } } @@ -248,10 +250,10 @@ impl ConnectorBuilder { /// /// If this method is called, hyper-rustls will instead use this resolver /// to compute the value used to verify the server certificate. - pub fn with_server_name_resolver(mut self, resolver: T) -> Self - where - T: ResolveServerName + 'static + Sync + Send, - { + pub fn with_server_name_resolver( + mut self, + resolver: impl ResolveServerName + 'static + Sync + Send, + ) -> Self { self.0.server_name_resolver = Some(Arc::new(resolver)); self } @@ -265,8 +267,19 @@ impl ConnectorBuilder { /// If this method is called, hyper-rustls will instead verify that server /// certificate contains `override_server_name`. Domain name included in /// the URL will not affect certificate validation. - pub fn with_server_name(self, override_server_name: String) -> Self { - self.with_server_name_resolver(FixedServerNameResolver::new(override_server_name)) + #[deprecated(since = "0.27.1", note = "use Self::with_server_name_resolver instead")] + pub fn with_server_name(self, mut override_server_name: String) -> Self { + // remove square brackets around IPv6 address. + if let Some(trimmed) = override_server_name + .strip_prefix('[') + .and_then(|s| s.strip_suffix(']')) + { + override_server_name = trimmed.to_string(); + } + + self.with_server_name_resolver(move |_: &_| { + ServerName::try_from(override_server_name.clone()) + }) } } diff --git a/src/lib.rs b/src/lib.rs index 3ed7432..1920e78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,6 @@ mod config; mod connector; -mod server_name_resolver; mod stream; #[cfg(feature = "logging")] @@ -56,8 +55,9 @@ mod log { pub use crate::config::ConfigBuilderExt; pub use crate::connector::builder::ConnectorBuilder as HttpsConnectorBuilder; -pub use crate::connector::HttpsConnector; -pub use crate::server_name_resolver::ResolveServerName; +pub use crate::connector::{ + DefaultServerNameResolver, FixedServerNameResolver, HttpsConnector, ResolveServerName, +}; pub use crate::stream::MaybeHttpsStream; /// The various states of the [`HttpsConnectorBuilder`] diff --git a/src/server_name_resolver.rs b/src/server_name_resolver.rs deleted file mode 100644 index 204a3f4..0000000 --- a/src/server_name_resolver.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::error::Error; - -use http::Uri; -use pki_types::ServerName; - -/// A trait implemented by types that can resolve a [`ServerName`] for a request. -pub trait ResolveServerName { - /// Maps a [`Uri`] into a [`ServerName`]. - fn resolve(&self, uri: &Uri) -> Result, Box>; -} - -impl ResolveServerName for F -where - F: Fn(&Uri) -> Result, E>, - E: Into>, -{ - fn resolve(&self, uri: &Uri) -> Result, Box> { - self(uri).map_err(Into::into) - } -} - -pub(crate) struct FixedServerNameResolver { - name: String, -} - -impl FixedServerNameResolver { - pub(crate) fn new(mut name: String) -> Self { - // Remove square brackets around IPv6 address. - if let Some(trimmed) = name - .strip_prefix('[') - .and_then(|h| h.strip_suffix(']')) - { - name = trimmed.to_string(); - } - - Self { name } - } -} - -impl ResolveServerName for FixedServerNameResolver { - fn resolve(&self, _: &Uri) -> Result, Box> { - ServerName::try_from(self.name.clone()).map_err(|e| Box::new(e) as _) - } -} From e54d77ac64c480c7f55f9f1b37e7e65092a9496f Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Tue, 9 Apr 2024 11:14:15 -0400 Subject: [PATCH 4/6] no constructor --- src/connector.rs | 9 +-------- src/connector/builder.rs | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/connector.rs b/src/connector.rs index 2be36da..02a29a8 100644 --- a/src/connector.rs +++ b/src/connector.rs @@ -121,7 +121,7 @@ where force_https: false, http, tls_config: cfg.into(), - server_name_resolver: Arc::new(DefaultServerNameResolver::new()), + server_name_resolver: Arc::new(DefaultServerNameResolver::default()), } } } @@ -138,13 +138,6 @@ impl fmt::Debug for HttpsConnector { #[derive(Default)] pub struct DefaultServerNameResolver(()); -impl DefaultServerNameResolver { - /// Creates a new resolver. - pub fn new() -> Self { - Self::default() - } -} - impl ResolveServerName for DefaultServerNameResolver { fn resolve( &self, diff --git a/src/connector/builder.rs b/src/connector/builder.rs index 6a0e064..1353297 100644 --- a/src/connector/builder.rs +++ b/src/connector/builder.rs @@ -191,7 +191,7 @@ impl WantsProtocols1 { tls_config: std::sync::Arc::new(self.tls_config), server_name_resolver: self .server_name_resolver - .unwrap_or_else(|| Arc::new(DefaultServerNameResolver::new())), + .unwrap_or_else(|| Arc::new(DefaultServerNameResolver::default())), } } From bb62a3795746e772ea39c295ba8046406dd178a3 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Wed, 10 Apr 2024 10:48:37 -0400 Subject: [PATCH 5/6] Update src/connector/builder.rs Co-authored-by: Daniel McCarney --- src/connector/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/connector/builder.rs b/src/connector/builder.rs index 1353297..5080620 100644 --- a/src/connector/builder.rs +++ b/src/connector/builder.rs @@ -267,7 +267,7 @@ impl ConnectorBuilder { /// If this method is called, hyper-rustls will instead verify that server /// certificate contains `override_server_name`. Domain name included in /// the URL will not affect certificate validation. - #[deprecated(since = "0.27.1", note = "use Self::with_server_name_resolver instead")] + #[deprecated(since = "0.27.1", note = "use Self::with_server_name_resolver with FixedServerNameResolver instead")] pub fn with_server_name(self, mut override_server_name: String) -> Self { // remove square brackets around IPv6 address. if let Some(trimmed) = override_server_name From 84a2ff7f2e771c79736b97f3fbb1b7877858e0c6 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Wed, 10 Apr 2024 10:56:19 -0400 Subject: [PATCH 6/6] fmt --- src/connector/builder.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/connector/builder.rs b/src/connector/builder.rs index 5080620..e0b78fc 100644 --- a/src/connector/builder.rs +++ b/src/connector/builder.rs @@ -267,7 +267,10 @@ impl ConnectorBuilder { /// If this method is called, hyper-rustls will instead verify that server /// certificate contains `override_server_name`. Domain name included in /// the URL will not affect certificate validation. - #[deprecated(since = "0.27.1", note = "use Self::with_server_name_resolver with FixedServerNameResolver instead")] + #[deprecated( + since = "0.27.1", + note = "use Self::with_server_name_resolver with FixedServerNameResolver instead" + )] pub fn with_server_name(self, mut override_server_name: String) -> Self { // remove square brackets around IPv6 address. if let Some(trimmed) = override_server_name