404610 - Reintroduce ability to disallow TLS renegotiation.

This commit is contained in:
Simone Bordet 2013-04-04 15:31:09 +02:00
parent fc31a16c23
commit 72219d016b
12 changed files with 569 additions and 35 deletions

View File

@ -966,6 +966,7 @@ public class HttpClient extends ContainerLifeCycle
engine.setUseClientMode(true); engine.setUseClientMode(true);
SslConnection sslConnection = newSslConnection(HttpClient.this, endPoint, engine); SslConnection sslConnection = newSslConnection(HttpClient.this, endPoint, engine);
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint(); EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
HttpConnection connection = newHttpConnection(HttpClient.this, appEndPoint, destination); HttpConnection connection = newHttpConnection(HttpClient.this, appEndPoint, destination);

View File

@ -0,0 +1,369 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
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.util.Arrays;
import java.util.concurrent.Callable;
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.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class SslBytesClientTest extends SslBytesTest
{
private ExecutorService threadPool;
private HttpClient client;
private SslContextFactory sslContextFactory;
private SSLServerSocket acceptor;
private SimpleProxy proxy;
@Before
public void init() throws Exception
{
threadPool = Executors.newCachedThreadPool();
client = new HttpClient(new SslContextFactory(true));
client.setMaxConnectionsPerDestination(1);
File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
sslContextFactory = client.getSslContextFactory();
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd");
client.start();
SSLContext sslContext = sslContextFactory.getSslContext();
acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(43191);
int serverPort = acceptor.getLocalPort();
proxy = new SimpleProxy(threadPool, "localhost", serverPort);
proxy.start();
logger.info(":{} <==> :{}", proxy.getPort(), serverPort);
}
@After
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);
Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
final SSLSocket server = (SSLSocket)acceptor.accept();
server.setUseClientMode(false);
Future<Object> handshake = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
server.startHandshake();
return null;
}
});
// Client Hello
TLSRecord record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToServer(record);
// Server Hello + Certificate + Server Done
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToClient(record);
// Client Key Exchange
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToServer(record);
// Change Cipher Spec
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
proxy.flushToServer(record);
// Client Done
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToServer(record);
// Change Cipher Spec
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
proxy.flushToClient(record);
// Server Done
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToClient(record);
Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
// Read request
BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream(), "UTF-8"));
String line = reader.readLine();
Assert.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("UTF-8"));
output.flush();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
server.close();
}
@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);
Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
final SSLSocket server = (SSLSocket)acceptor.accept();
server.setUseClientMode(false);
Future<Object> handshake = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
server.startHandshake();
return null;
}
});
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
// Read request
InputStream serverInput = server.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, "UTF-8"));
String line = reader.readLine();
Assert.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, "UTF-8");
byte[] data2 = new byte[1024];
Arrays.fill(data2, (byte)'Y');
final String content2 = new String(data2, "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("UTF-8"));
serverOutput.flush();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
// Renegotiate
Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
server.startHandshake();
return null;
}
});
// Renegotiation Handshake
TLSRecord record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToClient(record);
// Renegotiation Handshake
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToServer(record);
// Trigger a read to have the server write the final renegotiation steps
server.setSoTimeout(100);
try
{
serverInput.read();
Assert.fail();
}
catch (SocketTimeoutException x)
{
// Expected
}
// Renegotiation Handshake
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToClient(record);
// Renegotiation Change Cipher
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
proxy.flushToClient(record);
// Renegotiation Handshake
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToClient(record);
// Renegotiation Change Cipher
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
proxy.flushToServer(record);
// Renegotiation Handshake
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToServer(record);
Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
// Complete the response
automaticProxyFlow = proxy.startAutomaticFlow();
serverOutput.write(data2);
serverOutput.flush();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertEquals(data1.length + data2.length, response.getContent().length);
server.close();
}
@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);
Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
final SSLSocket server = (SSLSocket)acceptor.accept();
server.setUseClientMode(false);
Future<Object> handshake = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
server.startHandshake();
return null;
}
});
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
// Read request
InputStream serverInput = server.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, "UTF-8"));
String line = reader.readLine();
Assert.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, "UTF-8");
byte[] data2 = new byte[1024];
Arrays.fill(data2, (byte)'Y');
final String content2 = new String(data2, "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("UTF-8"));
serverOutput.flush();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
// Renegotiate
threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
server.startHandshake();
return null;
}
});
// Renegotiation Handshake
TLSRecord record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToClient(record);
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
proxy.flushToServer(record);
record = proxy.readFromClient();
Assert.assertNull(record);
proxy.flushToServer(record);
server.close();
}
}

