From 2dcf59ec862267452ad8932ae2d2d6d744469efa Mon Sep 17 00:00:00 2001 From: anavarr Date: Tue, 2 Dec 2025 11:42:42 +0100 Subject: [PATCH] Add support for hybrid key exchange protocol x25519mlkem768 for making TLS sessions quantum safe. This features relies on the netty-tcnative-openssl-dynamic bound to version 3.6 of openssl. - Add boolean useHybrid field to io.vertx.core.net.SSLOptions and create getters/setters in io.vertx.core.net.SSLOptions as well as every implementation of io.vertx.core.net.TCPSSLOptions. - If this value is set to true (default false), the ssl handler will be set to use x25519mlkem768 instead of x25519. - If key exchange protocol is set to x25519mlkem768 but JdkSsl is used instead of OpenSsl, or the version of openssl used at runtime does not suppot x25519mlkem768, the user is informed that hybrid key exchange is impossible channel is closed --- pom.xml | 5 ++ src/main/asciidoc/http.adoc | 4 ++ .../eventbus/EventBusOptionsConverter.java | 6 ++ .../vertx/core/net/SSLOptionsConverter.java | 6 ++ .../core/net/TCPSSLOptionsConverter.java | 6 ++ .../vertx/core/eventbus/EventBusOptions.java | 5 ++ .../io/vertx/core/http/HttpClientOptions.java | 5 ++ .../io/vertx/core/http/HttpServerOptions.java | 12 ++++ .../core/http/WebSocketClientOptions.java | 5 ++ .../core/http/impl/HttpServerWorker.java | 20 ++++++- .../io/vertx/core/net/ClientOptionsBase.java | 5 ++ .../io/vertx/core/net/NetClientOptions.java | 5 ++ .../io/vertx/core/net/NetServerOptions.java | 4 +- .../java/io/vertx/core/net/SSLOptions.java | 21 ++++++- .../java/io/vertx/core/net/TCPSSLOptions.java | 12 ++++ .../vertx/core/net/impl/ChannelProvider.java | 15 ++++- .../io/vertx/core/net/impl/NetServerImpl.java | 17 +++++- .../io/vertx/core/net/impl/NetSocketImpl.java | 7 ++- .../io/vertx/core/net/impl/SSLHelper.java | 5 +- .../core/net/impl/SslChannelProvider.java | 56 ++++++++++++++++--- .../vertx/core/net/impl/VertxSniHandler.java | 27 ++++++++- 21 files changed, 226 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 22ffe1d2d85..22d195ad4eb 100644 --- a/pom.xml +++ b/pom.xml @@ -204,6 +204,11 @@ log4j-core test + + io.netty + netty-tcnative-classes + 2.0.76.Final + io.netty netty-tcnative-boringssl-static diff --git a/src/main/asciidoc/http.adoc b/src/main/asciidoc/http.adoc index 16b2d1bd91b..9a64219a2f9 100644 --- a/src/main/asciidoc/http.adoc +++ b/src/main/asciidoc/http.adoc @@ -41,6 +41,10 @@ To handle `h2` requests, TLS must be enabled along with {@link io.vertx.core.htt {@link examples.HTTP2Examples#example0} ---- +The rise of quantum computers will make key exchange protocols such as x25519 obsolete as they will be able to "crack" secret keys quickly. +Vert.x proposes a quantum-safe key exchange protocol, x25519MLKEM768 (official recommendation of NIST) to ensure sessions over TLS are safe against quantum computers. +Hybrid key exchange must be enabled along with {@link io.vertx.core.http.HttpServerOptions#setUseHybrid(boolean)} and only works using OpenSsl ({$@link io.vertx.core.http.HttpServerOptions#setSslEngineOptions(SSLEngineOptions)}) + ALPN is a TLS extension that negotiates the protocol before the client and the server start to exchange data. Clients that don't support ALPN will still be able to do a _classic_ SSL handshake. diff --git a/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java b/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java index c750e1938bd..79e6fa8dfe5 100644 --- a/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java +++ b/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java @@ -279,6 +279,11 @@ static void fromJson(Iterable> json, EventBu obj.setUseAlpn((Boolean)member.getValue()); } break; + case "useHybrid": + if (member.getValue() instanceof Boolean) { + obj.setUseHybrid((Boolean)member.getValue()); + } + break; case "writeIdleTimeout": if (member.getValue() instanceof Number) { obj.setWriteIdleTimeout(((Number)member.getValue()).intValue()); @@ -388,6 +393,7 @@ static void toJson(EventBusOptions obj, java.util.Map json) { json.put("trustStoreOptions", obj.getTrustStoreOptions().toJson()); } json.put("useAlpn", obj.isUseAlpn()); + json.put("useHybrid", obj.isUseHybrid()); json.put("writeIdleTimeout", obj.getWriteIdleTimeout()); } } diff --git a/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java b/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java index ed5e57e40c9..76de6b53b5e 100644 --- a/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java @@ -69,6 +69,11 @@ static void fromJson(Iterable> json, SSLOpti obj.setUseAlpn((Boolean)member.getValue()); } break; + case "useHybrid": + if (member.getValue() instanceof Boolean) { + obj.setUseHybrid((Boolean)member.getValue()); + } + break; } } } @@ -103,5 +108,6 @@ static void toJson(SSLOptions obj, java.util.Map json) { json.put("sslHandshakeTimeoutUnit", obj.getSslHandshakeTimeoutUnit().name()); } json.put("useAlpn", obj.isUseAlpn()); + json.put("useHybrid", obj.isUseHybrid()); } } diff --git a/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java b/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java index 82392e43426..77b551b0262 100644 --- a/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java @@ -179,6 +179,11 @@ static void fromJson(Iterable> json, TCPSSLO obj.setUseAlpn((Boolean)member.getValue()); } break; + case "useHybrid": + if (member.getValue() instanceof Boolean) { + obj.setUseHybrid((Boolean)member.getValue()); + } + break; case "writeIdleTimeout": if (member.getValue() instanceof Number) { obj.setWriteIdleTimeout(((Number)member.getValue()).intValue()); @@ -258,6 +263,7 @@ static void toJson(TCPSSLOptions obj, java.util.Map json) { json.put("trustStoreOptions", obj.getTrustStoreOptions().toJson()); } json.put("useAlpn", obj.isUseAlpn()); + json.put("useHybrid", obj.isUseHybrid()); json.put("writeIdleTimeout", obj.getWriteIdleTimeout()); } } diff --git a/src/main/java/io/vertx/core/eventbus/EventBusOptions.java b/src/main/java/io/vertx/core/eventbus/EventBusOptions.java index 955260e319d..85b2c6d7604 100644 --- a/src/main/java/io/vertx/core/eventbus/EventBusOptions.java +++ b/src/main/java/io/vertx/core/eventbus/EventBusOptions.java @@ -475,6 +475,11 @@ public EventBusOptions setUseAlpn(boolean useAlpn) { return (EventBusOptions) super.setUseAlpn(useAlpn); } + @Override + public EventBusOptions setUseHybrid(boolean useHybrid) { + return (EventBusOptions) super.setUseHybrid(useHybrid); + } + @Override public EventBusOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (EventBusOptions) super.setSslEngineOptions(sslEngineOptions); diff --git a/src/main/java/io/vertx/core/http/HttpClientOptions.java b/src/main/java/io/vertx/core/http/HttpClientOptions.java index d5516ee040e..9cf2f3ed4dd 100755 --- a/src/main/java/io/vertx/core/http/HttpClientOptions.java +++ b/src/main/java/io/vertx/core/http/HttpClientOptions.java @@ -1154,6 +1154,11 @@ public HttpClientOptions setUseAlpn(boolean useAlpn) { return (HttpClientOptions) super.setUseAlpn(useAlpn); } + @Override + public HttpClientOptions setUseHybrid(boolean useHybrid) { + return (HttpClientOptions) super.setUseHybrid(useHybrid); + } + @Override public HttpClientOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (HttpClientOptions) super.setSslEngineOptions(sslEngineOptions); diff --git a/src/main/java/io/vertx/core/http/HttpServerOptions.java b/src/main/java/io/vertx/core/http/HttpServerOptions.java index 447767ca7a0..9acadf97188 100755 --- a/src/main/java/io/vertx/core/http/HttpServerOptions.java +++ b/src/main/java/io/vertx/core/http/HttpServerOptions.java @@ -421,6 +421,18 @@ public HttpServerOptions setUseAlpn(boolean useAlpn) { return this; } + /* + Use X25519MLKEM768 instead of X25519 for key exchange. + X25519MLKEM768 is a hybrid protocol exchange ensuring that encryption will resist quantum computers + */ + @Override + public HttpServerOptions setUseHybrid(boolean useHybrid) { + super.setUseHybrid(useHybrid); + return this; + } + + + @Override public HttpServerOptions setKeyCertOptions(KeyCertOptions options) { super.setKeyCertOptions(options); diff --git a/src/main/java/io/vertx/core/http/WebSocketClientOptions.java b/src/main/java/io/vertx/core/http/WebSocketClientOptions.java index 2fa6757755d..abb0f2c89a9 100644 --- a/src/main/java/io/vertx/core/http/WebSocketClientOptions.java +++ b/src/main/java/io/vertx/core/http/WebSocketClientOptions.java @@ -552,6 +552,11 @@ public WebSocketClientOptions setUseAlpn(boolean useAlpn) { return (WebSocketClientOptions)super.setUseAlpn(useAlpn); } + @Override + public WebSocketClientOptions setUseHybrid(boolean useHybrid) { + return (WebSocketClientOptions)super.setUseHybrid(useHybrid); + } + @Override public WebSocketClientOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (WebSocketClientOptions)super.setSslEngineOptions(sslEngineOptions); diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java b/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java index f783d9a333e..e1b2d34f044 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java @@ -33,9 +33,12 @@ import io.vertx.core.impl.VertxInternal; import io.vertx.core.net.impl.*; import io.vertx.core.spi.metrics.HttpServerMetrics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -61,6 +64,7 @@ public class HttpServerWorker implements BiConsumer private final CompressionOptions[] compressionOptions; private final Function encodingDetector; private final GlobalTrafficShapingHandler trafficShapingHandler; + private static final Logger log = LoggerFactory.getLogger(SslChannelProvider.class); public HttpServerWorker(ContextInternal context, Supplier streamContextSupplier, @@ -115,18 +119,28 @@ public void accept(Channel ch, SslChannelProvider sslChannelProvider) { if (idle != null) { ch.pipeline().remove(idle); } - configurePipeline(future.getNow(), sslChannelProvider); + try { + configurePipeline(future.getNow(), sslChannelProvider); + } catch (Exception e) { + log.error(e.getMessage()+ ", now closing the channel"); + ch.close(); + } } else { //No need to close the channel.HAProxyMessageDecoder already did handleException(future.cause()); } }); } else { - configurePipeline(ch, sslChannelProvider); + try { + configurePipeline(ch, sslChannelProvider); + } catch (Exception e) { + log.error(e.getMessage()+ ", now closing the channel"); + ch.close(); + } } } - private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider) { + private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (options.isSsl()) { pipeline.addLast("ssl", sslChannelProvider.createServerHandler(HttpUtils.socketAddressToHostAndPort(ch.remoteAddress()))); diff --git a/src/main/java/io/vertx/core/net/ClientOptionsBase.java b/src/main/java/io/vertx/core/net/ClientOptionsBase.java index 2fb8b876042..703e34c6bd6 100755 --- a/src/main/java/io/vertx/core/net/ClientOptionsBase.java +++ b/src/main/java/io/vertx/core/net/ClientOptionsBase.java @@ -340,6 +340,11 @@ public ClientOptionsBase setUseAlpn(boolean useAlpn) { return (ClientOptionsBase) super.setUseAlpn(useAlpn); } + @Override + public ClientOptionsBase setUseHybrid(boolean useHyrbrid) { + return (ClientOptionsBase) super.setUseHybrid(useHyrbrid); + } + @Override public ClientOptionsBase setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (ClientOptionsBase) super.setSslEngineOptions(sslEngineOptions); diff --git a/src/main/java/io/vertx/core/net/NetClientOptions.java b/src/main/java/io/vertx/core/net/NetClientOptions.java index 79f4bef00da..b4b3a63cb88 100755 --- a/src/main/java/io/vertx/core/net/NetClientOptions.java +++ b/src/main/java/io/vertx/core/net/NetClientOptions.java @@ -263,6 +263,11 @@ public NetClientOptions setUseAlpn(boolean useAlpn) { return (NetClientOptions) super.setUseAlpn(useAlpn); } + @Override + public NetClientOptions setUseHybrid(boolean useHybrid) { + return (NetClientOptions) super.setUseHybrid(useHybrid); + } + @Override public NetClientOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (NetClientOptions) super.setSslEngineOptions(sslEngineOptions); diff --git a/src/main/java/io/vertx/core/net/NetServerOptions.java b/src/main/java/io/vertx/core/net/NetServerOptions.java index 0c9352d4c6a..c8df4c07457 100755 --- a/src/main/java/io/vertx/core/net/NetServerOptions.java +++ b/src/main/java/io/vertx/core/net/NetServerOptions.java @@ -221,8 +221,8 @@ public NetServerOptions setSsl(boolean ssl) { } @Override - public NetServerOptions setUseAlpn(boolean useAlpn) { - super.setUseAlpn(useAlpn); + public NetServerOptions setUseHybrid(boolean useHybrid) { + super.setUseHybrid(useHybrid); return this; } diff --git a/src/main/java/io/vertx/core/net/SSLOptions.java b/src/main/java/io/vertx/core/net/SSLOptions.java index ed3f0bc0f69..f01f6e6da85 100644 --- a/src/main/java/io/vertx/core/net/SSLOptions.java +++ b/src/main/java/io/vertx/core/net/SSLOptions.java @@ -37,7 +37,11 @@ public class SSLOptions { /** * Default use alpn = false */ - public static final boolean DEFAULT_USE_ALPN = false; + public static final boolean DEFAULT_USE_ALPN = false; /** + + * Default use hybrid = false + */ + public static final boolean DEFAULT_USE_HYBRID = false; /** * The default value of SSL handshake timeout = 10 @@ -66,6 +70,7 @@ public class SSLOptions { private ArrayList crlPaths; private ArrayList crlValues; private boolean useAlpn; + private boolean useHybrid; private Set enabledSecureTransportProtocols; /** @@ -99,6 +104,7 @@ public SSLOptions(SSLOptions other) { this.crlPaths = new ArrayList<>(other.getCrlPaths()); this.crlValues = new ArrayList<>(other.getCrlValues()); this.useAlpn = other.useAlpn; + this.useHybrid = other.useHybrid; this.enabledSecureTransportProtocols = other.getEnabledSecureTransportProtocols() == null ? new LinkedHashSet<>() : new LinkedHashSet<>(other.getEnabledSecureTransportProtocols()); } @@ -110,6 +116,7 @@ private void init() { crlPaths = new ArrayList<>(); crlValues = new ArrayList<>(); useAlpn = DEFAULT_USE_ALPN; + useHybrid = DEFAULT_USE_HYBRID; enabledSecureTransportProtocols = new LinkedHashSet<>(DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS); } @@ -236,6 +243,13 @@ public boolean isUseAlpn() { return useAlpn; } + /** + * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 + */ + public boolean isUseHybrid() { + return useHybrid; + } + /** * Set the ALPN usage. * @@ -246,6 +260,11 @@ public SSLOptions setUseAlpn(boolean useAlpn) { return this; } + public SSLOptions setUseHybrid(boolean useHybrid) { + this.useHybrid = useHybrid; + return this; + } + /** * Returns the enabled SSL/TLS protocols * @return the enabled protocols diff --git a/src/main/java/io/vertx/core/net/TCPSSLOptions.java b/src/main/java/io/vertx/core/net/TCPSSLOptions.java index a697da66476..bb8516b0393 100755 --- a/src/main/java/io/vertx/core/net/TCPSSLOptions.java +++ b/src/main/java/io/vertx/core/net/TCPSSLOptions.java @@ -712,6 +712,13 @@ public boolean isUseAlpn() { return sslOptions.isUseAlpn(); } + /** + * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 + */ + public boolean isUseHybrid() { + return sslOptions.isUseHybrid(); + } + /** * Set the ALPN usage. * @@ -722,6 +729,11 @@ public TCPSSLOptions setUseAlpn(boolean useAlpn) { return this; } + public TCPSSLOptions setUseHybrid(boolean useHybrid) { + sslOptions.setUseHybrid(useHybrid); + return this; + } + /** * @return the SSL engine implementation to use */ diff --git a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java index 10006a636e3..7d5d1266d16 100644 --- a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java +++ b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java @@ -34,6 +34,8 @@ import io.vertx.core.net.ProxyOptions; import io.vertx.core.net.ProxyType; import io.vertx.core.net.SocketAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.SSLHandshakeException; import java.net.InetAddress; @@ -49,6 +51,7 @@ */ public final class ChannelProvider { + private static final Logger log = LoggerFactory.getLogger(ChannelProvider.class); private final Bootstrap bootstrap; private final SslChannelProvider sslContextProvider; private final ContextInternal context; @@ -115,9 +118,17 @@ private void connect(Handler handler, SocketAddress remoteAddress, Sock private void initSSL(Handler handler, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, Channel ch, Promise channelHandler) { if (ssl) { - SslHandler sslHandler = sslContextProvider.createClientSslHandler(peerAddress, serverName, useAlpn); + SslHandler sslHandler = null; + try { + sslHandler = sslContextProvider.createClientSslHandler(peerAddress, serverName, useAlpn); + } catch (Exception e) { + log.error(e.getMessage()); + ch.close(); + return; + } ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); + SslHandler finalSslHandler = sslHandler; pipeline.addLast(new ChannelInboundHandlerAdapter() { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { @@ -127,7 +138,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (completion.isSuccess()) { // Remove from the pipeline after handshake result ctx.pipeline().remove(this); - applicationProtocol = sslHandler.applicationProtocol(); + applicationProtocol = finalSslHandler.applicationProtocol(); if (handler != null) { context.dispatch(ch, handler); } diff --git a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java index 9f8c3ca4949..a47925e94df 100644 --- a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java @@ -39,6 +39,7 @@ import io.vertx.core.spi.metrics.VertxMetrics; import io.vertx.core.streams.ReadStream; +import java.util.concurrent.ExecutionException; import java.util.function.BiConsumer; /** @@ -209,18 +210,28 @@ public void accept(Channel ch, SslChannelProvider sslChannelProvider) { if (idle != null) { ch.pipeline().remove(idle); } - configurePipeline(future.getNow(), sslChannelProvider); + try { + configurePipeline(future.getNow(), sslChannelProvider); + } catch (Exception e) { + log.error(e.getMessage()+ ", now closing the channel"); + ch.close(); + } } else { //No need to close the channel.HAProxyMessageDecoder already did handleException(future.cause()); } }); } else { - configurePipeline(ch, sslChannelProvider); + try { + configurePipeline(ch, sslChannelProvider); + } catch (Exception e) { + log.error(e.getMessage()+ ", now closing the channel"); + ch.close(); + } } } - private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider) { + private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider) throws Exception{ if (options.isSsl()) { ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler(HttpUtils.socketAddressToHostAndPort(ch.remoteAddress()))); ChannelPromise p = ch.newPromise(); diff --git a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java index 883b653591f..4b8a2c001df 100644 --- a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java @@ -338,7 +338,12 @@ public Future upgradeToSsl(String serverName) { if (remoteAddress != null) { sslHandler = sslChannelProvider.createClientSslHandler(remoteAddress, serverName, false); } else { - sslHandler = sslChannelProvider.createServerHandler(HttpUtils.socketAddressToHostAndPort(chctx.channel().remoteAddress())); + try { + sslHandler = sslChannelProvider.createServerHandler(HttpUtils.socketAddressToHostAndPort(chctx.channel().remoteAddress())); + } catch (Exception e) { + promise.fail(e); + return; + } } chctx.pipeline().addFirst("ssl", sslHandler); } else { diff --git a/src/main/java/io/vertx/core/net/impl/SSLHelper.java b/src/main/java/io/vertx/core/net/impl/SSLHelper.java index 17747e45702..50ac4700abe 100755 --- a/src/main/java/io/vertx/core/net/impl/SSLHelper.java +++ b/src/main/java/io/vertx/core/net/impl/SSLHelper.java @@ -128,6 +128,7 @@ public static SSLEngineOptions resolveEngineOptions(SSLEngineOptions engineOptio private final ClientAuth clientAuth; private final boolean client; private final boolean useAlpn; + private final boolean useHybrid; private final String endpointIdentificationAlgorithm; private final SSLEngineOptions sslEngineOptions; private final List applicationProtocols; @@ -139,6 +140,7 @@ public static SSLEngineOptions resolveEngineOptions(SSLEngineOptions engineOptio private Future cachedProvider; public SSLHelper(TCPSSLOptions options, List applicationProtocols) { + this.useHybrid = options.isUseHybrid(); this.sslEngineOptions = options.getSslEngineOptions(); this.ssl = options.isSsl(); this.useAlpn = options.isUseAlpn(); @@ -264,7 +266,8 @@ public Future buildChannelProvider(SSLOptions sslOptions, Co trustAll, useAlpn, ctx.owner().getInternalWorkerPool().executor(), - c.useWorkerPool + c.useWorkerPool, + useHybrid )); } diff --git a/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java b/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java index 4c54eb412b9..2357e75d9ce 100644 --- a/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java +++ b/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java @@ -11,20 +11,28 @@ package io.vertx.core.net.impl; import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; import io.netty.handler.ssl.SniHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; +import io.netty.internal.tcnative.SSL; import io.netty.util.AsyncMapping; import io.netty.util.concurrent.ImmediateExecutor; import io.vertx.core.VertxException; import io.vertx.core.net.HostAndPort; import io.vertx.core.net.SocketAddress; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -35,12 +43,14 @@ */ public class SslChannelProvider { + private static final Logger log = LoggerFactory.getLogger(SslChannelProvider.class); private final long sslHandshakeTimeout; private final TimeUnit sslHandshakeTimeoutUnit; private final Executor workerPool; private final boolean useWorkerPool; private final boolean sni; private final boolean useAlpn; + private final boolean useHybrid; private final boolean trustAll; private final SslContextProvider sslContextProvider; private final SslContext[] sslContexts = new SslContext[2]; @@ -55,7 +65,7 @@ public SslChannelProvider(SslContextProvider sslContextProvider, boolean trustAll, boolean useAlpn, Executor workerPool, - boolean useWorkerPool) { + boolean useWorkerPool, boolean useHybrid) { this.workerPool = workerPool; this.useWorkerPool = useWorkerPool; this.useAlpn = useAlpn; @@ -64,6 +74,7 @@ public SslChannelProvider(SslContextProvider sslContextProvider, this.sslHandshakeTimeout = sslHandshakeTimeout; this.sslHandshakeTimeoutUnit = sslHandshakeTimeoutUnit; this.sslContextProvider = sslContextProvider; + this.useHybrid = useHybrid; } public int sniEntrySize() { @@ -131,7 +142,7 @@ public SslContext sslServerContext(boolean useAlpn) { }; } - public SslHandler createClientSslHandler(SocketAddress remoteAddress, String serverName, boolean useAlpn) { + public SslHandler createClientSslHandler(SocketAddress remoteAddress, String serverName, boolean useAlpn) throws Exception { SslContext sslContext = sslClientContext(serverName, useAlpn); SslHandler sslHandler; Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; @@ -140,19 +151,35 @@ public SslHandler createClientSslHandler(SocketAddress remoteAddress, String ser } else { sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, remoteAddress.host(), remoteAddress.port(), delegatedTaskExec); } + if (useHybrid) { + SSLEngine engine = sslHandler.engine(); + try { + long sslPtr = ((ReferenceCountedOpenSslEngine) engine).sslPointer(); + boolean success = SSL.setCurvesList(sslPtr, "X25519MLKEM768"); + if (!success) { + throw new Exception("Failed to set hybrid PQC groups on SSL instance"); + } + } catch (Exception e) { + throw new Exception("Unable to create sslHandler: "+e.getMessage()); + } + } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); return sslHandler; } - public ChannelHandler createServerHandler(HostAndPort remoteAddress) { + public ChannelHandler createServerHandler(HostAndPort remoteAddress) throws Exception { if (sni) { - return createSniHandler(remoteAddress); + SniHandler sniHandler = createSniHandler(useHybrid, remoteAddress); + if(sniHandler == null){ + throw new Exception("Unable to create a SNI handler"); + } + return sniHandler; } else { - return createServerSslHandler(useAlpn, remoteAddress); + return createServerSslHandler(useAlpn, useHybrid, remoteAddress); } } - private SslHandler createServerSslHandler(boolean useAlpn, HostAndPort remoteAddress) { + private SslHandler createServerSslHandler(boolean useAlpn, boolean useHybrid, HostAndPort remoteAddress) throws Exception { SslContext sslContext = sslServerContext(useAlpn); Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; SslHandler sslHandler; @@ -161,13 +188,26 @@ private SslHandler createServerSslHandler(boolean useAlpn, HostAndPort remoteAdd } else { sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); } + if (useHybrid) { + SSLEngine engine = sslHandler.engine(); + try { + long sslPtr = ((ReferenceCountedOpenSslEngine) engine).sslPointer(); + boolean success = SSL.setCurvesList(sslPtr, "X25519MLKEM768"); + if (!success) { + throw new Exception("Failed to set hybrid PQC groups on SSL instance"); + } + } catch (Exception e) { + throw new Exception("Unable to create sslHandler: "+e.getMessage()); + } + } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); return sslHandler; } - private SniHandler createSniHandler(HostAndPort remoteAddress) { + private SniHandler createSniHandler(boolean useHybrid, HostAndPort remoteAddress) { Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; - return new VertxSniHandler(serverNameMapping(), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec, remoteAddress); + return new VertxSniHandler(serverNameMapping(), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec, + useHybrid, remoteAddress); } private static int idx(boolean useAlpn) { diff --git a/src/main/java/io/vertx/core/net/impl/VertxSniHandler.java b/src/main/java/io/vertx/core/net/impl/VertxSniHandler.java index 07142c6d1c4..a3357d3f64b 100644 --- a/src/main/java/io/vertx/core/net/impl/VertxSniHandler.java +++ b/src/main/java/io/vertx/core/net/impl/VertxSniHandler.java @@ -11,12 +11,17 @@ package io.vertx.core.net.impl; import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; import io.netty.handler.ssl.SniHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; +import io.netty.internal.tcnative.SSL; import io.netty.util.AsyncMapping; import io.vertx.core.net.HostAndPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.net.ssl.SSLEngine; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -29,12 +34,16 @@ class VertxSniHandler extends SniHandler { private final Executor delegatedTaskExec; private final HostAndPort remoteAddress; + private final boolean useHybrid; + + private static final Logger log = LoggerFactory.getLogger(SslChannelProvider.class); public VertxSniHandler(AsyncMapping mapping, long handshakeTimeoutMillis, Executor delegatedTaskExec, - HostAndPort remoteAddress) { + boolean useHybrid, HostAndPort remoteAddress) { super(mapping, handshakeTimeoutMillis); this.delegatedTaskExec = delegatedTaskExec; + this.useHybrid = useHybrid; this.remoteAddress = remoteAddress; } @@ -46,6 +55,22 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato } else { sslHandler = context.newHandler(allocator, delegatedTaskExec); } + if(useHybrid){ + SSLEngine engine = sslHandler.engine(); + try { + long sslPtr = ((ReferenceCountedOpenSslEngine) engine).sslPointer(); + boolean success = SSL.setCurvesList(sslPtr, "X25519MLKEM768"); + if (!success) { + throw new Exception("Failed to set hybrid PQC groups on SSL instance"); + } + } catch (Exception e) { + /* + todo : would like to throw instead of returning null to be consistent with + io.vertx.core.net.impl.SslChannelProvider.createServerSslHandler(...) but can't as we extend a netty class here. + */ + return null; + } + } sslHandler.setHandshakeTimeout(handshakeTimeoutMillis, TimeUnit.MILLISECONDS); return sslHandler; }