Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.
This commit is contained in:
commit
364288886e
|
@ -19,17 +19,22 @@
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
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.Executor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
|
@ -39,13 +44,21 @@ import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
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.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||||
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Handler;
|
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.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
|
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
|
||||||
|
@ -68,7 +81,6 @@ public class HttpClientTLSTest
|
||||||
private Server server;
|
private Server server;
|
||||||
private ServerConnector connector;
|
private ServerConnector connector;
|
||||||
private HttpClient client;
|
private HttpClient client;
|
||||||
private SSLSocket sslSocket;
|
|
||||||
|
|
||||||
private void startServer(SslContextFactory.Server sslContextFactory, Handler handler) throws Exception
|
private void startServer(SslContextFactory.Server sslContextFactory, Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
|
@ -424,16 +436,16 @@ public class HttpClientTLSTest
|
||||||
|
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
Socket socket = new Socket(host, port);
|
Socket socket1 = new Socket(host, port);
|
||||||
sslSocket = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket, host, port, true);
|
SSLSocket sslSocket1 = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket1, host, port, true);
|
||||||
CountDownLatch handshakeLatch1 = new CountDownLatch(1);
|
CountDownLatch handshakeLatch1 = new CountDownLatch(1);
|
||||||
AtomicReference<byte[]> session1 = new AtomicReference<>();
|
AtomicReference<byte[]> session1 = new AtomicReference<>();
|
||||||
sslSocket.addHandshakeCompletedListener(event ->
|
sslSocket1.addHandshakeCompletedListener(event ->
|
||||||
{
|
{
|
||||||
session1.set(event.getSession().getId());
|
session1.set(event.getSession().getId());
|
||||||
handshakeLatch1.countDown();
|
handshakeLatch1.countDown();
|
||||||
});
|
});
|
||||||
sslSocket.startHandshake();
|
sslSocket1.startHandshake();
|
||||||
assertTrue(handshakeLatch1.await(5, TimeUnit.SECONDS));
|
assertTrue(handshakeLatch1.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
// In TLS 1.3 the server sends a NewSessionTicket post-handshake message
|
// In TLS 1.3 the server sends a NewSessionTicket post-handshake message
|
||||||
|
@ -441,29 +453,29 @@ public class HttpClientTLSTest
|
||||||
|
|
||||||
assertThrows(SocketTimeoutException.class, () ->
|
assertThrows(SocketTimeoutException.class, () ->
|
||||||
{
|
{
|
||||||
sslSocket.setSoTimeout(1000);
|
sslSocket1.setSoTimeout(1000);
|
||||||
sslSocket.getInputStream().read();
|
sslSocket1.getInputStream().read();
|
||||||
});
|
});
|
||||||
|
|
||||||
// The client closes abruptly.
|
// The client closes abruptly.
|
||||||
socket.close();
|
socket1.close();
|
||||||
|
|
||||||
// Try again and compare the session ids.
|
// Try again and compare the session ids.
|
||||||
socket = new Socket(host, port);
|
Socket socket2 = new Socket(host, port);
|
||||||
sslSocket = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket, host, port, true);
|
SSLSocket sslSocket2 = (SSLSocket)clientTLSFactory.getSslContext().getSocketFactory().createSocket(socket2, host, port, true);
|
||||||
CountDownLatch handshakeLatch2 = new CountDownLatch(1);
|
CountDownLatch handshakeLatch2 = new CountDownLatch(1);
|
||||||
AtomicReference<byte[]> session2 = new AtomicReference<>();
|
AtomicReference<byte[]> session2 = new AtomicReference<>();
|
||||||
sslSocket.addHandshakeCompletedListener(event ->
|
sslSocket2.addHandshakeCompletedListener(event ->
|
||||||
{
|
{
|
||||||
session2.set(event.getSession().getId());
|
session2.set(event.getSession().getId());
|
||||||
handshakeLatch2.countDown();
|
handshakeLatch2.countDown();
|
||||||
});
|
});
|
||||||
sslSocket.startHandshake();
|
sslSocket2.startHandshake();
|
||||||
assertTrue(handshakeLatch2.await(5, TimeUnit.SECONDS));
|
assertTrue(handshakeLatch2.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
assertArrayEquals(session1.get(), session2.get());
|
assertArrayEquals(session1.get(), session2.get());
|
||||||
|
|
||||||
sslSocket.close();
|
sslSocket2.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -485,7 +497,7 @@ public class HttpClientTLSTest
|
||||||
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory)
|
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||||
{
|
{
|
||||||
SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(sslContextFactory, connectionFactory);
|
SslClientConnectionFactory ssl = (SslClientConnectionFactory)super.newSslClientConnectionFactory(sslContextFactory, connectionFactory);
|
||||||
ssl.setAllowMissingCloseMessage(false);
|
ssl.setRequireCloseMessage(true);
|
||||||
return ssl;
|
return ssl;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -512,19 +524,19 @@ public class HttpClientTLSTest
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the response is Content-Length delimited, allowing the
|
// If the response is Content-Length delimited, the lack of
|
||||||
// missing TLS Close Message is fine because the application
|
// the TLS Close Message is fine because the application
|
||||||
// will see a EOFException anyway.
|
// will see a EOFException anyway: the Content-Length and
|
||||||
// If the response is connection delimited, allowing the
|
// the actual content bytes count won't match.
|
||||||
// missing TLS Close Message is bad because the application
|
// If the response is connection delimited, the lack of the
|
||||||
// will see a successful response with truncated content.
|
// TLS Close Message is bad because the application will
|
||||||
|
// see a successful response, but with truncated content.
|
||||||
|
|
||||||
// Verify that by not allowing the missing
|
// Verify that by requiring the TLS Close Message we get
|
||||||
// TLS Close Message we get a response failure.
|
// a response failure.
|
||||||
|
|
||||||
byte[] half = new byte[8];
|
byte[] half = new byte[8];
|
||||||
String response = "HTTP/1.1 200 OK\r\n" +
|
String response = "HTTP/1.1 200 OK\r\n" +
|
||||||
// "Content-Length: " + (half.length * 2) + "\r\n" +
|
|
||||||
"Connection: close\r\n" +
|
"Connection: close\r\n" +
|
||||||
"\r\n";
|
"\r\n";
|
||||||
OutputStream output = sslSocket.getOutputStream();
|
OutputStream output = sslSocket.getOutputStream();
|
||||||
|
@ -564,4 +576,200 @@ public class HttpClientTLSTest
|
||||||
|
|
||||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeverUsedConnectionThenServerIdleTimeout() throws Exception
|
||||||
|
{
|
||||||
|
long idleTimeout = 2000;
|
||||||
|
|
||||||
|
SslContextFactory.Server serverTLSFactory = createServerSslContextFactory();
|
||||||
|
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||||
|
serverThreads.setName("server");
|
||||||
|
server = new Server(serverThreads);
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory http = new HttpConnectionFactory(httpConfig);
|
||||||
|
AtomicLong serverBytes = new AtomicLong();
|
||||||
|
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 int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
serverBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connector = new ServerConnector(server, 1, 1, ssl, http);
|
||||||
|
connector.setIdleTimeout(idleTimeout);
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(new EmptyServerHandler());
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
SslContextFactory.Client clientTLSFactory = createClientSslContextFactory();
|
||||||
|
ClientConnector clientConnector = new ClientConnector();
|
||||||
|
clientConnector.setSelectors(1);
|
||||||
|
clientConnector.setSslContextFactory(clientTLSFactory);
|
||||||
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
|
clientThreads.setName("client");
|
||||||
|
clientConnector.setExecutor(clientThreads);
|
||||||
|
AtomicLong clientBytes = new AtomicLong();
|
||||||
|
client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector))
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
if (sslContextFactory == null)
|
||||||
|
sslContextFactory = getSslContextFactory();
|
||||||
|
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
clientBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
client.setExecutor(clientThreads);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// Create a connection but don't use it.
|
||||||
|
Origin origin = new Origin(HttpScheme.HTTPS.asString(), "localhost", connector.getLocalPort());
|
||||||
|
HttpDestination destination = client.resolveDestination(new HttpDestination.Key(origin, null));
|
||||||
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
// Trigger the creation of a new connection, but don't use it.
|
||||||
|
connectionPool.tryCreate(-1);
|
||||||
|
// Verify that the connection has been created.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Thread.sleep(50);
|
||||||
|
if (connectionPool.getConnectionCount() == 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the server to idle timeout the connection.
|
||||||
|
Thread.sleep(idleTimeout + idleTimeout / 2);
|
||||||
|
|
||||||
|
// The connection should be gone from the connection pool.
|
||||||
|
assertEquals(0, connectionPool.getConnectionCount(), connectionPool.dump());
|
||||||
|
assertEquals(0, serverBytes.get());
|
||||||
|
assertEquals(0, clientBytes.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNeverUsedConnectionThenClientIdleTimeout() throws Exception
|
||||||
|
{
|
||||||
|
SslContextFactory.Server serverTLSFactory = createServerSslContextFactory();
|
||||||
|
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||||
|
serverThreads.setName("server");
|
||||||
|
server = new Server(serverThreads);
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory http = new HttpConnectionFactory(httpConfig);
|
||||||
|
AtomicLong serverBytes = new AtomicLong();
|
||||||
|
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 int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
serverBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connector = new ServerConnector(server, 1, 1, ssl, http);
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(new EmptyServerHandler());
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
long idleTimeout = 2000;
|
||||||
|
|
||||||
|
SslContextFactory.Client clientTLSFactory = createClientSslContextFactory();
|
||||||
|
ClientConnector clientConnector = new ClientConnector();
|
||||||
|
clientConnector.setSelectors(1);
|
||||||
|
clientConnector.setSslContextFactory(clientTLSFactory);
|
||||||
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
|
clientThreads.setName("client");
|
||||||
|
clientConnector.setExecutor(clientThreads);
|
||||||
|
AtomicLong clientBytes = new AtomicLong();
|
||||||
|
client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector))
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
if (sslContextFactory == null)
|
||||||
|
sslContextFactory = getSslContextFactory();
|
||||||
|
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||||
|
{
|
||||||
|
return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
int n = super.networkFill(input);
|
||||||
|
if (n > 0)
|
||||||
|
clientBytes.addAndGet(n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
client.setIdleTimeout(idleTimeout);
|
||||||
|
client.setExecutor(clientThreads);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
// Create a connection but don't use it.
|
||||||
|
Origin origin = new Origin(HttpScheme.HTTPS.asString(), "localhost", connector.getLocalPort());
|
||||||
|
HttpDestination destination = client.resolveDestination(new HttpDestination.Key(origin, null));
|
||||||
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
// Trigger the creation of a new connection, but don't use it.
|
||||||
|
connectionPool.tryCreate(-1);
|
||||||
|
// Verify that the connection has been created.
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Thread.sleep(50);
|
||||||
|
if (connectionPool.getConnectionCount() == 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to idle timeout the connection.
|
||||||
|
Thread.sleep(idleTimeout + idleTimeout / 2);
|
||||||
|
|
||||||
|
// The connection should be gone from the connection pool.
|
||||||
|
assertEquals(0, connectionPool.getConnectionCount(), connectionPool.dump());
|
||||||
|
assertEquals(0, serverBytes.get());
|
||||||
|
assertEquals(0, clientBytes.get());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||||
private final ClientConnectionFactory connectionFactory;
|
private final ClientConnectionFactory connectionFactory;
|
||||||
private boolean _directBuffersForEncryption = true;
|
private boolean _directBuffersForEncryption = true;
|
||||||
private boolean _directBuffersForDecryption = true;
|
private boolean _directBuffersForDecryption = true;
|
||||||
private boolean allowMissingCloseMessage = true;
|
private boolean _requireCloseMessage;
|
||||||
|
|
||||||
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
||||||
{
|
{
|
||||||
|
@ -76,14 +76,22 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||||
return _directBuffersForEncryption;
|
return _directBuffersForEncryption;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAllowMissingCloseMessage()
|
/**
|
||||||
|
* @return whether peers must send the TLS {@code close_notify} message
|
||||||
|
* @see SslConnection#isRequireCloseMessage()
|
||||||
|
*/
|
||||||
|
public boolean isRequireCloseMessage()
|
||||||
{
|
{
|
||||||
return allowMissingCloseMessage;
|
return _requireCloseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
|
/**
|
||||||
|
* @param requireCloseMessage whether peers must send the TLS {@code close_notify} message
|
||||||
|
* @see SslConnection#setRequireCloseMessage(boolean)
|
||||||
|
*/
|
||||||
|
public void setRequireCloseMessage(boolean requireCloseMessage)
|
||||||
{
|
{
|
||||||
this.allowMissingCloseMessage = allowMissingCloseMessage;
|
_requireCloseMessage = requireCloseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,7 +126,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());
|
sslConnection.setRequireCloseMessage(isRequireCloseMessage());
|
||||||
ContainerLifeCycle client = (ContainerLifeCycle)context.get(ClientConnectionFactory.CLIENT_CONTEXT_KEY);
|
ContainerLifeCycle client = (ContainerLifeCycle)context.get(ClientConnectionFactory.CLIENT_CONTEXT_KEY);
|
||||||
if (client != null)
|
if (client != null)
|
||||||
client.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
|
client.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
|
||||||
|
|
|
@ -80,9 +80,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
private static final Logger LOG = Log.getLogger(SslConnection.class);
|
private static final Logger LOG = Log.getLogger(SslConnection.class);
|
||||||
private static final String TLS_1_3 = "TLSv1.3";
|
private static final String TLS_1_3 = "TLSv1.3";
|
||||||
|
|
||||||
private enum Handshake
|
private enum HandshakeState
|
||||||
{
|
{
|
||||||
INITIAL,
|
INITIAL,
|
||||||
|
HANDSHAKE,
|
||||||
SUCCEEDED,
|
SUCCEEDED,
|
||||||
FAILED
|
FAILED
|
||||||
}
|
}
|
||||||
|
@ -113,10 +114,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
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 boolean _requireCloseMessage;
|
||||||
private FlushState _flushState = FlushState.IDLE;
|
private FlushState _flushState = FlushState.IDLE;
|
||||||
private FillState _fillState = FillState.IDLE;
|
private FillState _fillState = FillState.IDLE;
|
||||||
private AtomicReference<Handshake> _handshake = new AtomicReference<>(Handshake.INITIAL);
|
private AtomicReference<HandshakeState> _handshake = new AtomicReference<>(HandshakeState.INITIAL);
|
||||||
private boolean _underflown;
|
private boolean _underflown;
|
||||||
|
|
||||||
private abstract class RunnableTask implements Runnable, Invocable
|
private abstract class RunnableTask implements Runnable, Invocable
|
||||||
|
@ -231,7 +232,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The number of renegotions allowed for this connection. When the limit
|
* @return The number of renegotiations allowed for this connection. When the limit
|
||||||
* is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
* is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
||||||
*/
|
*/
|
||||||
public int getRenegotiationLimit()
|
public int getRenegotiationLimit()
|
||||||
|
@ -240,7 +241,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param renegotiationLimit The number of renegotions allowed for this connection.
|
* @param renegotiationLimit The number of renegotiations allowed for this connection.
|
||||||
* When the limit is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
* When the limit is 0 renegotiation will be denied. If the limit is less than 0 then no limit is applied.
|
||||||
* Default -1.
|
* Default -1.
|
||||||
*/
|
*/
|
||||||
|
@ -249,14 +250,42 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
_renegotiationLimit = renegotiationLimit;
|
_renegotiationLimit = renegotiationLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAllowMissingCloseMessage()
|
/**
|
||||||
|
* @return whether peers must send the TLS {@code close_notify} message
|
||||||
|
*/
|
||||||
|
public boolean isRequireCloseMessage()
|
||||||
{
|
{
|
||||||
return _allowMissingCloseMessage;
|
return _requireCloseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllowMissingCloseMessage(boolean allowMissingCloseMessage)
|
/**
|
||||||
|
* <p>Sets whether it is required that a peer send the TLS {@code close_notify} message
|
||||||
|
* to indicate the will to close the connection, otherwise it may be interpreted as a
|
||||||
|
* truncation attack.</p>
|
||||||
|
* <p>This option is only useful on clients, since typically servers cannot accept
|
||||||
|
* connection-delimited content that may be truncated.</p>
|
||||||
|
*
|
||||||
|
* @param requireCloseMessage whether peers must send the TLS {@code close_notify} message
|
||||||
|
*/
|
||||||
|
public void setRequireCloseMessage(boolean requireCloseMessage)
|
||||||
{
|
{
|
||||||
this._allowMissingCloseMessage = allowMissingCloseMessage;
|
_requireCloseMessage = requireCloseMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandshakeInitial()
|
||||||
|
{
|
||||||
|
return _handshake.get() == HandshakeState.INITIAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandshakeSucceeded()
|
||||||
|
{
|
||||||
|
return _handshake.get() == HandshakeState.SUCCEEDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHandshakeComplete()
|
||||||
|
{
|
||||||
|
HandshakeState state = _handshake.get();
|
||||||
|
return state == HandshakeState.SUCCEEDED || state == HandshakeState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acquireEncryptedInput()
|
private void acquireEncryptedInput()
|
||||||
|
@ -361,6 +390,16 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int networkFill(ByteBuffer input) throws IOException
|
||||||
|
{
|
||||||
|
return getEndPoint().fill(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean networkFlush(ByteBuffer output) throws IOException
|
||||||
|
{
|
||||||
|
return getEndPoint().flush(output);
|
||||||
|
}
|
||||||
|
|
||||||
public class DecryptedEndPoint extends AbstractEndPoint
|
public class DecryptedEndPoint extends AbstractEndPoint
|
||||||
{
|
{
|
||||||
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
|
private final Callback _incompleteWriteCallback = new IncompleteWriteCallback();
|
||||||
|
@ -558,14 +597,23 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's try reading some encrypted data... even if we have some already.
|
// Let's try reading some encrypted data... even if we have some already.
|
||||||
int netFilled = getEndPoint().fill(_encryptedInput);
|
int netFilled = networkFill(_encryptedInput);
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("net filled={}", netFilled);
|
LOG.debug("net filled={}", netFilled);
|
||||||
|
|
||||||
if (netFilled > 0 && _handshake.get() == Handshake.INITIAL && isOutboundDone())
|
// Workaround for Java 11 behavior.
|
||||||
|
if (netFilled < 0 && isHandshakeInitial() && BufferUtil.isEmpty(_encryptedInput))
|
||||||
|
closeInbound();
|
||||||
|
|
||||||
|
if (netFilled > 0 && !isHandshakeComplete() && isOutboundDone())
|
||||||
throw new SSLHandshakeException("Closed during handshake");
|
throw new SSLHandshakeException("Closed during handshake");
|
||||||
|
|
||||||
|
if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("fill starting handshake {}", SslConnection.this);
|
||||||
|
}
|
||||||
|
|
||||||
// Let's unwrap even if we have no net data because in that
|
// Let's unwrap even if we have no net data because in that
|
||||||
// case we want to fall through to the handshake handling
|
// case we want to fall through to the handshake handling
|
||||||
int pos = BufferUtil.flipToFill(appIn);
|
int pos = BufferUtil.flipToFill(appIn);
|
||||||
|
@ -771,7 +819,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private void handshakeSucceeded() throws SSLException
|
private void handshakeSucceeded() throws SSLException
|
||||||
{
|
{
|
||||||
if (_handshake.compareAndSet(Handshake.INITIAL, Handshake.SUCCEEDED))
|
if (_handshake.compareAndSet(HandshakeState.HANDSHAKE, HandshakeState.SUCCEEDED))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("handshake succeeded {} {} {}/{}", SslConnection.this,
|
LOG.debug("handshake succeeded {} {} {}/{}", SslConnection.this,
|
||||||
|
@ -779,7 +827,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
_sslEngine.getSession().getProtocol(), _sslEngine.getSession().getCipherSuite());
|
_sslEngine.getSession().getProtocol(), _sslEngine.getSession().getCipherSuite());
|
||||||
notifyHandshakeSucceeded(_sslEngine);
|
notifyHandshakeSucceeded(_sslEngine);
|
||||||
}
|
}
|
||||||
else if (_handshake.get() == Handshake.SUCCEEDED)
|
else if (isHandshakeSucceeded())
|
||||||
{
|
{
|
||||||
if (_renegotiationLimit > 0)
|
if (_renegotiationLimit > 0)
|
||||||
_renegotiationLimit--;
|
_renegotiationLimit--;
|
||||||
|
@ -788,7 +836,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private void handshakeFailed(Throwable failure)
|
private void handshakeFailed(Throwable failure)
|
||||||
{
|
{
|
||||||
if (_handshake.compareAndSet(Handshake.INITIAL, Handshake.FAILED))
|
if (_handshake.compareAndSet(HandshakeState.HANDSHAKE, HandshakeState.FAILED))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("handshake failed {} {}", SslConnection.this, failure);
|
LOG.debug("handshake failed {} {}", SslConnection.this, failure);
|
||||||
|
@ -820,7 +868,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
catch (SSLException x)
|
catch (SSLException x)
|
||||||
{
|
{
|
||||||
if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && !isAllowMissingCloseMessage())
|
if (handshakeStatus == HandshakeStatus.NOT_HANDSHAKING && isRequireCloseMessage())
|
||||||
throw x;
|
throw x;
|
||||||
LOG.ignore(x);
|
LOG.ignore(x);
|
||||||
return x;
|
return x;
|
||||||
|
@ -850,7 +898,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
|
|
||||||
// finish of any previous flushes
|
// finish of any previous flushes
|
||||||
if (BufferUtil.hasContent(_encryptedOutput) && !getEndPoint().flush(_encryptedOutput))
|
if (BufferUtil.hasContent(_encryptedOutput) && !networkFlush(_encryptedOutput))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
boolean isEmpty = BufferUtil.isEmpty(appOuts);
|
boolean isEmpty = BufferUtil.isEmpty(appOuts);
|
||||||
|
@ -878,6 +926,9 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case NEED_UNWRAP:
|
case NEED_UNWRAP:
|
||||||
|
// Workaround for Java 11 behavior.
|
||||||
|
if (isHandshakeInitial() && isOutboundDone())
|
||||||
|
break;
|
||||||
if (_fillState == FillState.IDLE)
|
if (_fillState == FillState.IDLE)
|
||||||
{
|
{
|
||||||
int filled = fill(BufferUtil.EMPTY_BUFFER);
|
int filled = fill(BufferUtil.EMPTY_BUFFER);
|
||||||
|
@ -895,6 +946,12 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
if (_encryptedOutput == null)
|
if (_encryptedOutput == null)
|
||||||
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
|
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
|
||||||
|
|
||||||
|
if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("flush starting handshake {}", SslConnection.this);
|
||||||
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
|
@ -920,7 +977,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
// if we have net bytes, let's try to flush them
|
// if we have net bytes, let's try to flush them
|
||||||
boolean flushed = true;
|
boolean flushed = true;
|
||||||
if (BufferUtil.hasContent(_encryptedOutput))
|
if (BufferUtil.hasContent(_encryptedOutput))
|
||||||
flushed = getEndPoint().flush(_encryptedOutput);
|
flushed = networkFlush(_encryptedOutput);
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
|
LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
|
||||||
|
@ -1096,15 +1153,15 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
@Override
|
@Override
|
||||||
public void doShutdownOutput()
|
public void doShutdownOutput()
|
||||||
{
|
{
|
||||||
final EndPoint endp = getEndPoint();
|
EndPoint endPoint = getEndPoint();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
boolean close;
|
boolean close;
|
||||||
boolean flush = false;
|
boolean flush = false;
|
||||||
synchronized (_decryptedEndPoint)
|
synchronized (_decryptedEndPoint)
|
||||||
{
|
{
|
||||||
boolean ishut = endp.isInputShutdown();
|
boolean ishut = endPoint.isInputShutdown();
|
||||||
boolean oshut = endp.isOutputShutdown();
|
boolean oshut = endPoint.isOutputShutdown();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("shutdownOutput: {} oshut={}, ishut={}", SslConnection.this, oshut, ishut);
|
LOG.debug("shutdownOutput: {} oshut={}, ishut={}", SslConnection.this, oshut, ishut);
|
||||||
|
|
||||||
|
@ -1128,19 +1185,19 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
// let's just flush the encrypted output in the background.
|
// let's just flush the encrypted output in the background.
|
||||||
ByteBuffer write = _encryptedOutput;
|
ByteBuffer write = _encryptedOutput;
|
||||||
if (BufferUtil.hasContent(write))
|
if (BufferUtil.hasContent(write))
|
||||||
endp.write(Callback.from(Callback.NOOP::succeeded, t -> endp.close()), write);
|
endPoint.write(Callback.from(Callback.NOOP::succeeded, t -> endPoint.close()), write);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (close)
|
if (close)
|
||||||
endp.close();
|
endPoint.close();
|
||||||
else
|
else
|
||||||
ensureFillInterested();
|
ensureFillInterested();
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
LOG.ignore(x);
|
LOG.ignore(x);
|
||||||
endp.close();
|
endPoint.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1152,7 +1209,8 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
LOG.ignore(x);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1258,7 +1316,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
||||||
|
|
||||||
private boolean isRenegotiating()
|
private boolean isRenegotiating()
|
||||||
{
|
{
|
||||||
if (_handshake.get() == Handshake.INITIAL)
|
if (!isHandshakeComplete())
|
||||||
return false;
|
return false;
|
||||||
if (isTLS13())
|
if (isTLS13())
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in New Issue