View File

@ -16,7 +16,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.server.ssl; package org.eclipse.jetty.client.ssl;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.EOFException; import java.io.EOFException;
@ -84,7 +84,7 @@ public class SslBytesServerTest extends SslBytesTest
private final int idleTimeout = 2000; private final int idleTimeout = 2000;
private ExecutorService threadPool; private ExecutorService threadPool;
private Server server; private Server server;
private int serverPort; private SslContextFactory sslContextFactory;
private SSLContext sslContext; private SSLContext sslContext;
private SimpleProxy proxy; private SimpleProxy proxy;
@ -94,11 +94,10 @@ public class SslBytesServerTest extends SslBytesTest
threadPool = Executors.newCachedThreadPool(); threadPool = Executors.newCachedThreadPool();
server = new Server(); server = new Server();
File keyStore = MavenTestingUtils.getTestResourceFile("keystore"); File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath()); sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
HttpConnectionFactory httpFactory = new HttpConnectionFactory() HttpConnectionFactory httpFactory = new HttpConnectionFactory()
{ {
@ -204,7 +203,7 @@ public class SslBytesServerTest extends SslBytesTest
} }
}); });
server.start(); server.start();
serverPort = connector.getLocalPort(); int serverPort = connector.getLocalPort();
sslContext = sslContextFactory.getSslContext(); sslContext = sslContextFactory.getSslContext();
@ -1277,6 +1276,98 @@ public class SslBytesServerTest extends SslBytesTest
closeClient(client); closeClient(client);
} }
@Test
public void testRequestWithContentWithRenegotiationInMiddleOfContentWhenRenegotiationIsForbidden() throws Exception
{
assumeJavaVersionSupportsTLSRenegotiations();
sslContextFactory.setRenegotiationAllowed(false);
final SSLSocket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client.startHandshake();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
byte[] data1 = new byte[1024];
Arrays.fill(data1, (byte)'X');
String content1 = new String(data1, "UTF-8");
byte[] data2 = new byte[1024];
Arrays.fill(data2, (byte)'Y');
final String content2 = new String(data2, "UTF-8");
// Write only part of the body
automaticProxyFlow = proxy.startAutomaticFlow();
clientOutput.write(("" +
"POST / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Type: text/plain\r\n" +
"Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
"\r\n" +
content1).getBytes("UTF-8"));
clientOutput.flush();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
// Renegotiate
threadPool.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
client.startHandshake();
return null;
}
});
// Renegotiation Handshake
TLSRecord record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
proxy.flushToServer(record);
// Renegotiation now allowed, server has closed
record = proxy.readFromServer();
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
proxy.flushToClient(record);
record = proxy.readFromServer();
Assert.assertNull(record);
// Write the rest of the request
threadPool.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
clientOutput.write(content2.getBytes("UTF-8"));
clientOutput.flush();
return null;
}
});
// Trying to write more application data results in an exception since the server closed
record = proxy.readFromClient();
proxy.flushToServer(record);
try
{
record = proxy.readFromClient();
Assert.assertNotNull(record);
proxy.flushToServer(record);
Assert.fail();
}
catch (IOException expected)
{
}
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
client.close();
}
@Test @Test
public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception
{ {

View File

@ -16,7 +16,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.server.ssl; package org.eclipse.jetty.client.ssl;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
@ -115,8 +115,8 @@ public abstract class SslBytesTest
public void start() throws Exception public void start() throws Exception
{ {
// serverSocket = new ServerSocket(5871); serverSocket = new ServerSocket(47009);
serverSocket = new ServerSocket(0); // serverSocket = new ServerSocket(0);
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

@ -99,6 +99,7 @@ public class SslConnection extends AbstractConnection
_decryptedEndPoint.getWriteFlusher().completeWrite(); _decryptedEndPoint.getWriteFlusher().completeWrite();
} }
}; };
private boolean _renegotiationAllowed;
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine) public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
{ {
@ -137,6 +138,16 @@ public class SslConnection extends AbstractConnection
return _decryptedEndPoint; return _decryptedEndPoint;
} }
public boolean isRenegotiationAllowed()
{
return _renegotiationAllowed;
}
public void setRenegotiationAllowed(boolean renegotiationAllowed)
{
this._renegotiationAllowed = renegotiationAllowed;
}
@Override @Override
public void onOpen() public void onOpen()
{ {
@ -242,6 +253,7 @@ public class SslConnection extends AbstractConnection
private boolean _fillRequiresFlushToProgress; private boolean _fillRequiresFlushToProgress;
private boolean _flushRequiresFillToProgress; private boolean _flushRequiresFillToProgress;
private boolean _cannotAcceptMoreAppDataToFlush; private boolean _cannotAcceptMoreAppDataToFlush;
private boolean _handshaken;
private boolean _underFlown; private boolean _underFlown;
private final Callback _writeCallback = new Callback() private final Callback _writeCallback = new Callback()
@ -493,15 +505,19 @@ public class SslConnection extends AbstractConnection
if (DEBUG) if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult); LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
Status unwrapResultStatus = unwrapResult.getStatus();
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// and deal with the results // and deal with the results
switch (unwrapResult.getStatus()) switch (unwrapResultStatus)
{ {
case BUFFER_OVERFLOW: case BUFFER_OVERFLOW:
throw new IllegalStateException(); throw new IllegalStateException();
case CLOSED: case CLOSED:
// Dang! we have to care about the handshake state specially for close // Dang! we have to care about the handshake state specially for close
switch (_sslEngine.getHandshakeStatus()) switch (handshakeStatus)
{ {
case NOT_HANDSHAKING: case NOT_HANDSHAKING:
// We were not handshaking, so just tell the app we are closed // We were not handshaking, so just tell the app we are closed
@ -521,10 +537,28 @@ public class SslConnection extends AbstractConnection
throw new IllegalStateException(); throw new IllegalStateException();
default: default:
if (unwrapResult.getStatus()==Status.BUFFER_UNDERFLOW) if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
_underFlown=true; {
_handshaken = true;
if (DEBUG)
LOG.debug("{} handshake completed client-side", SslConnection.this);
}
// if we produced bytes, we don't care about the handshake state for now and it can be dealt with on another call to fill or flush // Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
closeInbound();
return -1;
}
if (unwrapResultStatus == Status.BUFFER_UNDERFLOW)
_underFlown = true;
// 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 (unwrapResult.bytesProduced() > 0)
{ {
if (app_in == buffer) if (app_in == buffer)
@ -533,7 +567,7 @@ public class SslConnection extends AbstractConnection
} }
// Dang! we have to care about the handshake state // Dang! we have to care about the handshake state
switch (_sslEngine.getHandshakeStatus()) switch (handshakeStatus)
{ {
case NOT_HANDSHAKING: case NOT_HANDSHAKING:
// we just didn't read anything. // we just didn't read anything.
@ -553,7 +587,7 @@ public class SslConnection extends AbstractConnection
// we need to send some handshake data // we need to send some handshake data
// if we are called from flush // if we are called from flush
if (buffer==__FLUSH_CALLED_FILL) if (buffer == __FLUSH_CALLED_FILL)
return 0; // let it do the wrapping return 0; // let it do the wrapping
_fillRequiresFlushToProgress = true; _fillRequiresFlushToProgress = true;
@ -661,7 +695,6 @@ public class SslConnection extends AbstractConnection
while (true) while (true)
{ {
// do the funky SSL thang!
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer // We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
BufferUtil.compact(_encryptedOutput); BufferUtil.compact(_encryptedOutput);
int pos = BufferUtil.flipToFill(_encryptedOutput); int pos = BufferUtil.flipToFill(_encryptedOutput);
@ -672,18 +705,20 @@ public class SslConnection extends AbstractConnection
if (wrapResult.bytesConsumed()>0) if (wrapResult.bytesConsumed()>0)
consumed+=wrapResult.bytesConsumed(); consumed+=wrapResult.bytesConsumed();
boolean all_consumed=true; boolean allConsumed=true;
// clear empty buffers to prevent position creeping up the buffer // clear empty buffers to prevent position creeping up the buffer
for (ByteBuffer b : appOuts) for (ByteBuffer b : appOuts)
{ {
if (BufferUtil.isEmpty(b)) if (BufferUtil.isEmpty(b))
BufferUtil.clear(b); BufferUtil.clear(b);
else else
all_consumed=false; allConsumed=false;
} }
Status wrapResultStatus = wrapResult.getStatus();
// and deal with the results returned from the sslEngineWrap // and deal with the results returned from the sslEngineWrap
switch (wrapResult.getStatus()) switch (wrapResultStatus)
{ {
case CLOSED: case CLOSED:
// The SSL engine has close, but there may be close handshake that needs to be written // The SSL engine has close, but there may be close handshake that needs to be written
@ -692,32 +727,52 @@ public class SslConnection extends AbstractConnection
_cannotAcceptMoreAppDataToFlush = true; _cannotAcceptMoreAppDataToFlush = true;
getEndPoint().flush(_encryptedOutput); getEndPoint().flush(_encryptedOutput);
// If we failed to flush the close handshake then we will just pretend that // If we failed to flush the close handshake then we will just pretend that
// the write has progressed normally and let a subsequent call to flush (or WriteFlusher#onIncompleteFlushed) // the write has progressed normally and let a subsequent call to flush
// to finish writing the close handshake. The caller will find out about the close on a subsequent flush or fill. // (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
// The caller will find out about the close on a subsequent flush or fill.
if (BufferUtil.hasContent(_encryptedOutput)) if (BufferUtil.hasContent(_encryptedOutput))
return false; return false;
} }
// otherwise we have written, and the caller will close the underlying connection // otherwise we have written, and the caller will close the underlying connection
return all_consumed; return allConsumed;
case BUFFER_UNDERFLOW: case BUFFER_UNDERFLOW:
throw new IllegalStateException(); throw new IllegalStateException();
default: default:
if (DEBUG) if (DEBUG)
LOG.debug("{} {} {}", this, wrapResult.getStatus(), BufferUtil.toDetailString(_encryptedOutput)); LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (DEBUG)
LOG.debug("{} handshake completed server-side", SslConnection.this);
}
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
shutdownOutput();
BufferUtil.clear(_encryptedOutput);
return allConsumed;
}
// if we have net bytes, let's try to flush them // if we have net bytes, let's try to flush them
if (BufferUtil.hasContent(_encryptedOutput)) if (BufferUtil.hasContent(_encryptedOutput))
getEndPoint().flush(_encryptedOutput); getEndPoint().flush(_encryptedOutput);
// But we also might have more to do for the handshaking state. // But we also might have more to do for the handshaking state.
switch (_sslEngine.getHandshakeStatus()) switch (handshakeStatus)
{ {
case NOT_HANDSHAKING: case NOT_HANDSHAKING:
// Return with the number of bytes consumed (which may be 0) // Return with the number of bytes consumed (which may be 0)
return all_consumed&&BufferUtil.isEmpty(_encryptedOutput); return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case NEED_TASK: case NEED_TASK:
// run the task and continue // run the task and continue
@ -737,14 +792,13 @@ public class SslConnection extends AbstractConnection
_flushRequiresFillToProgress = true; _flushRequiresFillToProgress = true;
fill(__FLUSH_CALLED_FILL); fill(__FLUSH_CALLED_FILL);
// Check if after the fill() we need to wrap again // Check if after the fill() we need to wrap again
if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) if (handshakeStatus == HandshakeStatus.NEED_WRAP)
continue; continue;
} }
return all_consumed&&BufferUtil.isEmpty(_encryptedOutput); return allConsumed&&BufferUtil.isEmpty(_encryptedOutput);
case FINISHED: case FINISHED:
throw new IllegalStateException(); throw new IllegalStateException();
} }
} }
} }

