Skip to content

Commit

Permalink
Testing Large TLS Records for Jetty 9.2.x
Browse files Browse the repository at this point in the history
Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
  • Loading branch information
joakime committed Oct 30, 2019
1 parent 45a5b9c commit c58fd58
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -180,4 +206,133 @@ public void testMismatchBetweenTLSProtocolAndTLSCiphersOnClient() throws Excepti
// 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));
}
}
15 changes: 12 additions & 3 deletions jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
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;
Expand Down Expand Up @@ -230,6 +229,16 @@ public void onFillInterestedFailed(Throwable cause)
_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()
{
Expand Down Expand Up @@ -525,7 +534,7 @@ else if (_decryptedInput == null)
SSLEngineResult unwrapResult;
try
{
unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
unwrapResult = unwrap(_sslEngine, _encryptedInput, app_in);
}
finally
{
Expand Down Expand Up @@ -757,7 +766,7 @@ public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
SSLEngineResult wrapResult;
try
{
wrapResult=_sslEngine.wrap(appOuts, _encryptedOutput);
wrapResult = wrap(_sslEngine, appOuts, _encryptedOutput);
}
finally
{
Expand Down

0 comments on commit c58fd58

Please sign in to comment.