Merged branch 'jetty-9.3.x' into 'jetty-9.4.x'.

This commit is contained in:
Simone Bordet 2017-05-17 13:16:21 +02:00
commit 1ac103b6ce
9 changed files with 193 additions and 33 deletions

View File

@ -59,6 +59,7 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
@ -1074,6 +1075,11 @@ public class HttpClient extends ContainerLifeCycle
return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme); return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme);
} }
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{
return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory);
}
private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory> private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
{ {
private final Set<ContentDecoder.Factory> set = new HashSet<>(); private final Set<ContentDecoder.Factory> set = new HashSet<>();

View File

@ -34,7 +34,6 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
@ -124,7 +123,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory) protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{ {
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory); return client.newSslClientConnectionFactory(connectionFactory);
} }
public boolean isSecure() public boolean isSecure()

View File

@ -34,7 +34,6 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -204,8 +203,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise); context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY); HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
HttpClient client = destination.getHttpClient(); HttpClient client = destination.getHttpClient();
ClientConnectionFactory sslConnectionFactory = ClientConnectionFactory sslConnectionFactory = client.newSslClientConnectionFactory(connectionFactory);
new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection(); HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context); org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
// Creating the connection will link the new Connection the EndPoint, // Creating the connection will link the new Connection the EndPoint,

View File

@ -30,7 +30,6 @@ import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
@ -197,7 +196,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
HttpClient client = destination.getHttpClient(); HttpClient client = destination.getHttpClient();
ClientConnectionFactory connectionFactory = this.connectionFactory; ClientConnectionFactory connectionFactory = this.connectionFactory;
if (destination.isSecure()) if (destination.isSecure())
connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory); connectionFactory = client.newSslClientConnectionFactory(connectionFactory);
org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context); org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context);
getEndPoint().upgrade(newConnection); getEndPoint().upgrade(newConnection);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())

View File

@ -18,22 +18,34 @@
package org.eclipse.jetty.client; package org.eclipse.jetty.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslHandshakeListener; import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -390,4 +402,116 @@ public class HttpClientTLSTest
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS)); Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS)); Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
} }
@Test
public void testClientRawCloseDoesNotInvalidateSession() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
SslContextFactory clientTLSFactory = createSslContextFactory();
clientTLSFactory.start();
String host = "localhost";
int port = connector.getLocalPort();
Socket socket = new Socket(host, port);
SSLSocket sslSocket = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket, host, port, true);
CountDownLatch handshakeLatch1 = new CountDownLatch(1);
AtomicReference<byte[]> session1 = new AtomicReference<>();
sslSocket.addHandshakeCompletedListener(event ->
{
session1.set(event.getSession().getId());
handshakeLatch1.countDown();
});
sslSocket.startHandshake();
Assert.assertTrue(handshakeLatch1.await(5, TimeUnit.SECONDS));
// The client closes abruptly.
socket.close();
// Try again and compare the session ids.
socket = new Socket(host, port);
sslSocket = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket, host, port, true);
CountDownLatch handshakeLatch2 = new CountDownLatch(1);
AtomicReference<byte[]> session2 = new AtomicReference<>();
sslSocket.addHandshakeCompletedListener(event ->
{
session2.set(event.getSession().getId());
handshakeLatch2.countDown();
});
sslSocket.startHandshake();
Assert.assertTrue(handshakeLatch2.await(5, TimeUnit.SECONDS));
Assert.assertArrayEquals(session1.get(), session2.get());
sslSocket.close();
}
@Test
public void testServerRawCloseDetectedByClient() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
serverTLSFactory.start();
try (ServerSocket server = new ServerSocket(0))
{
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
client = new HttpClient(createSslContextFactory())
{
@Override
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{
SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(connectionFactory);
ssl.setAllowMissingCloseMessage(false);
return ssl;
}
};
client.setExecutor(clientThreads);
client.start();
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", server.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send(result ->
{
Assert.assertThat(result.getResponseFailure(), Matchers.instanceOf(SSLException.class));
latch.countDown();
});
Socket socket = server.accept();
SSLSocket sslSocket = (SSLSocket)serverTLSFactory.getSslContext().getSocketFactory().createSocket(socket, null, socket.getPort(), true);
sslSocket.setUseClientMode(false);
BufferedReader reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream(), StandardCharsets.UTF_8));
while (true)
{
String line = reader.readLine();
if (line == null || line.isEmpty())
break;
}
// If the response is Content-Length delimited, allowing the
// missing TLS Close Message is fine because the application
// will see a EOFException anyway.
// If the response is connection delimited, allowing the
// missing TLS Close Message is bad because the application
// will see a successful response with truncated content.
// Verify that by not allowing the missing
// TLS Close Message we get a response failure.
byte[] half = new byte[8];
String response = "HTTP/1.1 200 OK\r\n" +
// "Content-Length: " + (half.length * 2) + "\r\n" +
"Connection: close\r\n" +
"\r\n";
OutputStream output = sslSocket.getOutputStream();
output.write(response.getBytes(StandardCharsets.UTF_8));
output.write(half);
output.flush();
// Simulate a truncation attack by raw closing.
socket.close();
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
} }

View File