View File

@ -74,10 +74,9 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
SSLEngine engine = __sslCtxFactory.newSSLEngine(); SSLEngine engine = __sslCtxFactory.newSSLEngine();
engine.setUseClientMode(false); engine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine); SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine);
sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
Connection appConnection = super.newConnection(channel,sslConnection.getDecryptedEndPoint()); Connection appConnection = super.newConnection(channel,sslConnection.getDecryptedEndPoint());
sslConnection.getDecryptedEndPoint().setConnection(appConnection); sslConnection.getDecryptedEndPoint().setConnection(appConnection);
return sslConnection; return sslConnection;
} }

View File

@ -30,7 +30,6 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
@ -80,10 +79,9 @@ public class SslConnectionTest
SSLEngine engine = __sslCtxFactory.newSSLEngine(); SSLEngine engine = __sslCtxFactory.newSSLEngine();
engine.setUseClientMode(false); engine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(__byteBufferPool, getExecutor(), endpoint, engine); SslConnection sslConnection = new SslConnection(__byteBufferPool, getExecutor(), endpoint, engine);
sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
Connection appConnection = new TestConnection(sslConnection.getDecryptedEndPoint()); Connection appConnection = new TestConnection(sslConnection.getDecryptedEndPoint());
sslConnection.getDecryptedEndPoint().setConnection(appConnection); sslConnection.getDecryptedEndPoint().setConnection(appConnection);
return sslConnection; return sslConnection;
} }

