You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pekko.apache.org by he...@apache.org on 2026/06/23 20:20:51 UTC
(pekko) branch fix/tls-hostname-3161 created (now 247469770b)
This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a change to branch fix/tls-hostname-3161
in repository https://gitbox.apache.org/repos/asf/pekko.git
at 247469770b feat: add optional TLS hostname verification for classic remoting
This branch includes the following new commits:
new 247469770b feat: add optional TLS hostname verification for classic remoting
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pekko.apache.org
For additional commands, e-mail: commits-help@pekko.apache.org
(pekko) 01/01: feat: add optional TLS hostname verification for classic remoting
Posted by he...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch fix/tls-hostname-3161
in repository https://gitbox.apache.org/repos/asf/pekko.git
commit 247469770b9759719180a2d95580c50f569566fe
Author: 虎鸣 <he...@alibaba-inc.com>
AuthorDate: Wed Jun 24 04:18:31 2026 +0800
feat: add optional TLS hostname verification for classic remoting
Motivation:
Classic remoting's ConfigSSLEngineProvider never called
setEndpointIdentificationAlgorithm, meaning TLS certificate hostname
verification was not performed. An attacker with any certificate signed
by a trusted CA could perform MITM attacks. Artery already supports
hostname verification via a configurable option.
Modification:
- SSLSettings: add hostname-verification config option (default off)
- SSLEngineProvider trait: add createClientSSLEngine(hostname, port)
default method for backward compatibility
- ConfigSSLEngineProvider: implement hostname-aware engine creation with
setEndpointIdentificationAlgorithm("HTTPS") when enabled
- NettyTransport: pass remote address hostname to SSL handler on client
side when hostname verification is enabled
- NettySSLSupport: accept optional hostname/port parameters
Result:
Classic remoting can now optionally verify TLS certificate hostnames,
matching Artery's behavior. Disabled by default for backward
compatibility. Enable with hostname-verification = on in SSL security
config.
Tests:
- remote / Test / testOnly org.apache.pekko.remote.Ticket1978ConfigSpec
References:
Fixes #3161
---
.../remote/transport/netty/NettySSLSupport.scala | 17 +++++++++--
.../remote/transport/netty/NettyTransport.scala | 13 ++++++--
.../remote/transport/netty/SSLEngineProvider.scala | 35 ++++++++++++++++++----
.../apache/pekko/remote/Ticket1978ConfigSpec.scala | 10 +++++++
4 files changed, 63 insertions(+), 12 deletions(-)
diff --git a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
index c127e4b0e8..cacfec264a 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettySSLSupport.scala
@@ -46,6 +46,9 @@ private[pekko] class SSLSettings(config: Config) {
val SSLRequireMutualAuthentication = getBoolean("require-mutual-authentication")
+ val SSLHostnameVerification: Boolean =
+ if (config.hasPath("hostname-verification")) config.getBoolean("hostname-verification")
+ else false
}
/**
@@ -60,10 +63,18 @@ private[pekko] object NettySSLSupport {
/**
* Construct a SSLHandler which can be inserted into a Netty server/client pipeline
*/
- def apply(sslEngineProvider: SSLEngineProvider, isClient: Boolean): SslHandler = {
+ def apply(
+ sslEngineProvider: SSLEngineProvider,
+ isClient: Boolean,
+ hostname: Option[String] = None,
+ port: Int = -1): SslHandler = {
val sslEngine =
- if (isClient) sslEngineProvider.createClientSSLEngine()
- else sslEngineProvider.createServerSSLEngine()
+ if (isClient) {
+ hostname match {
+ case Some(h) => sslEngineProvider.createClientSSLEngine(h, port)
+ case None => sslEngineProvider.createClientSSLEngine()
+ }
+ } else sslEngineProvider.createServerSSLEngine()
val handler = new SslHandler(sslEngine)
handler.handshakeFuture().addListener((future: Future[Channel]) => {
if (!future.isSuccess) {
diff --git a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
index f64a39ac02..c4b01b6add 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/NettyTransport.scala
@@ -420,10 +420,17 @@ class NettyTransport(val settings: NettyTransportSettings, val system: ExtendedA
.get)
} else OptionVal.None
- private def sslHandler(isClient: Boolean): SslHandler = {
+ private def sslHandler(isClient: Boolean, remoteAddress: Option[Address] = None): SslHandler = {
sslEngineProvider match {
case OptionVal.Some(sslProvider) =>
- NettySSLSupport(sslProvider, isClient)
+ val hostnameVerification = SslSettings.exists(_.SSLHostnameVerification)
+ if (isClient && hostnameVerification && remoteAddress.isDefined) {
+ val addr = remoteAddress.get
+ val port = addr.port.getOrElse(-1)
+ NettySSLSupport(sslProvider, isClient, addr.host, port)
+ } else {
+ NettySSLSupport(sslProvider, isClient)
+ }
case _ =>
throw new IllegalStateException("Expected enable-ssl=on")
}
@@ -440,7 +447,7 @@ class NettyTransport(val settings: NettyTransportSettings, val system: ExtendedA
private def clientPipelineInitializer(remoteAddress: Address): ChannelInitializer[SocketChannel] =
(ch: SocketChannel) => {
val pipeline = newPipeline(ch)
- if (EnableSsl) pipeline.addFirst("SslHandler", sslHandler(isClient = true))
+ if (EnableSsl) pipeline.addFirst("SslHandler", sslHandler(isClient = true, Some(remoteAddress)))
val handler = new TcpClientHandler(NettyTransport.this, remoteAddress, log)
pipeline.addLast("clienthandler", handler)
}
diff --git a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
index ecab23e25a..33ab37865e 100644
--- a/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
+++ b/remote/src/main/scala/org/apache/pekko/remote/transport/netty/SSLEngineProvider.scala
@@ -27,6 +27,7 @@ import javax.net.ssl.SSLEngine
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
+import scala.annotation.nowarn
import scala.util.Try
import org.apache.pekko
@@ -44,6 +45,13 @@ trait SSLEngineProvider {
def createClientSSLEngine(): SSLEngine
+ /**
+ * Create a client SSLEngine with the target hostname and port for hostname verification.
+ * Default implementation delegates to [[createClientSSLEngine()]].
+ */
+ @nowarn("msg=never used")
+ def createClientSSLEngine(hostname: String, port: Int): SSLEngine = createClientSSLEngine()
+
}
/**
@@ -115,18 +123,33 @@ class ConfigSSLEngineProvider(protected val log: MarkerLoggingAdapter, private v
SecureRandomFactory.createSecureRandom(SSLRandomNumberGenerator, log)
override def createServerSSLEngine(): SSLEngine =
- createSSLEngine(pekko.stream.Server)
+ createSSLEngine(pekko.stream.Server, None)
override def createClientSSLEngine(): SSLEngine =
- createSSLEngine(pekko.stream.Client)
+ createSSLEngine(pekko.stream.Client, None)
+
+ override def createClientSSLEngine(hostname: String, port: Int): SSLEngine =
+ createSSLEngine(pekko.stream.Client, Some((hostname, port)))
- private def createSSLEngine(role: TLSRole): SSLEngine = {
- createSSLEngine(sslContext, role)
+ private def createSSLEngine(role: TLSRole, peerHost: Option[(String, Int)]): SSLEngine = {
+ createSSLEngine(sslContext, role, peerHost)
}
- private def createSSLEngine(sslContext: SSLContext, role: TLSRole): SSLEngine = {
+ private def createSSLEngine(
+ sslContext: SSLContext,
+ role: TLSRole,
+ peerHost: Option[(String, Int)]): SSLEngine = {
- val engine = sslContext.createSSLEngine()
+ val engine = peerHost match {
+ case Some((hostname, port)) => sslContext.createSSLEngine(hostname, port)
+ case None => sslContext.createSSLEngine()
+ }
+
+ if (SSLHostnameVerification && role == pekko.stream.Client) {
+ val sslParams = sslContext.getDefaultSSLParameters
+ sslParams.setEndpointIdentificationAlgorithm("HTTPS")
+ engine.setSSLParameters(sslParams)
+ }
engine.setUseClientMode(role == pekko.stream.Client)
engine.setEnabledCipherSuites(SSLEnabledAlgorithms.toArray)
diff --git a/remote/src/test/scala/org/apache/pekko/remote/Ticket1978ConfigSpec.scala b/remote/src/test/scala/org/apache/pekko/remote/Ticket1978ConfigSpec.scala
index 2fc434d6c2..b7cb9934b4 100644
--- a/remote/src/test/scala/org/apache/pekko/remote/Ticket1978ConfigSpec.scala
+++ b/remote/src/test/scala/org/apache/pekko/remote/Ticket1978ConfigSpec.scala
@@ -36,6 +36,16 @@ class Ticket1978ConfigSpec extends PekkoSpec("""
settings.SSLEnabledAlgorithms should ===(
Set("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"))
settings.SSLRandomNumberGenerator should ===("SecureRandom")
+ settings.SSLHostnameVerification should ===(false)
+ }
+
+ "support hostname-verification setting" in {
+ val settings = new SSLSettings(
+ com.typesafe.config.ConfigFactory
+ .parseString("hostname-verification = on")
+ .withFallback(system.settings.config.getConfig("pekko.remote.classic.netty.ssl.security")))
+
+ settings.SSLHostnameVerification should ===(true)
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pekko.apache.org
For additional commands, e-mail: commits-help@pekko.apache.org