From 597fbaa90634eb6e0d0db3de62c40906dc90a83a Mon Sep 17 00:00:00 2001 From: Arthur Navarro Date: Wed, 6 May 2026 15:22:03 +0200 Subject: [PATCH 1/5] Add support for hybrid key exchange protocol x25519mlkem768 --- vertx-core/pom.xml | 10 ++++-- vertx-core/src/main/asciidoc/http.adoc | 4 +++ .../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 | 6 ++++ .../core/http/WebSocketClientOptions.java | 5 +++ .../core/internal/net/SslChannelProvider.java | 34 +++++++++++++++++-- .../core/internal/net/VertxSniHandler.java | 7 +++- .../io/vertx/core/net/ClientOptionsBase.java | 5 +++ .../io/vertx/core/net/ClientSSLOptions.java | 5 +++ .../io/vertx/core/net/NetClientOptions.java | 5 +++ .../io/vertx/core/net/NetServerOptions.java | 6 ++++ .../java/io/vertx/core/net/SSLOptions.java | 23 ++++++++++++- .../io/vertx/core/net/ServerSSLOptions.java | 5 +++ .../java/io/vertx/core/net/TCPSSLOptions.java | 13 +++++++ .../core/net/impl/tcp/ChannelProvider.java | 2 +- .../core/net/impl/tcp/NetServerImpl.java | 2 +- .../core/net/impl/tcp/NetSocketImpl.java | 4 +-- vertx-core/src/main/java/module-info.java | 1 + vertx-core/src/main/java21/module-info.java | 1 + vertx-core/src/test/java/module-info.java | 1 + 23 files changed, 151 insertions(+), 10 deletions(-) diff --git a/vertx-core/pom.xml b/vertx-core/pom.xml index 9157f21259b..656fd841578 100644 --- a/vertx-core/pom.xml +++ b/vertx-core/pom.xml @@ -186,8 +186,14 @@ io.netty - netty-tcnative-boringssl-static - test + netty-tcnative-classes + 2.0.76.Final + + + io.smallrye + smallrye-openssl + linux-x86_64 + 0.1.4 com.aayushatharva.brotli4j diff --git a/vertx-core/src/main/asciidoc/http.adoc b/vertx-core/src/main/asciidoc/http.adoc index 6fe79bc4b6b..fcc815bfde5 100644 --- a/vertx-core/src/main/asciidoc/http.adoc +++ b/vertx-core/src/main/asciidoc/http.adoc @@ -81,6 +81,10 @@ To handle `h2` requests, TLS must be enabled: {@link examples.HttpExamples#configurationOfAnH2Server} ---- +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.net.SSLOptions#setUseHybrid(boolean)} and only works using OpenSsl. + With plain text (TLS is disabled), the server handles `h2c` requests that wants to upgrade connections presenting an HTTP/1.1 upgrade request to HTTP/2. It also accepts direct `h2c` (with prior knowledge) connections beginning with the `PRI * HTTP/2.0\r\nSM\r\n` preface. diff --git a/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java index 84edc50a94d..04c7a23c76b 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java @@ -41,6 +41,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; case "enabledSecureTransportProtocols": if (member.getValue() instanceof JsonArray) { java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); @@ -96,6 +101,7 @@ static void toJson(SSLOptions obj, java.util.Map json) { json.put("crlValues", array); } json.put("useAlpn", obj.isUseAlpn()); + json.put("useHybrid", obj.isUseHybrid()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); diff --git a/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java index 0a095ab9443..8f501423505 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java @@ -116,6 +116,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 "enabledSecureTransportProtocols": if (member.getValue() instanceof JsonArray) { java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); @@ -200,6 +205,7 @@ static void toJson(TCPSSLOptions obj, java.util.Map json) { json.put("crlValues", array); } json.put("useAlpn", obj.isUseAlpn()); + json.put("useHybrid", obj.isUseHybrid()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); diff --git a/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java b/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java index 95aedd9dca2..0106145fe92 100644 --- a/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java @@ -433,6 +433,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/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java b/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java index 4c0b3a17ba9..ea62e2f9091 100755 --- a/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java @@ -812,6 +812,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/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java b/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java index e745abe5215..902320b8ebe 100755 --- a/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java @@ -424,6 +424,12 @@ public HttpServerOptions setUseAlpn(boolean useAlpn) { return this; } + @Override + public HttpServerOptions setUseHybrid(boolean useHybrid) { + super.setUseHybrid(useHybrid); + return this; + } + @Override public HttpServerOptions setKeyCertOptions(KeyCertOptions options) { super.setKeyCertOptions(options); diff --git a/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java b/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java index 887ef118448..88655a22040 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java @@ -575,6 +575,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/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java b/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java index 09cad4eceb2..decfbc9912c 100644 --- a/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java +++ b/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java @@ -12,11 +12,15 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandler; +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.concurrent.ImmediateExecutor; import io.vertx.core.internal.VertxInternal; +import io.vertx.core.internal.logging.Logger; +import io.vertx.core.internal.logging.LoggerFactory; import io.vertx.core.internal.tls.ClientSslContextProvider; import io.vertx.core.internal.tls.ServerSslContextProvider; import io.vertx.core.internal.tls.SslContextProvider; @@ -34,15 +38,20 @@ */ public class SslChannelProvider { + private static final Logger log = LoggerFactory.getLogger(SslChannelProvider.class); + private final Executor workerPool; private final boolean sni; + private final boolean useHybrid; private final SslContextProvider sslContextProvider; public SslChannelProvider(VertxInternal vertx, SslContextProvider sslContextProvider, - boolean sni) { + boolean sni, + boolean useHybrid) { this.workerPool = vertx.internalWorkerPool().executor(); this.sni = sni; + this.useHybrid = useHybrid; this.sslContextProvider = sslContextProvider; } @@ -63,6 +72,9 @@ public SslHandler createClientSslHandler(HostAndPort peer, } else { sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); } + if (useHybrid) { + applyHybridCurves(sslHandler); + } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); return sslHandler; } @@ -84,13 +96,31 @@ private SslHandler createServerSslHandler(List applicationProtocols, lon } else { sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); } + if (useHybrid) { + applyHybridCurves(sslHandler); + } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); return sslHandler; } private SniHandler createSniHandler(List applicationProtocols, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit, HostAndPort remoteAddress) { Executor delegatedTaskExec = sslContextProvider.useWorkerPool() ? workerPool : ImmediateExecutor.INSTANCE; - return new VertxSniHandler(((ServerSslContextProvider)sslContextProvider).serverNameAsyncMapping(delegatedTaskExec, applicationProtocols), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec, remoteAddress); + return new VertxSniHandler(((ServerSslContextProvider)sslContextProvider).serverNameAsyncMapping(delegatedTaskExec, applicationProtocols), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec, + useHybrid, remoteAddress); + } + + static void applyHybridCurves(SslHandler sslHandler) { + try { + long sslPtr = ((ReferenceCountedOpenSslEngine) sslHandler.engine()).sslPointer(); + boolean success = SSL.setCurvesList(sslPtr, "X25519MLKEM768"); + if (!success) { + log.error("Failed to set hybrid PQC groups on SSL instance, closing engine to prevent non-PQC fallback"); + sslHandler.engine().closeOutbound(); + } + } catch (Exception e) { + log.error("Unable to apply hybrid PQC curves: " + e.getMessage() + ", closing engine to prevent non-PQC fallback"); + sslHandler.engine().closeOutbound(); + } } } diff --git a/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java b/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java index cbbceaa763e..8017903f25f 100644 --- a/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java +++ b/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java @@ -28,13 +28,15 @@ class VertxSniHandler extends SniHandler { private final Executor delegatedTaskExec; + private final boolean useHybrid; private final HostAndPort remoteAddress; 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 +48,9 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato } else { sslHandler = context.newHandler(allocator, delegatedTaskExec); } + if (useHybrid) { + SslChannelProvider.applyHybridCurves(sslHandler); + } sslHandler.setHandshakeTimeout(handshakeTimeoutMillis, TimeUnit.MILLISECONDS); return sslHandler; } diff --git a/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java b/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java index 76cae3829ca..53c51c09c5d 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java @@ -314,6 +314,11 @@ public ClientOptionsBase setUseAlpn(boolean useAlpn) { return (ClientOptionsBase) super.setUseAlpn(useAlpn); } + @Override + public ClientOptionsBase setUseHybrid(boolean useHybrid) { + return (ClientOptionsBase) super.setUseHybrid(useHybrid); + } + @Override public ClientOptionsBase setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (ClientOptionsBase) super.setSslEngineOptions(sslEngineOptions); diff --git a/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java index 0625889efbc..86a0cc81a22 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java @@ -130,6 +130,11 @@ public ClientSSLOptions setUseAlpn(boolean useAlpn) { return (ClientSSLOptions) super.setUseAlpn(useAlpn); } + @Override + public ClientSSLOptions setUseHybrid(boolean useHybrid) { + return (ClientSSLOptions) super.setUseHybrid(useHybrid); + } + @Override public ClientSSLOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { return (ClientSSLOptions) super.setSslHandshakeTimeout(sslHandshakeTimeout); diff --git a/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java index 1ee8c7b78d5..41115b62632 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java @@ -220,6 +220,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/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java index 6f2f7495532..13cd961de36 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java @@ -235,6 +235,12 @@ public NetServerOptions setUseAlpn(boolean useAlpn) { return this; } + @Override + public NetServerOptions setUseHybrid(boolean useHybrid) { + super.setUseHybrid(useHybrid); + return this; + } + @Override public NetServerOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { super.setSslEngineOptions(sslEngineOptions); diff --git a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java index e2123d72971..81949e4bba3 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java @@ -39,6 +39,11 @@ public class SSLOptions { */ 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 +71,7 @@ public class SSLOptions { List crlPaths; List crlValues; private boolean useAlpn; + private boolean useHybrid; private Set enabledSecureTransportProtocols; private List applicationLayerProtocols; @@ -90,6 +96,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()); this.applicationLayerProtocols = other.getApplicationLayerProtocols() != null ? new ArrayList<>(other.getApplicationLayerProtocols()) : null; } @@ -112,6 +119,7 @@ protected void init() { crlPaths = new ArrayList<>(); crlValues = new ArrayList<>(); useAlpn = DEFAULT_USE_ALPN; + useHybrid = DEFAULT_USE_HYBRID; enabledSecureTransportProtocols = new LinkedHashSet<>(DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS); applicationLayerProtocols = null; } @@ -253,6 +261,18 @@ public SSLOptions setUseAlpn(boolean useAlpn) { return this; } + /** + * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 + */ + public boolean isUseHybrid() { + return useHybrid; + } + + public SSLOptions setUseHybrid(boolean useHybrid) { + this.useHybrid = useHybrid; + return this; + } + /** * Returns the enabled SSL/TLS protocols * @return the enabled protocols @@ -365,6 +385,7 @@ public boolean equals(Object obj) { Objects.equals(crlPaths, that.crlPaths) && Objects.equals(crlValues, that.crlValues) && useAlpn == that.useAlpn && + useHybrid == that.useHybrid && Objects.equals(enabledSecureTransportProtocols, that.enabledSecureTransportProtocols); } return false; @@ -372,7 +393,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout), keyCertOptions, trustOptions, enabledCipherSuites, crlPaths, crlValues, useAlpn, enabledSecureTransportProtocols); + return Objects.hash(sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout), keyCertOptions, trustOptions, enabledCipherSuites, crlPaths, crlValues, useAlpn, useHybrid, enabledSecureTransportProtocols); } /** diff --git a/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java index 6fa8339e231..42568b7c9aa 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java @@ -128,6 +128,11 @@ public ServerSSLOptions setUseAlpn(boolean useAlpn) { return (ServerSSLOptions) super.setUseAlpn(useAlpn); } + @Override + public ServerSSLOptions setUseHybrid(boolean useHybrid) { + return (ServerSSLOptions) super.setUseHybrid(useHybrid); + } + @Override public ServerSSLOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { return (ServerSSLOptions) super.setSslHandshakeTimeout(sslHandshakeTimeout); diff --git a/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java index 138f7d1bd7e..ac054ec7270 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java @@ -632,6 +632,19 @@ public TCPSSLOptions setUseAlpn(boolean useAlpn) { return this; } + /** + * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 + */ + public boolean isUseHybrid() { + SSLOptions o = sslOptions; + return o != null && o.isUseHybrid(); + } + + public TCPSSLOptions setUseHybrid(boolean useHybrid) { + getOrCreateSSLOptions().setUseHybrid(useHybrid); + return this; + } + /** * @return the SSL engine implementation to use */ diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java index 485e2ead0fa..a374e225ba5 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java @@ -114,7 +114,7 @@ private void initSSL(HostAndPort peerAddress, String serverName, boolean ssl, } else { applicationProtocols = null; } - SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false); + SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false, sslOptions.isUseHybrid()); SslHandler sslHandler = sslChannelProvider.createClientSslHandler(peerAddress, serverName, applicationProtocols, sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java index d210433609e..bad8e19becc 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java @@ -236,7 +236,7 @@ private void configurePipeline(Channel ch, SslContextProvider sslContextProvider } else { applicationProtocols = null; } - SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni()); + SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni(), sslOptions.isUseHybrid()); ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler(applicationProtocols, sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit(), HttpUtils.socketAddressToHostAndPort(ch.remoteAddress()))); ChannelPromise p = ch.newPromise(); diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java index 226cfb7f586..5a144972cb8 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java @@ -133,12 +133,12 @@ private Future sslUpgrade(String serverName, SSLOptions sslOptions, ByteBu ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; ClientSslContextManager clientSslContextManager = (ClientSslContextManager)sslContextManager; f = clientSslContextManager.resolveSslContextProvider(clientSSLOptions, context) - .map(p -> new SslChannelProvider(context.owner(), p, false)); + .map(p -> new SslChannelProvider(context.owner(), p, false, clientSSLOptions.isUseHybrid())); } else { ServerSSLOptions serverSSLOptions = (ServerSSLOptions) sslOptions; ServerSslContextManager serverSslContextManager = (ServerSslContextManager)sslContextManager; f = serverSslContextManager.resolveSslContextProvider(serverSSLOptions, context) - .map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni())); + .map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni(), serverSSLOptions.isUseHybrid())); } return f.compose(provider -> { PromiseInternal p = context.promise(); diff --git a/vertx-core/src/main/java/module-info.java b/vertx-core/src/main/java/module-info.java index ee82b9c0edb..6ed7e4e2ca4 100644 --- a/vertx-core/src/main/java/module-info.java +++ b/vertx-core/src/main/java/module-info.java @@ -28,6 +28,7 @@ requires static io.netty.transport.unix.common; requires static io.netty.codec.haproxy; requires static io.netty.codec.classes.quic; + requires static io.netty.tcnative.classes.openssl; // Annotation processing diff --git a/vertx-core/src/main/java21/module-info.java b/vertx-core/src/main/java21/module-info.java index 92c0170f4af..7311d57fe72 100644 --- a/vertx-core/src/main/java21/module-info.java +++ b/vertx-core/src/main/java21/module-info.java @@ -30,6 +30,7 @@ requires static io.netty.transport.unix.common; requires static io.netty.codec.haproxy; requires static io.netty.codec.classes.quic; + requires static io.netty.tcnative.classes.openssl; // Annotation processing diff --git a/vertx-core/src/test/java/module-info.java b/vertx-core/src/test/java/module-info.java index decd225fd7a..d0cc143da68 100644 --- a/vertx-core/src/test/java/module-info.java +++ b/vertx-core/src/test/java/module-info.java @@ -37,6 +37,7 @@ requires io.netty.handler.proxy; requires io.netty.codec.http3; requires io.netty.codec.dns; + requires static io.netty.tcnative.classes.openssl; requires jdk.management; provides VerticleFactory with ClasspathVerticleFactory, io.vertx.tests.vertx.AccessEventBusFromInitVerticleFactory; From fc5e513bab8095c08cfbee6639243a36fb2a1afb Mon Sep 17 00:00:00 2001 From: Arthur Navarro Date: Wed, 6 May 2026 17:14:06 +0200 Subject: [PATCH 2/5] removed setUseHybrid everywhere but SslOptions (and its inheritors for fluent API), renamed useHybrid useHybridKeyExchangeProtocol added tests --- vertx-core/src/main/asciidoc/http.adoc | 2 +- .../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 | 6 - .../core/http/WebSocketClientOptions.java | 5 - .../core/internal/net/SslChannelProvider.java | 12 +- .../core/internal/net/VertxSniHandler.java | 8 +- .../io/vertx/core/net/ClientOptionsBase.java | 5 - .../io/vertx/core/net/ClientSSLOptions.java | 4 +- .../io/vertx/core/net/NetClientOptions.java | 5 - .../io/vertx/core/net/NetServerOptions.java | 6 - .../java/io/vertx/core/net/SSLOptions.java | 18 +- .../io/vertx/core/net/ServerSSLOptions.java | 4 +- .../java/io/vertx/core/net/TCPSSLOptions.java | 13 - .../core/net/impl/tcp/ChannelProvider.java | 2 +- .../core/net/impl/tcp/NetServerImpl.java | 2 +- .../core/net/impl/tcp/NetSocketImpl.java | 4 +- .../io/vertx/it/HybridKeyExchangeTest.java | 403 ++++++++++++++++++ 20 files changed, 434 insertions(+), 87 deletions(-) create mode 100644 vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java diff --git a/vertx-core/src/main/asciidoc/http.adoc b/vertx-core/src/main/asciidoc/http.adoc index fcc815bfde5..81baea24448 100644 --- a/vertx-core/src/main/asciidoc/http.adoc +++ b/vertx-core/src/main/asciidoc/http.adoc @@ -83,7 +83,7 @@ To handle `h2` requests, TLS must be enabled: 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.net.SSLOptions#setUseHybrid(boolean)} and only works using OpenSsl. +Hybrid key exchange must be enabled along with {@link io.vertx.core.net.SSLOptions#setuseHybridKeyExchangeProtocol(boolean)} and only works using OpenSsl. With plain text (TLS is disabled), the server handles `h2c` requests that wants to upgrade connections presenting an HTTP/1.1 upgrade request to HTTP/2. It also accepts direct `h2c` (with prior knowledge) connections beginning with diff --git a/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java index 04c7a23c76b..8db462f5372 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java @@ -41,9 +41,9 @@ static void fromJson(Iterable> json, SSLOpti obj.setUseAlpn((Boolean)member.getValue()); } break; - case "useHybrid": + case "useHybridKeyExchangeProtocol": if (member.getValue() instanceof Boolean) { - obj.setUseHybrid((Boolean)member.getValue()); + obj.setuseHybridKeyExchangeProtocol((Boolean)member.getValue()); } break; case "enabledSecureTransportProtocols": @@ -101,7 +101,7 @@ static void toJson(SSLOptions obj, java.util.Map json) { json.put("crlValues", array); } json.put("useAlpn", obj.isUseAlpn()); - json.put("useHybrid", obj.isUseHybrid()); + json.put("useHybridKeyExchangeProtocol", obj.isuseHybridKeyExchangeProtocol()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); diff --git a/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java index 8f501423505..0a095ab9443 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java @@ -116,11 +116,6 @@ 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 "enabledSecureTransportProtocols": if (member.getValue() instanceof JsonArray) { java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); @@ -205,7 +200,6 @@ static void toJson(TCPSSLOptions obj, java.util.Map json) { json.put("crlValues", array); } json.put("useAlpn", obj.isUseAlpn()); - json.put("useHybrid", obj.isUseHybrid()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); diff --git a/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java b/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java index 0106145fe92..95aedd9dca2 100644 --- a/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/eventbus/EventBusOptions.java @@ -433,11 +433,6 @@ 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/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java b/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java index ea62e2f9091..4c0b3a17ba9 100755 --- a/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java @@ -812,11 +812,6 @@ 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/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java b/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java index 902320b8ebe..e745abe5215 100755 --- a/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/HttpServerOptions.java @@ -424,12 +424,6 @@ public HttpServerOptions setUseAlpn(boolean useAlpn) { return this; } - @Override - public HttpServerOptions setUseHybrid(boolean useHybrid) { - super.setUseHybrid(useHybrid); - return this; - } - @Override public HttpServerOptions setKeyCertOptions(KeyCertOptions options) { super.setKeyCertOptions(options); diff --git a/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java b/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java index 88655a22040..887ef118448 100644 --- a/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/http/WebSocketClientOptions.java @@ -575,11 +575,6 @@ 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/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java b/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java index decfbc9912c..a88d2ee0c45 100644 --- a/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java +++ b/vertx-core/src/main/java/io/vertx/core/internal/net/SslChannelProvider.java @@ -42,16 +42,16 @@ public class SslChannelProvider { private final Executor workerPool; private final boolean sni; - private final boolean useHybrid; + private final boolean useHybridKeyExchangeProtocol; private final SslContextProvider sslContextProvider; public SslChannelProvider(VertxInternal vertx, SslContextProvider sslContextProvider, boolean sni, - boolean useHybrid) { + boolean useHybridKeyExchangeProtocol) { this.workerPool = vertx.internalWorkerPool().executor(); this.sni = sni; - this.useHybrid = useHybrid; + this.useHybridKeyExchangeProtocol = useHybridKeyExchangeProtocol; this.sslContextProvider = sslContextProvider; } @@ -72,7 +72,7 @@ public SslHandler createClientSslHandler(HostAndPort peer, } else { sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); } - if (useHybrid) { + if (useHybridKeyExchangeProtocol) { applyHybridCurves(sslHandler); } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); @@ -96,7 +96,7 @@ private SslHandler createServerSslHandler(List applicationProtocols, lon } else { sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); } - if (useHybrid) { + if (useHybridKeyExchangeProtocol) { applyHybridCurves(sslHandler); } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); @@ -106,7 +106,7 @@ private SslHandler createServerSslHandler(List applicationProtocols, lon private SniHandler createSniHandler(List applicationProtocols, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit, HostAndPort remoteAddress) { Executor delegatedTaskExec = sslContextProvider.useWorkerPool() ? workerPool : ImmediateExecutor.INSTANCE; return new VertxSniHandler(((ServerSslContextProvider)sslContextProvider).serverNameAsyncMapping(delegatedTaskExec, applicationProtocols), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec, - useHybrid, remoteAddress); + useHybridKeyExchangeProtocol, remoteAddress); } static void applyHybridCurves(SslHandler sslHandler) { diff --git a/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java b/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java index 8017903f25f..43209ae382c 100644 --- a/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java +++ b/vertx-core/src/main/java/io/vertx/core/internal/net/VertxSniHandler.java @@ -28,15 +28,15 @@ class VertxSniHandler extends SniHandler { private final Executor delegatedTaskExec; - private final boolean useHybrid; + private final boolean useHybridKeyExchangeProtocol; private final HostAndPort remoteAddress; public VertxSniHandler(AsyncMapping mapping, long handshakeTimeoutMillis, Executor delegatedTaskExec, - boolean useHybrid, HostAndPort remoteAddress) { + boolean useHybridKeyExchangeProtocol, HostAndPort remoteAddress) { super(mapping, handshakeTimeoutMillis); this.delegatedTaskExec = delegatedTaskExec; - this.useHybrid = useHybrid; + this.useHybridKeyExchangeProtocol = useHybridKeyExchangeProtocol; this.remoteAddress = remoteAddress; } @@ -48,7 +48,7 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato } else { sslHandler = context.newHandler(allocator, delegatedTaskExec); } - if (useHybrid) { + if (useHybridKeyExchangeProtocol) { SslChannelProvider.applyHybridCurves(sslHandler); } sslHandler.setHandshakeTimeout(handshakeTimeoutMillis, TimeUnit.MILLISECONDS); diff --git a/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java b/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java index 53c51c09c5d..76cae3829ca 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ClientOptionsBase.java @@ -314,11 +314,6 @@ public ClientOptionsBase setUseAlpn(boolean useAlpn) { return (ClientOptionsBase) super.setUseAlpn(useAlpn); } - @Override - public ClientOptionsBase setUseHybrid(boolean useHybrid) { - return (ClientOptionsBase) super.setUseHybrid(useHybrid); - } - @Override public ClientOptionsBase setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (ClientOptionsBase) super.setSslEngineOptions(sslEngineOptions); diff --git a/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java index 86a0cc81a22..906f3844c76 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java @@ -131,8 +131,8 @@ public ClientSSLOptions setUseAlpn(boolean useAlpn) { } @Override - public ClientSSLOptions setUseHybrid(boolean useHybrid) { - return (ClientSSLOptions) super.setUseHybrid(useHybrid); + public ClientSSLOptions setuseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { + return (ClientSSLOptions) super.setuseHybridKeyExchangeProtocol(useHybridKeyExchangeProtocol); } @Override diff --git a/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java index 41115b62632..1ee8c7b78d5 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetClientOptions.java @@ -220,11 +220,6 @@ 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/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java index 13cd961de36..6f2f7495532 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/NetServerOptions.java @@ -235,12 +235,6 @@ public NetServerOptions setUseAlpn(boolean useAlpn) { return this; } - @Override - public NetServerOptions setUseHybrid(boolean useHybrid) { - super.setUseHybrid(useHybrid); - return this; - } - @Override public NetServerOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { super.setSslEngineOptions(sslEngineOptions); diff --git a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java index 81949e4bba3..fe22ce4ab22 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java @@ -71,7 +71,7 @@ public class SSLOptions { List crlPaths; List crlValues; private boolean useAlpn; - private boolean useHybrid; + private boolean useHybridKeyExchangeProtocol; private Set enabledSecureTransportProtocols; private List applicationLayerProtocols; @@ -96,7 +96,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.useHybridKeyExchangeProtocol = other.useHybridKeyExchangeProtocol; this.enabledSecureTransportProtocols = other.getEnabledSecureTransportProtocols() == null ? new LinkedHashSet<>() : new LinkedHashSet<>(other.getEnabledSecureTransportProtocols()); this.applicationLayerProtocols = other.getApplicationLayerProtocols() != null ? new ArrayList<>(other.getApplicationLayerProtocols()) : null; } @@ -119,7 +119,7 @@ protected void init() { crlPaths = new ArrayList<>(); crlValues = new ArrayList<>(); useAlpn = DEFAULT_USE_ALPN; - useHybrid = DEFAULT_USE_HYBRID; + useHybridKeyExchangeProtocol = DEFAULT_USE_HYBRID; enabledSecureTransportProtocols = new LinkedHashSet<>(DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS); applicationLayerProtocols = null; } @@ -264,12 +264,12 @@ public SSLOptions setUseAlpn(boolean useAlpn) { /** * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 */ - public boolean isUseHybrid() { - return useHybrid; + public boolean isuseHybridKeyExchangeProtocol() { + return useHybridKeyExchangeProtocol; } - public SSLOptions setUseHybrid(boolean useHybrid) { - this.useHybrid = useHybrid; + public SSLOptions setuseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { + this.useHybridKeyExchangeProtocol = useHybridKeyExchangeProtocol; return this; } @@ -385,7 +385,7 @@ public boolean equals(Object obj) { Objects.equals(crlPaths, that.crlPaths) && Objects.equals(crlValues, that.crlValues) && useAlpn == that.useAlpn && - useHybrid == that.useHybrid && + useHybridKeyExchangeProtocol == that.useHybridKeyExchangeProtocol && Objects.equals(enabledSecureTransportProtocols, that.enabledSecureTransportProtocols); } return false; @@ -393,7 +393,7 @@ public boolean equals(Object obj) { @Override public int hashCode() { - return Objects.hash(sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout), keyCertOptions, trustOptions, enabledCipherSuites, crlPaths, crlValues, useAlpn, useHybrid, enabledSecureTransportProtocols); + return Objects.hash(sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout), keyCertOptions, trustOptions, enabledCipherSuites, crlPaths, crlValues, useAlpn, useHybridKeyExchangeProtocol, enabledSecureTransportProtocols); } /** diff --git a/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java index 42568b7c9aa..46bde3d6b13 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java @@ -129,8 +129,8 @@ public ServerSSLOptions setUseAlpn(boolean useAlpn) { } @Override - public ServerSSLOptions setUseHybrid(boolean useHybrid) { - return (ServerSSLOptions) super.setUseHybrid(useHybrid); + public ServerSSLOptions setuseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { + return (ServerSSLOptions) super.setuseHybridKeyExchangeProtocol(useHybridKeyExchangeProtocol); } @Override diff --git a/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java index ac054ec7270..138f7d1bd7e 100755 --- a/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/TCPSSLOptions.java @@ -632,19 +632,6 @@ public TCPSSLOptions setUseAlpn(boolean useAlpn) { return this; } - /** - * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 - */ - public boolean isUseHybrid() { - SSLOptions o = sslOptions; - return o != null && o.isUseHybrid(); - } - - public TCPSSLOptions setUseHybrid(boolean useHybrid) { - getOrCreateSSLOptions().setUseHybrid(useHybrid); - return this; - } - /** * @return the SSL engine implementation to use */ diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java index a374e225ba5..e144f834b0d 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java @@ -114,7 +114,7 @@ private void initSSL(HostAndPort peerAddress, String serverName, boolean ssl, } else { applicationProtocols = null; } - SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false, sslOptions.isUseHybrid()); + SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false, sslOptions.isuseHybridKeyExchangeProtocol()); SslHandler sslHandler = sslChannelProvider.createClientSslHandler(peerAddress, serverName, applicationProtocols, sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java index bad8e19becc..422fbd2e08c 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java @@ -236,7 +236,7 @@ private void configurePipeline(Channel ch, SslContextProvider sslContextProvider } else { applicationProtocols = null; } - SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni(), sslOptions.isUseHybrid()); + SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni(), sslOptions.isuseHybridKeyExchangeProtocol()); ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler(applicationProtocols, sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit(), HttpUtils.socketAddressToHostAndPort(ch.remoteAddress()))); ChannelPromise p = ch.newPromise(); diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java index 5a144972cb8..3031bcbbc0f 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java @@ -133,12 +133,12 @@ private Future sslUpgrade(String serverName, SSLOptions sslOptions, ByteBu ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; ClientSslContextManager clientSslContextManager = (ClientSslContextManager)sslContextManager; f = clientSslContextManager.resolveSslContextProvider(clientSSLOptions, context) - .map(p -> new SslChannelProvider(context.owner(), p, false, clientSSLOptions.isUseHybrid())); + .map(p -> new SslChannelProvider(context.owner(), p, false, clientSSLOptions.isuseHybridKeyExchangeProtocol())); } else { ServerSSLOptions serverSSLOptions = (ServerSSLOptions) sslOptions; ServerSslContextManager serverSslContextManager = (ServerSslContextManager)sslContextManager; f = serverSslContextManager.resolveSslContextProvider(serverSSLOptions, context) - .map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni(), serverSSLOptions.isUseHybrid())); + .map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni(), serverSSLOptions.isuseHybridKeyExchangeProtocol())); } return f.compose(provider -> { PromiseInternal p = context.promise(); diff --git a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java new file mode 100644 index 00000000000..dddce828fff --- /dev/null +++ b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.it; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.ssl.*; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.internal.tcnative.SSL; +import io.vertx.core.http.*; +import io.vertx.core.net.ClientSSLOptions; +import io.vertx.core.net.OpenSSLEngineOptions; +import io.vertx.core.net.ServerSSLOptions; +import io.vertx.test.tls.Cert; +import io.vertx.test.tls.Trust; +import io.vertx.test.http.HttpTestBase; +import org.junit.Test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Tests hybrid key exchange (X25519MLKEM768) with OpenSSL. + */ +public class HybridKeyExchangeTest extends HttpTestBase { + + @Test + public void testHybridKeyExchangeHandshake() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setKeyCertOptions(Cert.SERVER_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(new OpenSSLEngineOptions()) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("hybrid-ok")); + startServer(server); + + ClientSSLOptions hybridClientSsl = new ClientSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(new OpenSSLEngineOptions()) + .with(hybridClientSsl) + .build(); + + ClientSSLOptions nonHybridClientSsl = new ClientSSLOptions() + .setuseHybridKeyExchangeProtocol(false) + .setTrustAll(true); + HttpClientAgent client2 = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(new OpenSSLEngineOptions()) + .with(nonHybridClientSsl) + .build(); + + CompletableFuture cf1 = new CompletableFuture<>(); + CompletableFuture cf2 = new CompletableFuture<>(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete(onSuccess(req -> { + req.send().onComplete(onSuccess(resp -> { + assertEquals(200, resp.statusCode()); + assertEquals("TLSv1.3", req.connection().sslSession().getProtocol()); + resp.body().onComplete(onSuccess(body -> { + assertEquals("hybrid-ok", body.toString()); + cf1.complete(true); + })); + })); + })); + + client2.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( + onFailure(err -> { + assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); + cf2.complete(true); + }) + ); + + CompletableFuture.allOf(cf1, cf2).thenAccept((v) -> testComplete()).get(); + } + + @Test + public void testHybridKeyExchangeHandshakeMTLS() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setClientAuth(io.vertx.core.http.ClientAuth.REQUIRED) + .setKeyCertOptions(Cert.SERVER_PEM_ROOT_CA.get()) + .setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(new OpenSSLEngineOptions()) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> { + assertTrue(req.isSSL()); + req.response().end("mtls-hybrid-ok"); + }); + startServer(server); + + ClientSSLOptions hybridClientSsl = new ClientSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setKeyCertOptions(Cert.CLIENT_PEM_ROOT_CA.get()) + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(new OpenSSLEngineOptions()) + .with(hybridClientSsl) + .build(); + + ClientSSLOptions nonHybridClientSsl = new ClientSSLOptions() + .setuseHybridKeyExchangeProtocol(false) + .setKeyCertOptions(Cert.CLIENT_PEM_ROOT_CA.get()) + .setTrustAll(true); + HttpClientAgent client2 = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(new OpenSSLEngineOptions()) + .with(nonHybridClientSsl) + .build(); + + CompletableFuture cf1 = new CompletableFuture<>(); + CompletableFuture cf2 = new CompletableFuture<>(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete(onSuccess(req -> { + req.send().onComplete(onSuccess(resp -> { + assertEquals(200, resp.statusCode()); + assertEquals("TLSv1.3", req.connection().sslSession().getProtocol()); + resp.body().onComplete(onSuccess(body -> { + assertEquals("mtls-hybrid-ok", body.toString()); + cf1.complete(true); + })); + })); + })); + + client2.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( + onFailure(err -> { + assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); + cf2.complete(true); + }) + ); + + CompletableFuture.allOf(cf1, cf2).thenAccept((v) -> testComplete()).get(); + } + + @Test + public void testHybridFailsWhenPqcNotAvailable() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setKeyCertOptions(Cert.SERVER_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("should-not-reach")); + startServer(server); + + ClientSSLOptions clientSsl = new ClientSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(clientSsl) + .build(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( + onFailure(err -> { + assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); + testComplete(); + }) + ); + await(); + } + + @Test + public void testHybridWithRawNettySocket() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setKeyCertOptions(Cert.SERVER_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(new OpenSSLEngineOptions()) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("hybrid-ok")); + startServer(server); + + SslContext sslContext = SslContextBuilder.forClient() + .sslProvider(SslProvider.OPENSSL) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build(); + + CompletableFuture negotiatedGroup = new CompletableFuture<>(); + + EventLoopGroup group = new NioEventLoopGroup(); + try { + Bootstrap bootstrap = new Bootstrap() + .group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + SslHandler sslHandler = sslContext.newHandler(ch.alloc(), + DEFAULT_HTTPS_HOST, DEFAULT_HTTPS_PORT); + + ReferenceCountedOpenSslEngine engine = + (ReferenceCountedOpenSslEngine) sslHandler.engine(); + SSL.setCurvesList(engine.sslPointer(), "X25519MLKEM768"); + + ch.pipeline().addLast("server-hello-interceptor", + new ServerHelloGroupExtractor(negotiatedGroup)); + ch.pipeline().addLast("ssl", sslHandler); + + sslHandler.handshakeFuture().addListener(future -> { + if (!future.isSuccess()) { + negotiatedGroup.completeExceptionally(future.cause()); + } + }); + } + }); + + Channel ch = bootstrap.connect(DEFAULT_HTTPS_HOST, DEFAULT_HTTPS_PORT) + .sync().channel(); + + int groupId = negotiatedGroup.get(10, TimeUnit.SECONDS); + // 0x11ec = 4588 = X25519MLKEM768 see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml + assertEquals(0x11ec, groupId); + + ch.close().sync(); + } finally { + group.shutdownGracefully(); + } + } + + @Test + public void testHybridMTLSWithRawNettySocket() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setuseHybridKeyExchangeProtocol(true) + .setClientAuth(io.vertx.core.http.ClientAuth.REQUIRED) + .setKeyCertOptions(Cert.SERVER_PEM_ROOT_CA.get()) + .setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(new OpenSSLEngineOptions()) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> { + assertTrue(req.isSSL()); + req.response().end("mtls-hybrid-ok"); + }); + startServer(server); + + SslContext sslContext = SslContextBuilder.forClient() + .sslProvider(SslProvider.OPENSSL) + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .keyManager( + getClass().getClassLoader().getResourceAsStream("tls/client-cert-root-ca.pem"), + getClass().getClassLoader().getResourceAsStream("tls/client-key.pem")) + .build(); + + CompletableFuture negotiatedGroup = new CompletableFuture<>(); + + EventLoopGroup group = new NioEventLoopGroup(); + try { + Bootstrap bootstrap = new Bootstrap() + .group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + SslHandler sslHandler = sslContext.newHandler(ch.alloc(), + DEFAULT_HTTPS_HOST, DEFAULT_HTTPS_PORT); + + ReferenceCountedOpenSslEngine engine = + (ReferenceCountedOpenSslEngine) sslHandler.engine(); + SSL.setCurvesList(engine.sslPointer(), "X25519MLKEM768"); + + ch.pipeline().addLast("server-hello-interceptor", + new ServerHelloGroupExtractor(negotiatedGroup)); + ch.pipeline().addLast("ssl", sslHandler); + + sslHandler.handshakeFuture().addListener(future -> { + if (!future.isSuccess()) { + negotiatedGroup.completeExceptionally(future.cause()); + } + }); + } + }); + + Channel ch = bootstrap.connect(DEFAULT_HTTPS_HOST, DEFAULT_HTTPS_PORT) + .sync().channel(); + + int groupId = negotiatedGroup.get(10, TimeUnit.SECONDS); + // 0x11ec = 4588 = X25519MLKEM768 see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml + assertEquals(0x11ec, groupId); + + ch.close().sync(); + } finally { + group.shutdownGracefully(); + } + } + + + static class ServerHelloGroupExtractor extends ChannelInboundHandlerAdapter { + + private static final int HANDSHAKE_CONTENT_TYPE = 0x16; + private static final int SERVER_HELLO = 0x02; + private static final int KEY_SHARE_EXTENSION = 0x0033; + + private final CompletableFuture result; + + ServerHelloGroupExtractor(CompletableFuture result) { + this.result = result; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof ByteBuf && !result.isDone()) { + ByteBuf buf = (ByteBuf) msg; + int readerIndex = buf.readerIndex(); + try { + parseServerHello(buf); + } catch (Exception e) { + // Not a ServerHello or not parseable yet, ignore + } finally { + buf.readerIndex(readerIndex); + } + } + super.channelRead(ctx, msg); + } + + private void parseServerHello(ByteBuf buf) { + if (buf.readableBytes() < 5) return; + + int contentType = buf.readUnsignedByte(); + if (contentType != HANDSHAKE_CONTENT_TYPE) return; + + buf.skipBytes(2); // protocol version + int recordLength = buf.readUnsignedShort(); + if (buf.readableBytes() < recordLength) return; + + int handshakeType = buf.readUnsignedByte(); + if (handshakeType != SERVER_HELLO) return; + + buf.skipBytes(3); // handshake length + buf.skipBytes(2); // server version (0x0303) + buf.skipBytes(32); // random + + int sessionIdLen = buf.readUnsignedByte(); + buf.skipBytes(sessionIdLen); // session id + + buf.skipBytes(2); // cipher suite + buf.skipBytes(1); // compression method + + if (buf.readableBytes() < 2) return; + int extensionsLength = buf.readUnsignedShort(); + + int extensionsEnd = buf.readerIndex() + extensionsLength; + while (buf.readerIndex() < extensionsEnd && buf.readableBytes() >= 4) { + int extType = buf.readUnsignedShort(); + int extLen = buf.readUnsignedShort(); + + if (extType == KEY_SHARE_EXTENSION && extLen >= 2) { + int groupId = buf.readUnsignedShort(); + result.complete(groupId); + return; + } + buf.skipBytes(extLen); + } + } + } +} From 6d41440a9e20b99e8a107ac6b56ec105e3773cd8 Mon Sep 17 00:00:00 2001 From: Arthur Navarro Date: Wed, 13 May 2026 10:31:40 +0200 Subject: [PATCH 3/5] add tests to fail pqc if only client or sever is unable to use it + fix typo --- vertx-core/src/main/asciidoc/http.adoc | 2 +- .../vertx/core/net/SSLOptionsConverter.java | 4 +- .../io/vertx/core/net/ClientSSLOptions.java | 4 +- .../java/io/vertx/core/net/SSLOptions.java | 4 +- .../io/vertx/core/net/ServerSSLOptions.java | 4 +- .../core/net/impl/tcp/ChannelProvider.java | 2 +- .../core/net/impl/tcp/NetServerImpl.java | 2 +- .../core/net/impl/tcp/NetSocketImpl.java | 4 +- .../io/vertx/it/HybridKeyExchangeTest.java | 85 ++++++++++++++++--- 9 files changed, 88 insertions(+), 23 deletions(-) diff --git a/vertx-core/src/main/asciidoc/http.adoc b/vertx-core/src/main/asciidoc/http.adoc index 81baea24448..6a53028d5c4 100644 --- a/vertx-core/src/main/asciidoc/http.adoc +++ b/vertx-core/src/main/asciidoc/http.adoc @@ -83,7 +83,7 @@ To handle `h2` requests, TLS must be enabled: 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.net.SSLOptions#setuseHybridKeyExchangeProtocol(boolean)} and only works using OpenSsl. +Hybrid key exchange must be enabled along with {@link io.vertx.core.net.SSLOptions#setUseHybridKeyExchangeProtocol(boolean)} and only works using OpenSsl. With plain text (TLS is disabled), the server handles `h2c` requests that wants to upgrade connections presenting an HTTP/1.1 upgrade request to HTTP/2. It also accepts direct `h2c` (with prior knowledge) connections beginning with diff --git a/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java b/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java index 8db462f5372..827f7ffd93f 100644 --- a/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java +++ b/vertx-core/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java @@ -43,7 +43,7 @@ static void fromJson(Iterable> json, SSLOpti break; case "useHybridKeyExchangeProtocol": if (member.getValue() instanceof Boolean) { - obj.setuseHybridKeyExchangeProtocol((Boolean)member.getValue()); + obj.setUseHybridKeyExchangeProtocol((Boolean)member.getValue()); } break; case "enabledSecureTransportProtocols": @@ -101,7 +101,7 @@ static void toJson(SSLOptions obj, java.util.Map json) { json.put("crlValues", array); } json.put("useAlpn", obj.isUseAlpn()); - json.put("useHybridKeyExchangeProtocol", obj.isuseHybridKeyExchangeProtocol()); + json.put("useHybridKeyExchangeProtocol", obj.isUseHybridKeyExchangeProtocol()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); diff --git a/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java index 906f3844c76..6eb8cf57696 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ClientSSLOptions.java @@ -131,8 +131,8 @@ public ClientSSLOptions setUseAlpn(boolean useAlpn) { } @Override - public ClientSSLOptions setuseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { - return (ClientSSLOptions) super.setuseHybridKeyExchangeProtocol(useHybridKeyExchangeProtocol); + public ClientSSLOptions setUseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { + return (ClientSSLOptions) super.setUseHybridKeyExchangeProtocol(useHybridKeyExchangeProtocol); } @Override diff --git a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java index fe22ce4ab22..221aeaa89ea 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java @@ -264,11 +264,11 @@ public SSLOptions setUseAlpn(boolean useAlpn) { /** * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 */ - public boolean isuseHybridKeyExchangeProtocol() { + public boolean isUseHybridKeyExchangeProtocol() { return useHybridKeyExchangeProtocol; } - public SSLOptions setuseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { + public SSLOptions setUseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { this.useHybridKeyExchangeProtocol = useHybridKeyExchangeProtocol; return this; } diff --git a/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java index 46bde3d6b13..53e7d3204b0 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/ServerSSLOptions.java @@ -129,8 +129,8 @@ public ServerSSLOptions setUseAlpn(boolean useAlpn) { } @Override - public ServerSSLOptions setuseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { - return (ServerSSLOptions) super.setuseHybridKeyExchangeProtocol(useHybridKeyExchangeProtocol); + public ServerSSLOptions setUseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { + return (ServerSSLOptions) super.setUseHybridKeyExchangeProtocol(useHybridKeyExchangeProtocol); } @Override diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java index e144f834b0d..1d1f0aa6f9e 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/ChannelProvider.java @@ -114,7 +114,7 @@ private void initSSL(HostAndPort peerAddress, String serverName, boolean ssl, } else { applicationProtocols = null; } - SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false, sslOptions.isuseHybridKeyExchangeProtocol()); + SslChannelProvider sslChannelProvider = new SslChannelProvider(context.owner(), sslContextProvider, false, sslOptions.isUseHybridKeyExchangeProtocol()); SslHandler sslHandler = sslChannelProvider.createClientSslHandler(peerAddress, serverName, applicationProtocols, sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java index 422fbd2e08c..30f80b0c2f1 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java @@ -236,7 +236,7 @@ private void configurePipeline(Channel ch, SslContextProvider sslContextProvider } else { applicationProtocols = null; } - SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni(), sslOptions.isuseHybridKeyExchangeProtocol()); + SslChannelProvider sslChannelProvider = new SslChannelProvider(vertx, sslContextProvider, sslOptions.isSni(), sslOptions.isUseHybridKeyExchangeProtocol()); ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler(applicationProtocols, sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit(), HttpUtils.socketAddressToHostAndPort(ch.remoteAddress()))); ChannelPromise p = ch.newPromise(); diff --git a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java index 3031bcbbc0f..ec273b8dfd3 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java +++ b/vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetSocketImpl.java @@ -133,12 +133,12 @@ private Future sslUpgrade(String serverName, SSLOptions sslOptions, ByteBu ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; ClientSslContextManager clientSslContextManager = (ClientSslContextManager)sslContextManager; f = clientSslContextManager.resolveSslContextProvider(clientSSLOptions, context) - .map(p -> new SslChannelProvider(context.owner(), p, false, clientSSLOptions.isuseHybridKeyExchangeProtocol())); + .map(p -> new SslChannelProvider(context.owner(), p, false, clientSSLOptions.isUseHybridKeyExchangeProtocol())); } else { ServerSSLOptions serverSSLOptions = (ServerSSLOptions) sslOptions; ServerSslContextManager serverSslContextManager = (ServerSslContextManager)sslContextManager; f = serverSslContextManager.resolveSslContextProvider(serverSSLOptions, context) - .map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni(), serverSSLOptions.isuseHybridKeyExchangeProtocol())); + .map(p -> new SslChannelProvider(context.owner(), p, serverSSLOptions.isSni(), serverSSLOptions.isUseHybridKeyExchangeProtocol())); } return f.compose(provider -> { PromiseInternal p = context.promise(); diff --git a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java index dddce828fff..c02986ade67 100644 --- a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java +++ b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java @@ -40,7 +40,7 @@ public class HybridKeyExchangeTest extends HttpTestBase { @Test public void testHybridKeyExchangeHandshake() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); server.close(); @@ -55,7 +55,7 @@ public void testHybridKeyExchangeHandshake() throws Exception { startServer(server); ClientSSLOptions hybridClientSsl = new ClientSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setTrustAll(true); client = vertx.httpClientBuilder() .with(new HttpClientOptions().setSsl(true)) @@ -64,7 +64,7 @@ public void testHybridKeyExchangeHandshake() throws Exception { .build(); ClientSSLOptions nonHybridClientSsl = new ClientSSLOptions() - .setuseHybridKeyExchangeProtocol(false) + .setUseHybridKeyExchangeProtocol(false) .setTrustAll(true); HttpClientAgent client2 = vertx.httpClientBuilder() .with(new HttpClientOptions().setSsl(true)) @@ -99,7 +99,7 @@ public void testHybridKeyExchangeHandshake() throws Exception { @Test public void testHybridKeyExchangeHandshakeMTLS() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setClientAuth(io.vertx.core.http.ClientAuth.REQUIRED) .setKeyCertOptions(Cert.SERVER_PEM_ROOT_CA.get()) .setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); @@ -119,7 +119,7 @@ public void testHybridKeyExchangeHandshakeMTLS() throws Exception { startServer(server); ClientSSLOptions hybridClientSsl = new ClientSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.CLIENT_PEM_ROOT_CA.get()) .setTrustAll(true); client = vertx.httpClientBuilder() @@ -129,7 +129,7 @@ public void testHybridKeyExchangeHandshakeMTLS() throws Exception { .build(); ClientSSLOptions nonHybridClientSsl = new ClientSSLOptions() - .setuseHybridKeyExchangeProtocol(false) + .setUseHybridKeyExchangeProtocol(false) .setKeyCertOptions(Cert.CLIENT_PEM_ROOT_CA.get()) .setTrustAll(true); HttpClientAgent client2 = vertx.httpClientBuilder() @@ -162,10 +162,75 @@ public void testHybridKeyExchangeHandshakeMTLS() throws Exception { CompletableFuture.allOf(cf1, cf2).thenAccept((v) -> testComplete()).get(); } + @Test + public void testHybridFailsServerSideWhenPqcNotAvailable() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setUseHybridKeyExchangeProtocol(true) + .setKeyCertOptions(Cert.SERVER_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("should-not-reach")); + startServer(server); + + ClientSSLOptions clientSsl = new ClientSSLOptions() + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(clientSsl) + .build(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( + onFailure(err -> { + System.out.println("this is the err " + err); + assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); + testComplete(); + }) + ); + await(); + } + + @Test + public void testHybridFailsClientSideWhenPqcNotAvailable() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setKeyCertOptions(Cert.SERVER_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("should-not-reach")); + startServer(server); + + ClientSSLOptions clientSsl = new ClientSSLOptions() + .setUseHybridKeyExchangeProtocol(true) + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(clientSsl) + .build(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( + onFailure(err -> { + assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); + testComplete(); + }) + ); + await(); + } + @Test public void testHybridFailsWhenPqcNotAvailable() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); server.close(); @@ -179,7 +244,7 @@ public void testHybridFailsWhenPqcNotAvailable() throws Exception { startServer(server); ClientSSLOptions clientSsl = new ClientSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setTrustAll(true); client = vertx.httpClientBuilder() .with(new HttpClientOptions().setSsl(true)) @@ -198,7 +263,7 @@ public void testHybridFailsWhenPqcNotAvailable() throws Exception { @Test public void testHybridWithRawNettySocket() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); server.close(); @@ -262,7 +327,7 @@ protected void initChannel(SocketChannel ch) { @Test public void testHybridMTLSWithRawNettySocket() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() - .setuseHybridKeyExchangeProtocol(true) + .setUseHybridKeyExchangeProtocol(true) .setClientAuth(io.vertx.core.http.ClientAuth.REQUIRED) .setKeyCertOptions(Cert.SERVER_PEM_ROOT_CA.get()) .setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); From 4d60c176c37aaf5d5f68af36c4297236c7fcd78c Mon Sep 17 00:00:00 2001 From: Arthur Navarro Date: Wed, 13 May 2026 12:24:46 +0200 Subject: [PATCH 4/5] improve docs and add dependencies --- vertx-core/pom.xml | 1 - vertx-core/src/main/asciidoc/http.adoc | 8 +- .../java/io/vertx/core/net/SSLOptions.java | 20 ++++- .../io/vertx/it/HybridKeyExchangeTest.java | 73 +++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/vertx-core/pom.xml b/vertx-core/pom.xml index 656fd841578..68dfed89870 100644 --- a/vertx-core/pom.xml +++ b/vertx-core/pom.xml @@ -187,7 +187,6 @@ io.netty netty-tcnative-classes - 2.0.76.Final io.smallrye diff --git a/vertx-core/src/main/asciidoc/http.adoc b/vertx-core/src/main/asciidoc/http.adoc index 6a53028d5c4..74fd307334d 100644 --- a/vertx-core/src/main/asciidoc/http.adoc +++ b/vertx-core/src/main/asciidoc/http.adoc @@ -83,7 +83,13 @@ To handle `h2` requests, TLS must be enabled: 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.net.SSLOptions#setUseHybridKeyExchangeProtocol(boolean)} and only works using OpenSsl. + +Hybrid key exchange must be enabled with {@link io.vertx.core.net.SSLOptions#setUseHybridKeyExchangeProtocol(boolean)} and **requires OpenSSL** — it does not work with the JDK SSL engine. You must configure {@link io.vertx.core.net.OpenSSLEngineOptions} and add the following dependencies: + +- `io.netty:netty-tcnative-classes` (version managed by the Netty BOM) +- An OpenSSL provider such as `io.smallrye:smallrye-openssl` + +If OpenSSL is not available at runtime, the TLS handshake will fail rather than silently falling back to a non-quantum-safe key exchange. With plain text (TLS is disabled), the server handles `h2c` requests that wants to upgrade connections presenting an HTTP/1.1 upgrade request to HTTP/2. It also accepts direct `h2c` (with prior knowledge) connections beginning with diff --git a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java index 221aeaa89ea..ade0c5865a4 100644 --- a/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java +++ b/vertx-core/src/main/java/io/vertx/core/net/SSLOptions.java @@ -262,12 +262,30 @@ public SSLOptions setUseAlpn(boolean useAlpn) { } /** - * @return whether to use or not Hybrid key exchange protocol x25519MLKEM768 + * @return whether the hybrid key exchange protocol X25519MLKEM768 is enabled */ public boolean isUseHybridKeyExchangeProtocol() { return useHybridKeyExchangeProtocol; } + /** + * Enable or disable the hybrid post-quantum key exchange protocol X25519MLKEM768. + *

+ * When enabled, TLS connections will use X25519MLKEM768 for key exchange, providing + * protection against quantum computer attacks. + *

+ * This feature requires OpenSSL and will not work with the JDK SSL engine. You must: + *

    + *
  • Use {@link OpenSSLEngineOptions} as the SSL engine
  • + *
  • Have {@code io.netty:netty-tcnative-classes} on the classpath
  • + *
  • Have an OpenSSL provider (e.g. {@code io.smallrye:smallrye-openssl}) on the classpath
  • + *
+ * If OpenSSL is not available, the TLS handshake will fail rather than silently falling back + * to a non-quantum-safe key exchange. + * + * @param useHybridKeyExchangeProtocol {@code true} to enable hybrid key exchange + * @return a reference to this, so the API can be used fluently + */ public SSLOptions setUseHybridKeyExchangeProtocol(boolean useHybridKeyExchangeProtocol) { this.useHybridKeyExchangeProtocol = useHybridKeyExchangeProtocol; return this; diff --git a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java index c02986ade67..8cdfdddac71 100644 --- a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java +++ b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java @@ -260,6 +260,79 @@ public void testHybridFailsWhenPqcNotAvailable() throws Exception { await(); } + @Test + public void testHybridKeyExchangeWithSNI() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setUseHybridKeyExchangeProtocol(true) + .setSni(true) + .setKeyCertOptions(Cert.SNI_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(new OpenSSLEngineOptions()) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("sni-hybrid-ok")); + startServer(server); + + ClientSSLOptions hybridClientSsl = new ClientSSLOptions() + .setUseHybridKeyExchangeProtocol(true) + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(new OpenSSLEngineOptions()) + .with(hybridClientSsl) + .build(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete(onSuccess(req -> { + req.send().onComplete(onSuccess(resp -> { + assertEquals(200, resp.statusCode()); + assertEquals("TLSv1.3", req.connection().sslSession().getProtocol()); + resp.body().onComplete(onSuccess(body -> { + assertEquals("sni-hybrid-ok", body.toString()); + testComplete(); + })); + })); + })); + await(); + } + + @Test + public void testHybridFailsWithSNIWhenPqcNotAvailable() throws Exception { + ServerSSLOptions serverSslOptions = new ServerSSLOptions() + .setUseHybridKeyExchangeProtocol(true) + .setSni(true) + .setKeyCertOptions(Cert.SNI_PEM.get()); + + server.close(); + server = vertx.httpServerBuilder() + .with(new HttpServerConfig(new HttpServerOptions() + .setPort(DEFAULT_HTTPS_PORT) + .setHost(DEFAULT_HTTPS_HOST))) + .with(serverSslOptions) + .build(); + server.requestHandler(req -> req.response().end("should-not-reach")); + startServer(server); + + ClientSSLOptions clientSsl = new ClientSSLOptions() + .setTrustAll(true); + client = vertx.httpClientBuilder() + .with(new HttpClientOptions().setSsl(true)) + .with(clientSsl) + .build(); + + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( + onFailure(err -> { + assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); + testComplete(); + }) + ); + await(); + } + @Test public void testHybridWithRawNettySocket() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() From 7beb63205671653cd3c422a2fdaa3ebc141fceef Mon Sep 17 00:00:00 2001 From: Arthur Navarro Date: Wed, 13 May 2026 16:57:59 +0200 Subject: [PATCH 5/5] replace completableFuture with more idiomatic variants and fix sni test --- .../io/vertx/it/HybridKeyExchangeTest.java | 176 ++++++++---------- 1 file changed, 82 insertions(+), 94 deletions(-) diff --git a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java index 8cdfdddac71..07cae832b8f 100644 --- a/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java +++ b/vertx-core/src/test/java/io/vertx/it/HybridKeyExchangeTest.java @@ -29,6 +29,7 @@ import io.vertx.test.http.HttpTestBase; import org.junit.Test; +import javax.net.ssl.SSLHandshakeException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -43,7 +44,6 @@ public void testHybridKeyExchangeHandshake() throws Exception { .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -72,28 +72,24 @@ public void testHybridKeyExchangeHandshake() throws Exception { .with(nonHybridClientSsl) .build(); - CompletableFuture cf1 = new CompletableFuture<>(); - CompletableFuture cf2 = new CompletableFuture<>(); - - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete(onSuccess(req -> { - req.send().onComplete(onSuccess(resp -> { - assertEquals(200, resp.statusCode()); - assertEquals("TLSv1.3", req.connection().sslSession().getProtocol()); - resp.body().onComplete(onSuccess(body -> { - assertEquals("hybrid-ok", body.toString()); - cf1.complete(true); - })); - })); - })); - - client2.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( - onFailure(err -> { - assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); - cf2.complete(true); - }) - ); - - CompletableFuture.allOf(cf1, cf2).thenAccept((v) -> testComplete()).get(); + var bodyBuffer = client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .expecting(req -> req.connection().sslSession().getProtocol().equals("TLSv1.3")) + .compose(HttpClientRequest::send) + .expecting(HttpResponseExpectation.SC_OK) + .compose(HttpClientResponse::body) + .await(); + assertEquals("hybrid-ok", bodyBuffer.toString()); + + + try { + client2.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .await(); + fail("Expected SSLHandshakeException"); + } catch (Exception expected) { + assertEquals(SSLHandshakeException.class, expected.getClass()); + } + testComplete(); } @Test @@ -104,7 +100,6 @@ public void testHybridKeyExchangeHandshakeMTLS() throws Exception { .setKeyCertOptions(Cert.SERVER_PEM_ROOT_CA.get()) .setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -138,28 +133,25 @@ public void testHybridKeyExchangeHandshakeMTLS() throws Exception { .with(nonHybridClientSsl) .build(); - CompletableFuture cf1 = new CompletableFuture<>(); - CompletableFuture cf2 = new CompletableFuture<>(); - - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete(onSuccess(req -> { - req.send().onComplete(onSuccess(resp -> { - assertEquals(200, resp.statusCode()); - assertEquals("TLSv1.3", req.connection().sslSession().getProtocol()); - resp.body().onComplete(onSuccess(body -> { - assertEquals("mtls-hybrid-ok", body.toString()); - cf1.complete(true); - })); - })); - })); - - client2.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( - onFailure(err -> { - assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); - cf2.complete(true); - }) - ); - - CompletableFuture.allOf(cf1, cf2).thenAccept((v) -> testComplete()).get(); + + var buffer = client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .expecting(req -> req.connection().sslSession().getProtocol().equals("TLSv1.3")) + .compose(HttpClientRequest::send) + .expecting(HttpResponseExpectation.SC_OK) + .compose(HttpClientResponse::body) + .await(); + + assertEquals("mtls-hybrid-ok", buffer.toString()); + + try { + client2.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .await(); + fail("Expected a SSLHandshakeException"); + } catch (Exception e) { + assertTrue(e instanceof javax.net.ssl.SSLHandshakeException); + } + testComplete(); } @Test @@ -168,7 +160,6 @@ public void testHybridFailsServerSideWhenPqcNotAvailable() throws Exception { .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -185,14 +176,15 @@ public void testHybridFailsServerSideWhenPqcNotAvailable() throws Exception { .with(clientSsl) .build(); - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( - onFailure(err -> { - System.out.println("this is the err " + err); - assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); - testComplete(); - }) - ); - await(); + try { + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .await(); + fail("Expected a thing"); + } catch ( Exception e) { + assertTrue(e instanceof javax.net.ssl.SSLHandshakeException); + } + testComplete(); } @Test @@ -200,7 +192,6 @@ public void testHybridFailsClientSideWhenPqcNotAvailable() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -218,13 +209,15 @@ public void testHybridFailsClientSideWhenPqcNotAvailable() throws Exception { .with(clientSsl) .build(); - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( - onFailure(err -> { - assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); - testComplete(); - }) - ); - await(); + try { + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .await(); + fail("Expected an exceptin"); + } catch (Exception e) { + assertTrue(e instanceof javax.net.ssl.SSLHandshakeException); + } + testComplete(); } @Test @@ -233,7 +226,6 @@ public void testHybridFailsWhenPqcNotAvailable() throws Exception { .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -251,13 +243,15 @@ public void testHybridFailsWhenPqcNotAvailable() throws Exception { .with(clientSsl) .build(); - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( - onFailure(err -> { - assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); - testComplete(); - }) - ); - await(); + try { + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .await(); + fail("qsdfqsfd"); + } catch (Exception e) { + assertTrue(e instanceof javax.net.ssl.SSLHandshakeException); + } + testComplete(); } @Test @@ -265,9 +259,8 @@ public void testHybridKeyExchangeWithSNI() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() .setUseHybridKeyExchangeProtocol(true) .setSni(true) - .setKeyCertOptions(Cert.SNI_PEM.get()); + .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -287,17 +280,12 @@ public void testHybridKeyExchangeWithSNI() throws Exception { .with(hybridClientSsl) .build(); - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete(onSuccess(req -> { - req.send().onComplete(onSuccess(resp -> { - assertEquals(200, resp.statusCode()); - assertEquals("TLSv1.3", req.connection().sslSession().getProtocol()); - resp.body().onComplete(onSuccess(body -> { - assertEquals("sni-hybrid-ok", body.toString()); - testComplete(); - })); - })); - })); - await(); + var body = client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .expecting(HttpResponseExpectation.SC_OK) + .compose(HttpClientResponse::body) + .await(); + assertEquals("sni-hybrid-ok", body.toString()); } @Test @@ -305,9 +293,8 @@ public void testHybridFailsWithSNIWhenPqcNotAvailable() throws Exception { ServerSSLOptions serverSslOptions = new ServerSSLOptions() .setUseHybridKeyExchangeProtocol(true) .setSni(true) - .setKeyCertOptions(Cert.SNI_PEM.get()); + .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -324,12 +311,15 @@ public void testHybridFailsWithSNIWhenPqcNotAvailable() throws Exception { .with(clientSsl) .build(); - client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/").onComplete( - onFailure(err -> { - assertTrue(err instanceof javax.net.ssl.SSLHandshakeException); - testComplete(); - }) - ); + try { + client.request(HttpMethod.GET, DEFAULT_HTTPS_PORT, DEFAULT_HTTPS_HOST, "/") + .compose(HttpClientRequest::send) + .await(); + fail("Was expecting a SSLHandshakException"); + } catch(Exception e) { + assertTrue(e instanceof javax.net.ssl.SSLHandshakeException); + } + testComplete(); await(); } @@ -339,7 +329,6 @@ public void testHybridWithRawNettySocket() throws Exception { .setUseHybridKeyExchangeProtocol(true) .setKeyCertOptions(Cert.SERVER_PEM.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT) @@ -405,7 +394,6 @@ public void testHybridMTLSWithRawNettySocket() throws Exception { .setKeyCertOptions(Cert.SERVER_PEM_ROOT_CA.get()) .setTrustOptions(Trust.SERVER_PEM_ROOT_CA.get()); - server.close(); server = vertx.httpServerBuilder() .with(new HttpServerConfig(new HttpServerOptions() .setPort(DEFAULT_HTTPS_PORT)