Merged branch 'master' into 'jetty-9.1'.

This commit is contained in:
Simone Bordet 2013-10-16 16:30:04 +02:00
commit 114a95234b
3 changed files with 218 additions and 120 deletions

View File

@ -88,6 +88,7 @@ public class SslBytesServerTest extends SslBytesTest
private ExecutorService threadPool; private ExecutorService threadPool;
private Server server; private Server server;
private SslContextFactory sslContextFactory; private SslContextFactory sslContextFactory;
private int serverPort;
private SSLContext sslContext; private SSLContext sslContext;
private SimpleProxy proxy; private SimpleProxy proxy;
private Runnable idleHook; private Runnable idleHook;
@ -215,7 +216,7 @@ public class SslBytesServerTest extends SslBytesTest
} }
}); });
server.start(); server.start();
int serverPort = connector.getLocalPort(); serverPort = connector.getLocalPort();
sslContext = sslContextFactory.getSslContext(); sslContext = sslContextFactory.getSslContext();
@ -296,6 +297,88 @@ public class SslBytesServerTest extends SslBytesTest
closeClient(client); closeClient(client);
} }
@Test
public void testHandshakeWithResumedSessionThenClose() throws Exception
{
// First socket will establish the SSL session
SSLSocket client1 = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client1.startHandshake();
client1.close();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
int proxyPort = proxy.getPort();
proxy.stop();
proxy = new SimpleProxy(threadPool, proxyPort, "localhost", serverPort);
proxy.start();
logger.info("proxy:{} <==> server:{}", proxy.getPort(), serverPort);
final SSLSocket client2 = newClient(proxy);
Future<Object> handshake = threadPool.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
client2.startHandshake();
return null;
}
});
// Client Hello with SessionID
TLSRecord record = proxy.readFromClient();
Assert.assertNotNull(record);
proxy.flushToServer(record);
// Server Hello
record = proxy.readFromServer();
Assert.assertNotNull(record);
proxy.flushToClient(record);
// Change Cipher Spec
record = proxy.readFromServer();
Assert.assertNotNull(record);
proxy.flushToClient(record);
// Server Done
record = proxy.readFromServer();
Assert.assertNotNull(record);
proxy.flushToClient(record);
// Client Key Exchange
record = proxy.readFromClient();
Assert.assertNotNull(record);
// Client Done
TLSRecord doneRecord = proxy.readFromClient();
Assert.assertNotNull(doneRecord);
// Close
client2.close();
TLSRecord closeRecord = proxy.readFromClient();
Assert.assertNotNull(closeRecord);
Assert.assertEquals(TLSRecord.Type.ALERT, closeRecord.getType());
// Flush to server Client Key Exchange + Client Done + Close in one chunk
byte[] recordBytes = record.getBytes();
byte[] doneBytes = doneRecord.getBytes();
byte[] closeRecordBytes = closeRecord.getBytes();
byte[] chunk = new byte[recordBytes.length + doneBytes.length + closeRecordBytes.length];
System.arraycopy(recordBytes, 0, chunk, 0, recordBytes.length);
System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
proxy.flushToServer(0, chunk);
// Close the raw socket
proxy.flushToServer(null);
// Expect the server to send a FIN as well
record = proxy.readFromServer();
Assert.assertNull(record);
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
Assert.assertThat(sslFills.get(), Matchers.lessThan(20));
Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
Assert.assertThat(httpParses.get(), Matchers.lessThan(20));
}
@Test @Test
public void testHandshakeWithSplitBoundary() throws Exception public void testHandshakeWithSplitBoundary() throws Exception
{ {
@ -1849,6 +1932,11 @@ public class SslBytesServerTest extends SslBytesTest
} }
private SSLSocket newClient() throws IOException, InterruptedException private SSLSocket newClient() throws IOException, InterruptedException
{
return newClient(proxy);
}
private SSLSocket newClient(SimpleProxy proxy) throws IOException, InterruptedException
{ {
SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort()); SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", proxy.getPort());
client.setUseClientMode(true); client.setUseClientMode(true);

View File

@ -105,6 +105,7 @@ public abstract class SslBytesTest
{ {
private final CountDownLatch latch = new CountDownLatch(1); private final CountDownLatch latch = new CountDownLatch(1);
private final ExecutorService threadPool; private final ExecutorService threadPool;
private final int proxyPort;
private final String serverHost; private final String serverHost;
private final int serverPort; private final int serverPort;
private volatile ServerSocket serverSocket; private volatile ServerSocket serverSocket;
@ -112,15 +113,21 @@ public abstract class SslBytesTest
private volatile Socket client; private volatile Socket client;
public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort) public SimpleProxy(ExecutorService threadPool, String serverHost, int serverPort)
{
this(threadPool, 0, serverHost, serverPort);
}
public SimpleProxy(ExecutorService threadPool, int proxyPort, String serverHost, int serverPort)
{ {
this.threadPool = threadPool; this.threadPool = threadPool;
this.proxyPort = proxyPort;
this.serverHost = serverHost; this.serverHost = serverHost;
this.serverPort = serverPort; this.serverPort = serverPort;
} }
public void start() throws Exception public void start() throws Exception
{ {
serverSocket = new ServerSocket(0); serverSocket = new ServerSocket(proxyPort);
Thread acceptor = new Thread(this); Thread acceptor = new Thread(this);
acceptor.start(); acceptor.start();
server = new Socket(serverHost, serverPort); server = new Socket(serverHost, serverPort);

View File

@ -193,7 +193,7 @@ public class SslConnection extends AbstractConnection
// We have received a close handshake, close the end point to send FIN. // We have received a close handshake, close the end point to send FIN.
if (_decryptedEndPoint.isInputShutdown()) if (_decryptedEndPoint.isInputShutdown())
getEndPoint().close(); _decryptedEndPoint.close();
// wake up whoever is doing the fill or the flush so they can // wake up whoever is doing the fill or the flush so they can
// do all the filling, unwrapping, wrapping and flushing // do all the filling, unwrapping, wrapping and flushing
@ -508,142 +508,142 @@ public class SslConnection extends AbstractConnection
int net_filled = getEndPoint().fill(_encryptedInput); int net_filled = getEndPoint().fill(_encryptedInput);
if (DEBUG) if (DEBUG)
LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled); LOG.debug("{} filled {} encrypted bytes", SslConnection.this, net_filled);
if (net_filled > 0)
_underFlown = false;
// Let's try the SSL thang even if we have no net data because in that decryption: while (true)
// case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
BufferUtil.flipToFlush(app_in, pos);
if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
Status unwrapResultStatus = unwrapResult.getStatus();
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// and deal with the results
switch (unwrapResultStatus)
{ {
case BUFFER_OVERFLOW: // Let's unwrap even if we have no net data because in that
throw new IllegalStateException(); // case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
BufferUtil.flipToFlush(app_in, pos);
if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
case CLOSED: HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// Dang! we have to care about the handshake state specially for close HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
switch (handshakeStatus) Status unwrapResultStatus = unwrapResult.getStatus();
{
case NOT_HANDSHAKING:
// We were not handshaking, so just tell the app we are closed
return -1;
case NEED_TASK: _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
// run the task
_sslEngine.getDelegatedTask().run();
continue;
case NEED_WRAP: if (_underFlown)
// we need to send some handshake data (probably to send a close handshake). {
// but that will not enable any extra data to fill, so we just return -1 if (net_filled < 0)
// The wrapping can be done by any output drivers doing flushing or shutdown output.
return -1;
}
throw new IllegalStateException();
default:
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (DEBUG)
LOG.debug("{} handshake completed client-side", SslConnection.this);
}
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
closeInbound(); closeInbound();
return -1; if (net_filled <= 0)
} return net_filled;
}
if (unwrapResultStatus == Status.BUFFER_UNDERFLOW) switch (unwrapResultStatus)
_underFlown = true; {
case CLOSED:
// If bytes were produced, don't bother with the handshake status;
// pass the decrypted data to the application, which will perform
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{ {
if (app_in == buffer) switch (handshakeStatus)
return unwrapResult.bytesProduced(); {
return BufferUtil.flipPutFlip(_decryptedInput, buffer); case NOT_HANDSHAKING:
}
// Dang! we have to care about the handshake state
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// we just didn't read anything.
if (net_filled < 0)
{ {
closeInbound(); // We were not handshaking, so just tell the app we are closed
return -1; return -1;
} }
return 0; case NEED_TASK:
case NEED_TASK:
// run the task
_sslEngine.getDelegatedTask().run();
continue;
case NEED_WRAP:
// we need to send some handshake data
// if we are called from flush
if (buffer == __FLUSH_CALLED_FILL)
return 0; // let it do the wrapping
_fillRequiresFlushToProgress = true;
flush(__FILL_CALLED_FLUSH);
if (BufferUtil.isEmpty(_encryptedOutput))
{ {
// the flush completed so continue _sslEngine.getDelegatedTask().run();
_fillRequiresFlushToProgress = false;
continue; continue;
} }
return 0; case NEED_WRAP:
case NEED_UNWRAP:
// if we just filled some net data
if (net_filled < 0)
{ {
closeInbound(); // We need to send some handshake data (probably the close handshake).
// We return -1 so that the application can drive the close by flushing
// or shutting down the output.
return -1; return -1;
} }
else if (net_filled > 0) default:
{ {
// maybe we will fill some more on a retry throw new IllegalStateException();
}
}
}
case BUFFER_UNDERFLOW:
case OK:
{
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (DEBUG)
LOG.debug("{} {} handshake completed", SslConnection.this,
_sslEngine.getUseClientMode() ? "client-side" : "resumed session server-side");
}
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
closeInbound();
return -1;
}
// If bytes were produced, don't bother with the handshake status;
// pass the decrypted data to the application, which will perform
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{
if (app_in == buffer)
return unwrapResult.bytesProduced();
return BufferUtil.flipPutFlip(_decryptedInput, buffer);
}
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
{
if (_underFlown)
break decryption;
continue; continue;
} }
else case NEED_TASK:
{ {
if (_encryptedInput.hasRemaining()) _sslEngine.getDelegatedTask().run();
continue;
}
case NEED_WRAP:
{
// If we are called from flush()
// return to let it do the wrapping.
if (buffer == __FLUSH_CALLED_FILL)
return 0;
_fillRequiresFlushToProgress = true;
flush(__FILL_CALLED_FLUSH);
if (BufferUtil.isEmpty(_encryptedOutput))
{ {
// if there are more encrypted bytes, // The flush wrote all the encrypted bytes so continue to fill
// then we need to unwrap more, we don't _fillRequiresFlushToProgress = false;
// care if net_filled is zero
continue; continue;
} }
// we need to wait for more net data else
return 0; {
// The flush did not complete, return from fill()
// and let the write completion mechanism to kick in.
return 0;
}
} }
case NEED_UNWRAP:
case FINISHED: {
throw new IllegalStateException(); if (_underFlown)
break decryption;
continue;
}
default:
{
throw new IllegalStateException();
}
}
} }
default:
{
throw new IllegalStateException();
}
}
} }
} }
} }
@ -778,7 +778,7 @@ public class SslConnection extends AbstractConnection
{ {
_handshaken = true; _handshaken = true;
if (DEBUG) if (DEBUG)
LOG.debug("{} handshake completed server-side", SslConnection.this); LOG.debug("{} {} handshake completed", SslConnection.this, "server-side");
} }
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus(); HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
@ -824,7 +824,7 @@ public class SslConnection extends AbstractConnection
if (handshakeStatus == HandshakeStatus.NEED_WRAP) if (handshakeStatus == HandshakeStatus.NEED_WRAP)
continue; continue;
} }
return allConsumed&&BufferUtil.isEmpty(_encryptedOutput); return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case FINISHED: case FINISHED:
throw new IllegalStateException(); throw new IllegalStateException();
@ -860,17 +860,18 @@ public class SslConnection extends AbstractConnection
public void shutdownOutput() public void shutdownOutput()
{ {
boolean ishut = isInputShutdown(); boolean ishut = isInputShutdown();
boolean oshut = isOutputShutdown();
if (DEBUG) if (DEBUG)
LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, isOutputShutdown(), ishut); LOG.debug("{} shutdownOutput: oshut={}, ishut={}", SslConnection.this, oshut, ishut);
if (ishut) if (ishut)
{ {
// Aggressively close, since inbound close alert has already been processed // Aggressively close, since inbound close alert has already been processed
// and the TLS specification allows to close the connection directly, which // and the TLS specification allows to close the connection directly, which
// is what most other implementations expect: a FIN rather than a TLS close // is what most other implementations expect: a FIN rather than a TLS close
// reply. If a TLS close reply is sent, most implementation send a RST. // reply. If a TLS close reply is sent, most implementations send a RST.
getEndPoint().close(); getEndPoint().close();
} }
else else if (!oshut)
{ {
try try
{ {
@ -895,6 +896,8 @@ public class SslConnection extends AbstractConnection
@Override @Override
public void close() public void close()
{ {
// First send the TLS Close Alert, then the FIN
shutdownOutput();
getEndPoint().close(); getEndPoint().close();
} }