From cdd9ce5bcc0cbbc3cc23a34f3c980fb2dff17616 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 3 Jul 2023 10:02:03 +0200 Subject: [PATCH] Enforce the configured TLS version Fix https://access.redhat.com/security/cve/cve-2023-2974 --- .../io/quarkus/grpc/runtime/GrpcSslUtils.java | 7 +- .../grpc/runtime/GrpcTestPortUtils.java | 4 +- .../grpc/runtime/config/SslServerConfig.java | 14 ++- .../vertx/http/runtime/ServerSslConfig.java | 12 ++- .../options/HttpServerOptionsUtils.java | 13 +-- integration-tests/vertx-http/pom.xml | 5 ++ .../io/quarkus/it/vertx/HelloResource.java | 13 +++ .../quarkus/it/vertx/ServerWithTLS13Only.java | 14 +++ .../TlsProtocolVersionDefaultTestCase.java | 83 ++++++++++++++++++ .../TlsProtocolVersionSelectionTestCase.java | 85 +++++++++++++++++++ 10 files changed, 227 insertions(+), 23 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 5c418de5588ad..b8758ea18154f 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 @@ -117,12 +117,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(sslConfig.protocols); options.setClientAuth(sslConfig.clientAuth); return false; } diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcTestPortUtils.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcTestPortUtils.java index 56d5384b9dc3e..bb3358616df7c 100644 --- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcTestPortUtils.java +++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcTestPortUtils.java @@ -1,6 +1,6 @@ package io.quarkus.grpc.runtime; -import java.util.List; +import java.util.Set; import org.eclipse.microprofile.config.ConfigProvider; @@ -36,7 +36,7 @@ private static boolean isHttpsConfigured(SslServerConfig ssl) { || !isDefaultProtocols(ssl.protocols); } - private static boolean isDefaultProtocols(List protocols) { + private static boolean isDefaultProtocols(Set protocols) { return protocols.size() == 2 && protocols.contains("TLSv1.3") && protocols.contains("TLSv1.2"); } 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 c557ecbd43c05..72d0cf003ca96 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 @@ -3,6 +3,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Optional; +import java.util.Set; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -49,7 +50,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,11 +76,18 @@ 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") - public List protocols; + public Set protocols; /** * Configures the engine to require/request client authentication. 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..bcab41197df6f 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 @@ -2,6 +2,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @@ -24,11 +25,18 @@ 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") - public List protocols; + public Set protocols; /** * Enables Server Name Indication (SNI), an TLS extension allowing the server to use multiple certificates. diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java index 1b0870c71234c..ad284f75f93a9 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/options/HttpServerOptionsUtils.java @@ -119,11 +119,7 @@ public static HttpServerOptions createSslOptions(HttpBuildTimeConfig buildTimeCo serverOptions.addEnabledCipherSuite(cipher); } - for (String protocol : sslConfig.protocols) { - if (!protocol.isEmpty()) { - serverOptions.addEnabledSecureTransportProtocol(protocol); - } - } + serverOptions.setEnabledSecureTransportProtocols(sslConfig.protocols); serverOptions.setSsl(true); serverOptions.setSni(sslConfig.sni); int sslPort = httpConfiguration.determineSslPort(launchMode); @@ -224,11 +220,8 @@ public static HttpServerOptions createSslOptionsForManagementInterface(Managemen serverOptions.addEnabledCipherSuite(cipher); } - for (String protocol : sslConfig.protocols) { - if (!protocol.isEmpty()) { - serverOptions.addEnabledSecureTransportProtocol(protocol); - } - } + serverOptions.setEnabledSecureTransportProtocols(sslConfig.protocols); + serverOptions.setSsl(true); serverOptions.setSni(sslConfig.sni); int sslPort = httpConfiguration.determinePort(launchMode); diff --git a/integration-tests/vertx-http/pom.xml b/integration-tests/vertx-http/pom.xml index 8f466263f48cb..ba3cd9d408d75 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..e2c618bfe4818 --- /dev/null +++ b/integration-tests/vertx-http/src/main/java/io/quarkus/it/vertx/HelloResource.java @@ -0,0 +1,13 @@ +package io.quarkus.it.vertx; + +import jakarta.ws.rs.GET; +import jakarta.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..645369fad5a94 --- /dev/null +++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionDefaultTestCase.java @@ -0,0 +1,83 @@ +package io.quarkus.it.vertx; + +import java.util.Set; +import java.util.concurrent.CompletionException; + +import javax.net.ssl.SSLHandshakeException; + +import jakarta.inject.Inject; + +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 TLS 1.2 or 1.3, the server is exposing 1.3 - 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 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 testWithWebClientRequestingTls12() { + // The Web client is requesting TLS 1.2, the server is exposing 1.2 and 1.3 - 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 testWithWebClientRequestingTls11() { + // The Web client is requesting TLS 1.1, the server is exposing 1.2 and 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); + } +} 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..9fa967bade361 --- /dev/null +++ b/integration-tests/vertx-http/src/test/java/io/quarkus/it/vertx/TlsProtocolVersionSelectionTestCase.java @@ -0,0 +1,85 @@ +package io.quarkus.it.vertx; + +import java.util.Set; +import java.util.concurrent.CompletionException; + +import javax.net.ssl.SSLHandshakeException; + +import jakarta.inject.Inject; + +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 TLS 1.2 or 1.3, the server is exposing 1.3 - 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 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 testWithWebClientRequestingTls12() { + // The Web client is requesting TLS 1.2, the server is exposing 1.3 - 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); + } +}