From c58fd58e41f8b38208bd041853d64e7f639e1e0f Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 29 Oct 2019 19:14:07 -0500 Subject: [PATCH] Testing Large TLS Records for Jetty 9.2.x Signed-off-by: Joakim Erdfelt --- .../jetty/client/HttpClientTLSTest.java | 155 ++++++++++++++++++ .../eclipse/jetty/io/ssl/SslConnection.java | 15 +- 2 files changed, 167 insertions(+), 3 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 fe4de6bfe34..2aa21e9fba6 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 @@ -18,19 +18,45 @@ package org.eclipse.jetty.client; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP; +import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.ClientConnectionFactory; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.ssl.SslClientConnectionFactory; +import org.eclipse.jetty.io.ssl.SslConnection; +import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.After; import org.junit.Assert; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + public class HttpClientTLSTest { private Server server; @@ -180,4 +206,133 @@ public class HttpClientTLSTest // Expected. } } + + @Test + public void testTLSLargeFragments() throws Exception + { + final CountDownLatch serverLatch = new CountDownLatch(1); + SslContextFactory serverTLSFactory = createSslContextFactory(); + QueuedThreadPool serverThreads = new QueuedThreadPool(); + serverThreads.setName("server"); + server = new Server(serverThreads); + HttpConfiguration httpConfig = new HttpConfiguration(); + httpConfig.addCustomizer(new SecureRequestCustomizer()); + HttpConnectionFactory http = new HttpConnectionFactory(httpConfig); + SslConnectionFactory ssl = new SslConnectionFactory(serverTLSFactory, http.getProtocol()) + { + @Override + protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine) + { + return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) + { + @Override + protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException + { + int inputBytes = input.remaining(); + SSLEngineResult result = super.unwrap(sslEngine, input, output); + if (inputBytes == 5) + serverLatch.countDown(); + return result; + } + }; + } + }; + connector = new ServerConnector(server, 1, 1, ssl, http); + server.addConnector(connector); + server.setHandler(new EmptyServerHandler()); + server.start(); + + long idleTimeout = 2000; + + final CountDownLatch clientLatch = new CountDownLatch(1); + final SslContextFactory clientTLSFactory = createSslContextFactory(); + QueuedThreadPool clientThreads = new QueuedThreadPool(); + clientThreads.setName("client"); + client = new HttpClient( + new HttpClientTransportOverHTTP() + { + @Override + public HttpDestination newHttpDestination(Origin origin) + { + return new HttpDestinationOverHTTP(getHttpClient(), origin) + { + @Override + protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) + { + SslContextFactory sslContextFactory = getHttpClient().getSslContextFactory(); + ByteBufferPool bufferPool = getHttpClient().getByteBufferPool(); + Executor executor = getHttpClient().getExecutor(); + return new SslClientConnectionFactory(sslContextFactory, bufferPool, executor, connectionFactory) + { + @Override + protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine) + { + return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) + { + @Override + protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuffer output) throws SSLException + { + try + { + clientLatch.countDown(); + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + return super.wrap(sslEngine, input, output); + } + catch (InterruptedException x) + { + throw new SSLException(x); + } + } + }; + } + }; + } + }; + } + }, clientTLSFactory); + client.setIdleTimeout(idleTimeout); + client.setExecutor(clientThreads); + client.start(); + + String host = "localhost"; + int port = connector.getLocalPort(); + + final CountDownLatch responseLatch = new CountDownLatch(1); + client.newRequest(host, port) + .scheme(HttpScheme.HTTPS.asString()) + .send(new Response.CompleteListener() + { + public void onComplete(Result result) + { + assertTrue(result.isSucceeded()); + assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); + responseLatch.countDown(); + } + } + ); + // Wait for the TLS buffers to be acquired by the client, then the + // HTTP request will be paused waiting for the TLS buffer to be expanded. + assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + + // Send the large frame bytes that will enlarge the TLS buffers. + try (Socket socket = new Socket(host, port)) + { + OutputStream output = socket.getOutputStream(); + byte[] largeFrameBytes = new byte[5]; + largeFrameBytes[0] = 22; // Type = handshake + largeFrameBytes[1] = 3; // Major TLS version + largeFrameBytes[2] = 3; // Minor TLS version + // Frame length is 0x7FFF == 32767, i.e. a "large fragment". + // Maximum allowed by RFC 8446 is 16384, but SSLEngine supports up to 33093. + largeFrameBytes[3] = 0x7F; // Length hi byte + largeFrameBytes[4] = (byte)0xFF; // Length lo byte + output.write(largeFrameBytes); + output.flush(); + // Just close the connection now, the large frame + // length was enough to trigger the buffer expansion. + } + + // The HTTP request will resume and be forced to handle the TLS buffer expansion. + assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } } diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java index 9cb3cf94bda..819e41b4865 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java @@ -23,7 +23,6 @@ import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.Arrays; import java.util.concurrent.Executor; - import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; @@ -230,6 +229,16 @@ public class SslConnection extends AbstractConnection _decryptedEndPoint.getWriteFlusher().onFail(cause); } + protected SSLEngineResult wrap(SSLEngine sslEngine, ByteBuffer[] input, ByteBuffer output) throws SSLException + { + return sslEngine.wrap(input, output); + } + + protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException + { + return sslEngine.unwrap(input, output); + } + @Override public String toString() { @@ -525,7 +534,7 @@ public class SslConnection extends AbstractConnection SSLEngineResult unwrapResult; try { - unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in); + unwrapResult = unwrap(_sslEngine, _encryptedInput, app_in); } finally { @@ -757,7 +766,7 @@ public class SslConnection extends AbstractConnection SSLEngineResult wrapResult; try { - wrapResult=_sslEngine.wrap(appOuts, _encryptedOutput); + wrapResult = wrap(_sslEngine, appOuts, _encryptedOutput); } finally {