diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java index 1dd89a95d3a6..d8d73255db95 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java @@ -14,10 +14,12 @@ package org.eclipse.jetty.client; import java.io.IOException; +import java.net.InetAddress; import java.net.URLDecoder; +import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.nio.channels.UnresolvedAddressException; import java.nio.charset.StandardCharsets; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -38,9 +40,11 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.toolchain.test.IO; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -52,6 +56,8 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest { + private static final Logger LOG = LoggerFactory.getLogger(HttpClientRedirectTest.class); + @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void test303(Scenario scenario) throws Exception @@ -287,24 +293,26 @@ protected void service(String target, Request jettyRequest, HttpServletRequest r @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) - @Disabled public void testRedirectFailed(Scenario scenario) throws Exception { - // TODO this test is failing with timout after an ISP upgrade?? DNS dependent? + // Skip this test if DNS Hijacking is detected + Assumptions.assumeFalse(detectDnsHijacking()); + start(scenario, new RedirectHandler()); - try - { - client.newRequest("localhost", connector.getLocalPort()) + ExecutionException e = assertThrows(ExecutionException.class, + () -> client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) .path("/303/doesNotExist/done") .timeout(5, TimeUnit.SECONDS) - .send(); - } - catch (ExecutionException x) - { - assertThat(x.getCause(), Matchers.instanceOf(UnresolvedAddressException.class)); - } + .send()); + + assertThat("Cause", e.getCause(), Matchers.anyOf( + // Exception seen on some updates of OpenJDK 8 + // Matchers.instanceOf(UnresolvedAddressException.class), + // Exception seen on OpenJDK 11+ + Matchers.instanceOf(UnknownHostException.class)) + ); } @ParameterizedTest @@ -711,4 +719,48 @@ protected void service(String target, Request jettyRequest, HttpServletRequest r } } } + + public static boolean detectDnsHijacking() + { + String host1 = randomHostname(); + String host2 = randomHostname(); + String addr1 = getInetHostAddress(host1); + String addr2 = getInetHostAddress(host2); + + boolean ret = (addr1.equals(addr2)); + + if (ret) + { + LOG.warn("DNS Hijacking detected (these should not return the same host address): host1={} ({}), host2={} ({})", + host1, addr1, + host2, addr2); + } + + return ret; + } + + private static String getInetHostAddress(String hostname) + { + try + { + InetAddress addr = InetAddress.getByName(hostname); + return addr.getHostAddress(); + } + catch (Throwable t) + { + return ""; + } + } + + private static String randomHostname() + { + String digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + Random random = new Random(); + char[] host = new char[7 + random.nextInt(8)]; + for (int i = 0; i < host.length; ++i) + { + host[i] = digits.charAt(random.nextInt(digits.length())); + } + return new String(host) + ".tld."; + } } 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 7734639c3ba1..ca01c19287fb 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 @@ -23,6 +23,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -66,7 +67,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnJre; import org.junit.jupiter.api.condition.JRE; @@ -265,10 +265,20 @@ public void handshakeFailed(Event event, Throwable failure) assertTrue(clientLatch.await(1, TimeUnit.SECONDS)); } - // In JDK 11+, a mismatch on the client does not generate any bytes towards - // the server, while in previous JDKs the client sends to the server the close_notify. - // @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10}) - @Disabled("No longer viable, TLS protocol behavior changed in 8u272") + /** + * This tests the behavior of the Client side when you have an intentional + * mismatch of the TLS Protocol and TLS Ciphers on the client and attempt to + * initiate a connection. + *

+ * In older versions of Java 8 (pre 8u272) the JDK client side logic + * would generate bytes and send it to the server along with a close_notify + *

+ *

+ * Starting in Java 8 (8u272) and Java 11.0.0, the client logic will not + * send any bytes to the server. + *

+ */ + @Test public void testMismatchBetweenTLSProtocolAndTLSCiphersOnClient() throws Exception { SslContextFactory.Server serverTLSFactory = createServerSslContextFactory(); @@ -285,11 +295,18 @@ public void handshakeFailed(Event event, Throwable failure) }); SslContextFactory.Client clientTLSFactory = createClientSslContextFactory(); - // TLS 1.1 protocol, but only TLS 1.2 ciphers. - clientTLSFactory.setIncludeProtocols("TLSv1.1"); - clientTLSFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + // Use TLSv1.2 (as TLSv1.1 and older are now disabled in Java 8u292+ and Java 11+) + clientTLSFactory.setIncludeProtocols("TLSv1.2"); + // Use only a cipher suite that exists for TLSv1.3 (this is the mismatch) + clientTLSFactory.setIncludeCipherSuites("TLS_AES_256_GCM_SHA384"); startClient(clientTLSFactory); + // If this JVM has "TLSv1.3" present, then it's assumed that it also has the new logic + // to not send bytes to the server when a protocol and cipher suite mismatch occurs + // on the client side configuration. + List supportedClientProtocols = Arrays.asList(clientTLSFactory.getSslContext().getSupportedSSLParameters().getProtocols()); + boolean expectServerFailure = !(supportedClientProtocols.contains("TLSv1.3")); + CountDownLatch clientLatch = new CountDownLatch(1); client.addBean(new SslHandshakeListener() { @@ -306,7 +323,8 @@ public void handshakeFailed(Event event, Throwable failure) .timeout(5, TimeUnit.SECONDS) .send()); - assertTrue(serverLatch.await(1, TimeUnit.SECONDS)); + if (expectServerFailure) + assertTrue(serverLatch.await(1, TimeUnit.SECONDS)); assertTrue(clientLatch.await(1, TimeUnit.SECONDS)); } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java deleted file mode 100644 index 772af1a73658..000000000000 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesClientTest.java +++ /dev/null @@ -1,358 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.client.ssl; - -import java.io.BufferedReader; -import java.io.File; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLSocket; - -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; -import org.eclipse.jetty.client.util.FutureResponseListener; -import org.eclipse.jetty.http.HttpScheme; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.ClientConnector; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -// This whole test is very specific to how TLS < 1.3 works. -// Starting in Java 11, TLS/1.3 is now enabled by default. -@Disabled("Since 8u272 this is no longer valid") -public class SslBytesClientTest extends SslBytesTest -{ - private ExecutorService threadPool; - private HttpClient client; - private SslContextFactory.Client sslContextFactory; - private SSLServerSocket acceptor; - private SimpleProxy proxy; - - @BeforeEach - public void init() throws Exception - { - threadPool = Executors.newCachedThreadPool(); - - ClientConnector clientConnector = new ClientConnector(); - clientConnector.setSelectors(1); - sslContextFactory = new SslContextFactory.Client(true); - clientConnector.setSslContextFactory(sslContextFactory); - client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)); - client.setMaxConnectionsPerDestination(1); - File keyStore = MavenTestingUtils.getTestResourceFile("keystore.p12"); - sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath()); - sslContextFactory.setKeyStorePassword("storepwd"); - client.start(); - - SSLContext sslContext = this.sslContextFactory.getSslContext(); - acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(0); - - int serverPort = acceptor.getLocalPort(); - - proxy = new SimpleProxy(threadPool, "localhost", serverPort); - proxy.start(); - logger.info(":{} <==> :{}", proxy.getPort(), serverPort); - } - - @AfterEach - public void destroy() throws Exception - { - if (acceptor != null) - acceptor.close(); - if (proxy != null) - proxy.stop(); - if (client != null) - client.stop(); - if (threadPool != null) - threadPool.shutdownNow(); - } - - @Test - public void testHandshake() throws Exception - { - Request request = client.newRequest("localhost", proxy.getPort()); - FutureResponseListener listener = new FutureResponseListener(request); - request.scheme(HttpScheme.HTTPS.asString()).send(listener); - - assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS)); - - try (SSLSocket server = (SSLSocket)acceptor.accept()) - { - server.setUseClientMode(false); - - Future handshake = threadPool.submit(() -> - { - server.startHandshake(); - return null; - }); - - // Client Hello - TLSRecord record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - // Server Hello + Certificate + Server Done - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Client Key Exchange - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - // Change Cipher Spec - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToServer(record); - - // Client Done - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - // Change Cipher Spec - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToClient(record); - - // Server Done - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - assertNull(handshake.get(5, TimeUnit.SECONDS)); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - // Read request - BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream(), StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertTrue(line.startsWith("GET")); - while (line.length() > 0) - { - line = reader.readLine(); - } - - // Write response - OutputStream output = server.getOutputStream(); - output.write(("HTTP/1.1 200 OK\r\n" + - "Content-Length: 0\r\n" + - "\r\n").getBytes(StandardCharsets.UTF_8)); - output.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - ContentResponse response = listener.get(5, TimeUnit.SECONDS); - assertEquals(HttpStatus.OK_200, response.getStatus()); - } - } - - @Test - public void testServerRenegotiation() throws Exception - { - Request request = client.newRequest("localhost", proxy.getPort()); - FutureResponseListener listener = new FutureResponseListener(request); - request.scheme(HttpScheme.HTTPS.asString()).send(listener); - - assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS)); - - try (SSLSocket server = (SSLSocket)acceptor.accept()) - { - server.setUseClientMode(false); - - Future handshake = threadPool.submit(() -> - { - server.startHandshake(); - return null; - }); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - assertNull(handshake.get(5, TimeUnit.SECONDS)); - - // Read request - InputStream serverInput = server.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertTrue(line.startsWith("GET")); - while (line.length() > 0) - { - line = reader.readLine(); - } - - OutputStream serverOutput = server.getOutputStream(); - byte[] data1 = new byte[1024]; - Arrays.fill(data1, (byte)'X'); - String content1 = new String(data1, StandardCharsets.UTF_8); - byte[] data2 = new byte[1024]; - Arrays.fill(data2, (byte)'Y'); - final String content2 = new String(data2, StandardCharsets.UTF_8); - // Write first part of the response - serverOutput.write(("HTTP/1.1 200 OK\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + (content1.length() + content2.length()) + "\r\n" + - "\r\n" + - content1).getBytes(StandardCharsets.UTF_8)); - serverOutput.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Renegotiate - Future renegotiation = threadPool.submit(() -> - { - server.startHandshake(); - return null; - }); - - // Renegotiation Handshake - TLSRecord record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Handshake - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - // Trigger a read to have the server write the final renegotiation steps - server.setSoTimeout(100); - assertThrows(SocketTimeoutException.class, () -> serverInput.read()); - - // Renegotiation Handshake - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Change Cipher - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Handshake - record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Renegotiation Change Cipher - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType()); - proxy.flushToServer(record); - - // Renegotiation Handshake - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToServer(record); - - assertNull(renegotiation.get(5, TimeUnit.SECONDS)); - - // Complete the response - automaticProxyFlow = proxy.startAutomaticFlow(); - serverOutput.write(data2); - serverOutput.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - ContentResponse response = listener.get(5, TimeUnit.SECONDS); - assertEquals(HttpStatus.OK_200, response.getStatus()); - assertEquals(data1.length + data2.length, response.getContent().length); - } - } - - @Test - public void testServerRenegotiationWhenRenegotiationIsForbidden() throws Exception - { - sslContextFactory.setRenegotiationAllowed(false); - - Request request = client.newRequest("localhost", proxy.getPort()); - FutureResponseListener listener = new FutureResponseListener(request); - request.scheme(HttpScheme.HTTPS.asString()).send(listener); - - assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS)); - - try (SSLSocket server = (SSLSocket)acceptor.accept()) - { - server.setUseClientMode(false); - - Future handshake = threadPool.submit(() -> - { - server.startHandshake(); - return null; - }); - - SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow(); - assertNull(handshake.get(5, TimeUnit.SECONDS)); - - // Read request - InputStream serverInput = server.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, StandardCharsets.UTF_8)); - String line = reader.readLine(); - assertTrue(line.startsWith("GET")); - while (line.length() > 0) - { - line = reader.readLine(); - } - - OutputStream serverOutput = server.getOutputStream(); - byte[] data1 = new byte[1024]; - Arrays.fill(data1, (byte)'X'); - String content1 = new String(data1, StandardCharsets.UTF_8); - byte[] data2 = new byte[1024]; - Arrays.fill(data2, (byte)'Y'); - final String content2 = new String(data2, StandardCharsets.UTF_8); - // Write first part of the response - serverOutput.write(("HTTP/1.1 200 OK\r\n" + - "Content-Type: text/plain\r\n" + - "Content-Length: " + (content1.length() + content2.length()) + "\r\n" + - "\r\n" + - content1).getBytes(StandardCharsets.UTF_8)); - serverOutput.flush(); - assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS)); - - // Renegotiate - threadPool.submit(() -> - { - server.startHandshake(); - return null; - }); - - // Renegotiation Handshake - TLSRecord record = proxy.readFromServer(); - assertEquals(TLSRecord.Type.HANDSHAKE, record.getType()); - proxy.flushToClient(record); - - // Client sends close alert. - record = proxy.readFromClient(); - assertEquals(TLSRecord.Type.ALERT, record.getType()); - record = proxy.readFromClient(); - assertNull(record); - } - } -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java similarity index 95% rename from jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java rename to jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java index 1457bd24e65a..4a8d0af7a9b9 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/ServerConnectorSslServerTest.java @@ -46,8 +46,8 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.slf4j.Logger; @@ -62,14 +62,14 @@ import static org.junit.jupiter.api.condition.OS.WINDOWS; /** - * HttpServer Tester. + * HttpServer Tester for SSL based ServerConnector */ -public class SelectChannelServerSslTest extends HttpServerTestBase +public class ServerConnectorSslServerTest extends HttpServerTestBase { - private static final Logger LOG = LoggerFactory.getLogger(SelectChannelServerSslTest.class); + private static final Logger LOG = LoggerFactory.getLogger(ServerConnectorSslServerTest.class); private SSLContext _sslContext; - public SelectChannelServerSslTest() + public ServerConnectorSslServerTest() { _scheme = "https"; } @@ -227,16 +227,15 @@ public void testRequest2FixedFragments() throws Exception @Override @Test - @Disabled("Override and ignore this test as SSLSocket.shutdownOutput() is not supported, " + - "but shutdownOutput() is needed by the test.") public void testInterruptedRequest() { + Assumptions.assumeFalse(_serverURI.getScheme().equals("https"), "SSLSocket.shutdownOutput() is not supported, but shutdownOutput() is needed by the test"); } @Override - @Disabled public void testAvailable() throws Exception { + Assumptions.assumeFalse(_serverURI.getScheme().equals("https"), "SSLSocket available() is not supported"); } @Test