From 42f768c1c5e33b2443a6c227b3c9e2dc8e30e36a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 17 Aug 2021 18:35:04 +0200 Subject: [PATCH] Fixes #6624 - Non-domain SNI on java17 Java 17 only allows letter|digit|hyphen characters for SNI names. While we could bypass this restriction on the client, when the SNI bytes arrive to the server they will be verified and if not allowed the TLS handshake will fail. Signed-off-by: Simone Bordet (cherry picked from commit 693663a4ce3ed0f35cc7da66760e02c9e3bc2d97) --- .../jetty/client/HttpClientTLSTest.java | 49 ++++++++++++++----- .../jetty/util/ssl/SslContextFactory.java | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index 1efb74bb83e2..d7de9d72e18f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -60,14 +60,16 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import static org.hamcrest.MatcherAssert.assertThat; @@ -365,7 +367,7 @@ public void handshakeSucceeded(Event event) // Excluded in JDK 11+ because resumed sessions cannot be compared // using their session IDs even though they are resumed correctly. - @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10}) + @EnabledForJreRange(max = JRE.JAVA_10) @Test public void testHandshakeSucceededWithSessionResumption() throws Exception { @@ -445,7 +447,7 @@ public void handshakeSucceeded(Event event) // Excluded in JDK 11+ because resumed sessions cannot be compared // using their session IDs even though they are resumed correctly. - @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10}) + @EnabledForJreRange(max = JRE.JAVA_10) @Test public void testClientRawCloseDoesNotInvalidateSession() throws Exception { @@ -1013,7 +1015,6 @@ public void testForcedNonDomainSNI() throws Exception // Force TLS-level hostName verification, as we want to receive the correspondent certificate. clientTLS.setEndpointIdentificationAlgorithm("HTTPS"); startClient(clientTLS); - clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER); // Send a request with SNI "localhost", we should get the certificate at alias=localhost. @@ -1027,18 +1028,40 @@ public void testForcedNonDomainSNI() throws Exception .scheme(HttpScheme.HTTPS.asString()) .send(); assertEquals(HttpStatus.OK_200, response2.getStatus()); + } - /* TODO Fix. See #6624 - if (Net.isIpv6InterfaceAvailable()) + @Test + @EnabledForJreRange(max = JRE.JAVA_16, disabledReason = "Since Java 17, SNI host names can only have letter|digit|hyphen characters.") + public void testForcedNonDomainSNIWithIPv6() throws Exception + { + Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); + + SslContextFactory.Server serverTLS = new SslContextFactory.Server(); + serverTLS.setKeyStorePath("src/test/resources/keystore_sni_non_domain.p12"); + serverTLS.setKeyStorePassword("storepwd"); + serverTLS.setSNISelector((keyType, issuers, session, sniHost, certificates) -> { - // Send a request with SNI "[::1]", we should get the certificate at alias=ip. - ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort()) - .scheme(HttpScheme.HTTPS.asString()) - .send(); + // We have forced the client to send the non-domain SNI. + assertNotNull(sniHost); + return serverTLS.sniSelect(keyType, issuers, session, sniHost, certificates); + }); + startServer(serverTLS, new EmptyServerHandler()); - assertEquals(HttpStatus.OK_200, response3.getStatus()); - } - */ + SslContextFactory.Client clientTLS = new SslContextFactory.Client(); + // Trust any certificate received by the server. + clientTLS.setTrustStorePath("src/test/resources/keystore_sni_non_domain.p12"); + clientTLS.setTrustStorePassword("storepwd"); + // Force TLS-level hostName verification, as we want to receive the correspondent certificate. + clientTLS.setEndpointIdentificationAlgorithm("HTTPS"); + startClient(clientTLS); + clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER); + + // Send a request with SNI "[::1]", we should get the certificate at alias=ip. + ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort()) + .scheme(HttpScheme.HTTPS.asString()) + .send(); + + assertEquals(HttpStatus.OK_200, response3.getStatus()); } @Test diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java index 8132414e7780..c3470f4124ac 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java @@ -2178,9 +2178,9 @@ private static List getSniServerNames(SSLEngine sslEngine, List