diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
index 80d8100f94170..87d3001b6df73 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
@@ -8,6 +8,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
+import java.util.Set;
import org.jboss.logging.Logger;
@@ -114,12 +115,7 @@ static boolean applySslOptions(GrpcServerConfiguration config, HttpServerOptions
for (String cipher : sslConfig.cipherSuites.orElse(Collections.emptyList())) {
options.addEnabledCipherSuite(cipher);
}
-
- for (String protocol : sslConfig.protocols) {
- if (!protocol.isEmpty()) {
- options.addEnabledSecureTransportProtocol(protocol);
- }
- }
+ options.setEnabledSecureTransportProtocols(Set.copyOf(sslConfig.protocols));
options.setClientAuth(sslConfig.clientAuth);
return false;
}
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/SslServerConfig.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/SslServerConfig.java
index 77f47bfd2a09a..cbe70aa9addd5 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/SslServerConfig.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/config/SslServerConfig.java
@@ -49,7 +49,7 @@ public class SslServerConfig {
/**
* An optional trust store which holds the certificate information of the certificates to trust
- *
+ *
* The trust store can be either on classpath or an external file.
*/
@ConfigItem
@@ -75,7 +75,14 @@ public class SslServerConfig {
public Optional> cipherSuites;
/**
- * The list of protocols to explicitly enable.
+ * Sets the ordered list of enabled SSL/TLS protocols.
+ *
+ * If not set, it defaults to {@code "TLSv1.3, TLSv1.2"}.
+ * The following list of protocols are supported: {@code TLSv1, TLSv1.1, TLSv1.2, TLSv1.3}.
+ * To only enable {@code TLSv1.3}, set the value to {@code to "TLSv1.3"}.
+ *
+ * Note that setting an empty list, and enabling SSL/TLS is invalid.
+ * You must at least have one protocol.
*/
@DefaultConverter
@ConfigItem(defaultValue = "TLSv1.3,TLSv1.2")
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java
index f57a3f27b4d29..1726fceb34b34 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/ServerSslConfig.java
@@ -24,7 +24,14 @@ public class ServerSslConfig {
public Optional> cipherSuites;
/**
- * The list of protocols to explicitly enable.
+ * Sets the ordered list of enabled SSL/TLS protocols.
+ *
+ * If not set, it defaults to {@code "TLSv1.3, TLSv1.2"}.
+ * The following list of protocols are supported: {@code TLSv1, TLSv1.1, TLSv1.2, TLSv1.3}.
+ * To only enable {@code TLSv1.3}, set the value to {@code to "TLSv1.3"}.
+ *
+ * Note that setting an empty list, and enabling SSL/TLS is invalid.
+ * You must at least have one protocol.
*/
@DefaultConverter
@ConfigItem(defaultValue = "TLSv1.3,TLSv1.2")
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
index 18f5922d0140d..fc4e92e91ebcd 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
@@ -11,16 +11,7 @@
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TreeMap;
+import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -890,11 +881,7 @@ private static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeC
serverOptions.addEnabledCipherSuite(cipher);
}
- for (String protocol : sslConfig.protocols) {
- if (!protocol.isEmpty()) {
- serverOptions.addEnabledSecureTransportProtocol(protocol);
- }
- }
+ serverOptions.setEnabledSecureTransportProtocols(Set.copyOf(sslConfig.protocols));
serverOptions.setSsl(true);
serverOptions.setSni(sslConfig.sni);
int sslPort = httpConfiguration.determineSslPort(launchMode);
diff --git a/integration-tests/vertx-http/pom.xml b/integration-tests/vertx-http/pom.xml
index 8d5717f90c11a..2767bf0b58d16 100644
--- a/integration-tests/vertx-http/pom.xml
+++ b/integration-tests/vertx-http/pom.xml
@@ -47,6 +47,11 @@
awaitility
test
+
+ io.smallrye.reactive
+ smallrye-mutiny-vertx-web-client
+ test
+
diff --git a/integration-tests/vertx-http/src/main/java/io/quarkus/it/vertx/HelloResource.java b/integration-tests/vertx-http/src/main/java/io/quarkus/it/vertx/HelloResource.java
new file mode 100644
index 0000000000000..648c343c2ef1e
--- /dev/null
+++ b/integration-tests/vertx-http/src/main/java/io/quarkus/it/vertx/HelloResource.java
@@ -0,0 +1,12 @@
+package io.quarkus.it.vertx;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+@Path("/hello")
+public class HelloResource {
+ @GET
+ public String hello() {
+ return "hello";
+ }
+}
\ No newline at end of file
diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/ServerWithTLS13Only.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/ServerWithTLS13Only.java
new file mode 100644
index 0000000000000..f43436d40b13f
--- /dev/null
+++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/ServerWithTLS13Only.java
@@ -0,0 +1,14 @@
+package io.quarkus.it.vertx;
+
+import java.util.Map;
+
+import io.quarkus.test.junit.QuarkusTestProfile;
+
+public class ServerWithTLS13Only implements QuarkusTestProfile {
+
+ @Override
+ public Map getConfigOverrides() {
+ return Map.of(
+ "quarkus.http.ssl.protocols", "TLSv1.3");
+ }
+}
diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionDefaultTestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionDefaultTestCase.java
new file mode 100644
index 0000000000000..3c011653ffcb0
--- /dev/null
+++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionDefaultTestCase.java
@@ -0,0 +1,96 @@
+package io.quarkus.it.vertx;
+
+import java.util.Set;
+import java.util.concurrent.CompletionException;
+
+import javax.inject.Inject;
+import javax.net.ssl.SSLHandshakeException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.vertx.core.net.JksOptions;
+import io.vertx.ext.web.client.WebClientOptions;
+import io.vertx.mutiny.core.Vertx;
+import io.vertx.mutiny.ext.web.client.WebClient;
+
+@QuarkusTest
+public class TlsProtocolVersionDefaultTestCase {
+
+ @TestHTTPResource(value = "/hello", ssl = true)
+ String url;
+
+ @Inject
+ Vertx vertx;
+
+ @Test
+ void testWithWebClientRequestingMultipleTlsVersions() {
+ // The Web client is requesting "TLSv1", "TLSv1.1" or "TLSv1.2", the server is exposing TLSv1.3 and TLSv1.2 - all good
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ var resp = client.getAbs(url).sendAndAwait();
+ Assertions.assertEquals(200, resp.statusCode());
+ }
+
+ @Test
+ void testWithWebClientRequestingTls13() {
+ // The Web client is requesting "TLSv1.3", the server is exposing TLSv1.3 and TLSv1.2 - all good
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.3"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ var resp = client.getAbs(url).sendAndAwait();
+ Assertions.assertEquals(200, resp.statusCode());
+ }
+
+ @Test
+ void testWithWebClientRequestingTls12() {
+ // The Web client is requesting "TLSv1.2", the server is exposing TLSv1.3 and TLSv1.2 - all good
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.2"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ var resp = client.getAbs(url).sendAndAwait();
+ Assertions.assertEquals(200, resp.statusCode());
+ }
+
+ @Test
+ void testWithWebClientRequestingTls12And13() {
+ // The Web client is requesting TLS 1.2 or 1.3, the server is exposing TLSv1.3 and TLSv1.2 - all good
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.2", "TLSv1.3"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ var resp = client.getAbs(url).sendAndAwait();
+ Assertions.assertEquals(200, resp.statusCode());
+ }
+
+ @Test
+ void testWithWebClientRequestingTls11() {
+ // The Web client is requesting "TLSv1.1", the server is exposing TLSv1.3 and TLSv1.2 - KO
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.1"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ Throwable exception = Assertions.assertThrows(CompletionException.class, () -> client.getAbs(url).sendAndAwait());
+ Assertions.assertTrue(exception.getCause() instanceof SSLHandshakeException);
+ }
+}
diff --git a/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionSelectionTestCase.java b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionSelectionTestCase.java
new file mode 100644
index 0000000000000..eef20063570d0
--- /dev/null
+++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionSelectionTestCase.java
@@ -0,0 +1,98 @@
+package io.quarkus.it.vertx;
+
+import java.util.Set;
+import java.util.concurrent.CompletionException;
+
+import javax.inject.Inject;
+import javax.net.ssl.SSLHandshakeException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.common.http.TestHTTPResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import io.vertx.core.net.JksOptions;
+import io.vertx.ext.web.client.WebClientOptions;
+import io.vertx.mutiny.core.Vertx;
+import io.vertx.mutiny.ext.web.client.WebClient;
+
+@QuarkusTest
+@TestProfile(ServerWithTLS13Only.class)
+public class TlsProtocolVersionSelectionTestCase {
+
+ @TestHTTPResource(value = "/hello", ssl = true)
+ String url;
+
+ @Inject
+ Vertx vertx;
+
+ @Test
+ void testWithWebClientRequestingMultipleTlsVersions() {
+ // The Web client is requesting "TLSv1", "TLSv1.1" or "TLSv1.2", the server is exposing 1.3 - KO
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ Throwable exception = Assertions.assertThrows(CompletionException.class, () -> client.getAbs(url).sendAndAwait());
+ Assertions.assertTrue(exception.getCause() instanceof SSLHandshakeException);
+ }
+
+ @Test
+ void testWithWebClientRequestingTls13() {
+ // The Web client is requesting TLS 1.3, the server is exposing 1.3 - all good
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.3"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ var resp = client.getAbs(url).sendAndAwait();
+ Assertions.assertEquals(200, resp.statusCode());
+ }
+
+ @Test
+ void testWithWebClientRequestingTls12And13() {
+ // The Web client is requesting TLS q.2 or 1.3, the server is exposing 1.3 - all good
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.2", "TLSv1.3"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ var resp = client.getAbs(url).sendAndAwait();
+ Assertions.assertEquals(200, resp.statusCode());
+ }
+
+ @Test
+ void testWithWebClientRequestingTls12() {
+ // The Web client is requesting TLS 1.2, the server is exposing 1.3 only - KO
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.2"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ Throwable exception = Assertions.assertThrows(CompletionException.class, () -> client.getAbs(url).sendAndAwait());
+ Assertions.assertTrue(exception.getCause() instanceof SSLHandshakeException);
+ }
+
+ @Test
+ void testWithWebClientRequestingTls11() {
+ // The Web client is requesting TLS 1.1, the server is exposing 1.3 - KO
+ WebClient client = WebClient.create(vertx, new WebClientOptions().setSsl(true)
+ .setEnabledSecureTransportProtocols(Set.of("TLSv1.1"))
+ .setKeyStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-keystore-1.jks").setPassword("password"))
+ .setTrustStoreOptions(
+ new JksOptions().setPath("src/test/resources/client-truststore.jks").setPassword("password"))
+ .setVerifyHost(false));
+ Throwable exception = Assertions.assertThrows(CompletionException.class, () -> client.getAbs(url).sendAndAwait());
+ Assertions.assertTrue(exception.getCause() instanceof SSLHandshakeException);
+ }
+}