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

This commit is contained in:
Simone Bordet 2019-10-16 18:40:08 +02:00
commit 364288886e
3 changed files with 329 additions and 55 deletions

View File

@ -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());
}
} }

View File

@ -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);

View File

@ -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;