@ -40,8 +40,6 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; 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.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
@ -252,18 +250,12 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
start(new TimeoutHandler(2 * timeout)); start(new TimeoutHandler(2 * timeout));
client.stop(); client.stop();
final AtomicBoolean sslIdle = new AtomicBoolean(); final AtomicBoolean sslIdle = new AtomicBoolean();
client = new HttpClient(new HttpClientTransportOverHTTP() client = new HttpClient(sslContextFactory)
{ {
@Override @Override
public HttpDestination newHttpDestination(Origin origin) public ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{ {
return new HttpDestinationOverHTTP(getHttpClient(), origin) return new SslClientConnectionFactory(getSslContextFactory(), getByteBufferPool(), getExecutor(), connectionFactory)
{
@Override
protected ClientConnectionFactory newSslClientConnectionFactory(ClientConnectionFactory connectionFactory)
{
HttpClient client = getHttpClient();
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory)
{ {
@Override @Override
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine) protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
@ -281,8 +273,6 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
}; };
} }
}; };
}
}, sslContextFactory);
client.setIdleTimeout(timeout); client.setIdleTimeout(timeout);
client.start(); client.start();

View File

@ -150,7 +150,7 @@ public class HTTP2Client extends ContainerLifeCycle
if (sslContextFactory != null) if (sslContextFactory != null)
{ {
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), h2, getProtocols()); ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), h2, getProtocols());
factory = new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), alpn); factory = newSslClientConnectionFactory(sslContextFactory, alpn);
} }
return factory.newConnection(endPoint, context); return factory.newConnection(endPoint, context);
}); });
@ -171,6 +171,11 @@ public class HTTP2Client extends ContainerLifeCycle
return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors()); return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors());
} }
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory sslContextFactory, ClientConnectionFactory connectionFactory)
{
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
}
public Executor getExecutor() public Executor getExecutor()
{ {
return executor; return executor;

View File

@ -42,6 +42,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
private final ByteBufferPool byteBufferPool; private final ByteBufferPool byteBufferPool;
private final Executor executor; private final Executor executor;
private final ClientConnectionFactory connectionFactory; private final ClientConnectionFactory connectionFactory;
private boolean allowMissingCloseMessage = true;
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory) public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
{ {
@ -51,6 +52,16 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
} }
public boolean isAllowMissingCloseMessage()
{
return allowMissingCloseMessage;
}
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
{
this.allowMissingCloseMessage = allowMissingCloseMessage;
}
@Override @Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{ {
@ -84,6 +95,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
SslConnection sslConnection = (SslConnection)connection; SslConnection sslConnection = (SslConnection)connection;
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed()); sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
sslConnection.setRenegotiationLimit(sslContextFactory.getRenegotiationLimit()); sslConnection.setRenegotiationLimit(sslContextFactory.getRenegotiationLimit());
sslConnection.setAllowMissingCloseMessage(isAllowMissingCloseMessage());
ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY); ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener); connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
} }

View File

@ -93,6 +93,7 @@ public class SslConnection extends AbstractConnection
private boolean _renegotiationAllowed; private boolean _renegotiationAllowed;
private int _renegotiationLimit = -1; private int _renegotiationLimit = -1;
private boolean _closedOutbound; private boolean _closedOutbound;
private boolean _allowMissingCloseMessage = true;
private abstract class RunnableTask implements Runnable, Invocable private abstract class RunnableTask implements Runnable, Invocable
{ {
@ -231,6 +232,16 @@ public class SslConnection extends AbstractConnection
_renegotiationLimit = renegotiationLimit; _renegotiationLimit = renegotiationLimit;
} }
public boolean isAllowMissingCloseMessage()
{
return _allowMissingCloseMessage;
}
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
{
this._allowMissingCloseMessage = allowMissingCloseMessage;
}
@Override @Override
public void onOpen() public void onOpen()
{ {
@ -662,7 +673,7 @@ public class SslConnection extends AbstractConnection
if (_underFlown) if (_underFlown)
{ {
if (net_filled < 0) if (net_filled < 0 && _sslEngine.getUseClientMode())
closeInbound(); closeInbound();
if (net_filled <= 0) if (net_filled <= 0)
return net_filled; return net_filled;
@ -860,29 +871,45 @@ public class SslConnection extends AbstractConnection
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Renegotiation denied {}", SslConnection.this); LOG.debug("Renegotiation denied {}", SslConnection.this);
closeInbound(); terminateInput();
return false; return false;
} }
if (_renegotiationLimit==0) if (getRenegotiationLimit()==0)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Renegotiation limit exceeded {}", SslConnection.this); LOG.debug("Renegotiation limit exceeded {}", SslConnection.this);
closeInbound(); terminateInput();
return false; return false;
} }
return true; return true;
} }
private void closeInbound() private void terminateInput()
{ {
try
{
_sslEngine.closeInbound();
}
catch (Throwable x)
{
LOG.ignore(x);
}
}
private void closeInbound() throws SSLException
{
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
try try
{ {
_sslEngine.closeInbound(); _sslEngine.closeInbound();
} }
catch (SSLException x) catch (SSLException x)
{ {
if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && !isAllowMissingCloseMessage())
throw x;
else
LOG.ignore(x); LOG.ignore(x);
} }
} }