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: 虎鸣 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