View File

@ -78,6 +78,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
engine.setUseClientMode(false); engine.setUseClientMode(false);
SslConnection sslConnection = newSslConnection(connector, endPoint, engine); SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
configure(sslConnection, connector, endPoint); configure(sslConnection, connector, endPoint);
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol); ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);

View File

@ -307,6 +307,7 @@ public class SPDYClient
{ {
final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel); final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine); SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint(); DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client); NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client);
sslEndPoint.setConnection(connection); sslEndPoint.setConnection(connection);

View File

@ -199,8 +199,12 @@ public class SslContextFactory extends AbstractLifeCycle
/** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */ /** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */
private String _endpointIdentificationAlgorithm = null; private String _endpointIdentificationAlgorithm = null;
/** Whether to blindly trust certificates */
private boolean _trustAll; private boolean _trustAll;
/** Whether TLS renegotiation is allowed */
private boolean _renegotiationAllowed = true;
/** /**
* Construct an instance of SslContextFactory * Construct an instance of SslContextFactory
* Default constructor for use in XmlConfiguration files * Default constructor for use in XmlConfiguration files
@ -764,6 +768,22 @@ public class SslContextFactory extends AbstractLifeCycle
_trustManagerFactoryAlgorithm = algorithm; _trustManagerFactoryAlgorithm = algorithm;
} }
/**
* @return whether TLS renegotiation is allowed (true by default)
*/
public boolean isRenegotiationAllowed()
{
return _renegotiationAllowed;
}
/**
* @param renegotiationAllowed whether TLS renegotiation is allowed
*/
public void setRenegotiationAllowed(boolean renegotiationAllowed)
{
_renegotiationAllowed = renegotiationAllowed;
}
/** /**
* @return Path to file that contains Certificate Revocation List * @return Path to file that contains Certificate Revocation List
*/ */

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
@ -82,6 +81,7 @@ public class WebSocketClientSelectorManager extends SelectorManager
{ {
SSLEngine engine = newSSLEngine(sslContextFactory,channel); SSLEngine engine = newSSLEngine(sslContextFactory,channel);
SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine); SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine);
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint(); EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
Connection connection = newUpgradeConnection(channel,sslEndPoint,connectPromise); Connection connection = newUpgradeConnection(channel,sslEndPoint,connectPromise);