From d5fc95429199629b189b61a78f3d8da76f75ed86 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 3 Jul 2023 10:17:12 +0200 Subject: [PATCH] Enforce the configured TLS version Fix https://access.redhat.com/security/cve/cve-2023-2974 --- .../io/quarkus/grpc/runtime/GrpcSslUtils.java | 8 +- .../grpc/runtime/config/SslServerConfig.java | 11 ++- .../vertx/http/runtime/ServerSslConfig.java | 9 +- .../vertx/http/runtime/VertxHttpRecorder.java | 17 +--- integration-tests/vertx-http/pom.xml | 5 + .../io/quarkus/it/vertx/HelloResource.java | 12 +++ .../quarkus/it/vertx/ServerWithTLS13Only.java | 14 +++ .../TlsProtocolVersionDefaultTestCase.java | 96 ++++++++++++++++++ .../TlsProtocolVersionSelectionTestCase.java | 98 +++++++++++++++++++ 9 files changed, 246 insertions(+), 24 deletions(-) create mode 100644 integration-tests/vertx-http/src/main/java/io/quarkus/it/vertx/HelloResource.java create mode 100644 integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/ServerWithTLS13Only.java create mode 100644 integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionDefaultTestCase.java create mode 100644 integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionSelectionTestCase.java 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); + } +}