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