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 <simone.bordet@gmail.com>
(cherry picked from commit 693663a4ce)
This commit is contained in:
Simone Bordet 2021-08-17 18:35:04 +02:00
parent c150ce6859
commit 9f896c6390
2 changed files with 37 additions and 14 deletions

View File

@ -60,14 +60,16 @@ import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test; 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 org.junit.jupiter.api.condition.JRE;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -365,7 +367,7 @@ public class HttpClientTLSTest
// Excluded in JDK 11+ because resumed sessions cannot be compared // Excluded in JDK 11+ because resumed sessions cannot be compared
// using their session IDs even though they are resumed correctly. // 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 @Test
public void testHandshakeSucceededWithSessionResumption() throws Exception public void testHandshakeSucceededWithSessionResumption() throws Exception
{ {
@ -445,7 +447,7 @@ public class HttpClientTLSTest
// Excluded in JDK 11+ because resumed sessions cannot be compared // Excluded in JDK 11+ because resumed sessions cannot be compared
// using their session IDs even though they are resumed correctly. // 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 @Test
public void testClientRawCloseDoesNotInvalidateSession() throws Exception public void testClientRawCloseDoesNotInvalidateSession() throws Exception
{ {
@ -1013,7 +1015,6 @@ public class HttpClientTLSTest
// Force TLS-level hostName verification, as we want to receive the correspondent certificate. // Force TLS-level hostName verification, as we want to receive the correspondent certificate.
clientTLS.setEndpointIdentificationAlgorithm("HTTPS"); clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
startClient(clientTLS); startClient(clientTLS);
clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER); clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER);
// Send a request with SNI "localhost", we should get the certificate at alias=localhost. // Send a request with SNI "localhost", we should get the certificate at alias=localhost.
@ -1027,10 +1028,34 @@ public class HttpClientTLSTest
.scheme(HttpScheme.HTTPS.asString()) .scheme(HttpScheme.HTTPS.asString())
.send(); .send();
assertEquals(HttpStatus.OK_200, response2.getStatus()); assertEquals(HttpStatus.OK_200, response2.getStatus());
}
/* TODO Fix. See #6624 @Test
if (Net.isIpv6InterfaceAvailable()) @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) ->
{
// 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());
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. // Send a request with SNI "[::1]", we should get the certificate at alias=ip.
ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort()) ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString()) .scheme(HttpScheme.HTTPS.asString())
@ -1038,8 +1063,6 @@ public class HttpClientTLSTest
assertEquals(HttpStatus.OK_200, response3.getStatus()); assertEquals(HttpStatus.OK_200, response3.getStatus());
} }
*/
}
@Test @Test
public void testBytesInBytesOut() throws Exception public void testBytesInBytesOut() throws Exception

View File

@ -2178,9 +2178,9 @@ public abstract class SslContextFactory extends AbstractLifeCycle implements Dum
String host = sslEngine.getPeerHost(); String host = sslEngine.getPeerHost();
if (host != null) if (host != null)
{ {
// TODO Must handle : somehow as java17 SNIHostName never handles: See #6624
// Must use the byte[] constructor, because the character ':' is forbidden when // Must use the byte[] constructor, because the character ':' is forbidden when
// using the String constructor (but typically present in IPv6 addresses). // using the String constructor (but typically present in IPv6 addresses).
// Since Java 17, only letter|digit|hyphen characters are allowed, even by the byte[] constructor.
return List.of(new SNIHostName(host.getBytes(StandardCharsets.US_ASCII))); return List.of(new SNIHostName(host.getBytes(StandardCharsets.US_ASCII)));
} }
} }