diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties index b184b922fad7..4deb112c0823 100644 --- a/java/org/apache/tomcat/util/net/LocalStrings.properties +++ b/java/org/apache/tomcat/util/net/LocalStrings.properties @@ -26,6 +26,8 @@ channel.nio.ssl.expandNetInBuffer=Expanding network input buffer to [{0}] bytes channel.nio.ssl.expandNetOutBuffer=Expanding network output buffer to [{0}] bytes channel.nio.ssl.foundHttp=Found an plain text HTTP request on what should be an encrypted TLS connection channel.nio.ssl.handshakeError=Handshake error +channel.nio.ssl.handshakeWrapPending=There is already handshake data waiting to be wrapped +channel.nio.ssl.handshakeWrapQueueTooLong=The queue of handshake data to be wrapped has grown too long channel.nio.ssl.incompleteHandshake=Handshake incomplete, you must complete handshake before reading data. channel.nio.ssl.invalidCloseState=Invalid close state, will not send network data. channel.nio.ssl.invalidStatus=Unexpected status [{0}]. diff --git a/java/org/apache/tomcat/util/net/SecureNio2Channel.java b/java/org/apache/tomcat/util/net/SecureNio2Channel.java index 0016b01bb446..ecbd91261527 100644 --- a/java/org/apache/tomcat/util/net/SecureNio2Channel.java +++ b/java/org/apache/tomcat/util/net/SecureNio2Channel.java @@ -54,10 +54,12 @@ public class SecureNio2Channel extends Nio2Channel { private static final Log log = LogFactory.getLog(SecureNio2Channel.class); private static final StringManager sm = StringManager.getManager(SecureNio2Channel.class); - // Value determined by observation of what the SSL Engine requested in - // various scenarios + // Value determined by observation of what the SSL Engine requested in various scenarios private static final int DEFAULT_NET_BUFFER_SIZE = 16921; + // Much longer than it should ever need to be but short enough to trigger connection closure if something goes wrong + private static final int HANDSHAKE_WRAP_QUEUE_LENGTH_LIMIT = 100; + protected final Nio2Endpoint endpoint; protected ByteBuffer netInBuffer; @@ -68,6 +70,7 @@ public class SecureNio2Channel extends Nio2Channel { protected volatile boolean sniComplete = false; private volatile boolean handshakeComplete = false; + private volatile int handshakeWrapQueueLength = 0; private volatile HandshakeStatus handshakeStatus; //gets set by handshake protected boolean closed; @@ -763,6 +766,11 @@ private Integer unwrap(int nRead, long timeout, TimeUnit unit) throws ExecutionE //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { tasks(); + } else if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + if (++handshakeWrapQueueLength > HANDSHAKE_WRAP_QUEUE_LENGTH_LIMIT) { + throw new ExecutionException( + new IOException(sm.getString("channel.nio.ssl.handshakeWrapQueueTooLong"))); + } } //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { @@ -893,6 +901,8 @@ protected void wrap() { if (!netOutBuffer.hasRemaining()) { netOutBuffer.clear(); SSLEngineResult result = sslEngine.wrap(src, netOutBuffer); + // Call to wrap() will have included any required handshake data + handshakeWrapQueueLength = 0; written = result.bytesConsumed(); netOutBuffer.flip(); if (result.getStatus() == Status.OK) { @@ -958,6 +968,11 @@ public void completed(Integer nBytes, A attach) { //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { tasks(); + } else if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + if (++handshakeWrapQueueLength > HANDSHAKE_WRAP_QUEUE_LENGTH_LIMIT) { + throw new ExecutionException(new IOException( + sm.getString("channel.nio.ssl.handshakeWrapQueueTooLong"))); + } } //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { @@ -1071,6 +1086,11 @@ public void completed(Integer nBytes, A attach) { //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { tasks(); + } else if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + if (++handshakeWrapQueueLength > HANDSHAKE_WRAP_QUEUE_LENGTH_LIMIT) { + throw new ExecutionException(new IOException( + sm.getString("channel.nio.ssl.handshakeWrapQueueTooLong"))); + } } //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { @@ -1180,6 +1200,8 @@ public void write(final ByteBuffer src, final long timeout, final TimeUnit u netOutBuffer.clear(); // Wrap the source data into the internal buffer SSLEngineResult result = sslEngine.wrap(src, netOutBuffer); + // Call to wrap() will have included any required handshake data + handshakeWrapQueueLength = 0; final int written = result.bytesConsumed(); netOutBuffer.flip(); if (result.getStatus() == Status.OK) { diff --git a/java/org/apache/tomcat/util/net/SecureNioChannel.java b/java/org/apache/tomcat/util/net/SecureNioChannel.java index 4b49792fceb4..fed722c02d6b 100644 --- a/java/org/apache/tomcat/util/net/SecureNioChannel.java +++ b/java/org/apache/tomcat/util/net/SecureNioChannel.java @@ -66,6 +66,7 @@ public class SecureNioChannel extends NioChannel { protected boolean sniComplete = false; protected boolean handshakeComplete = false; + protected boolean needHandshakeWrap = false; protected HandshakeStatus handshakeStatus; //gets set by handshake protected boolean closed = false; @@ -621,6 +622,14 @@ public int read(ByteBuffer dst) throws IOException { //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { tasks(); + } else if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + if (getOutboundRemaining() == 0) { + handshakeWrap(true); + } else if (needHandshakeWrap) { + throw new IOException(sm.getString("channel.nio.ssl.handshakeWrapPending")); + } else { + needHandshakeWrap = true; + } } //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { @@ -710,6 +719,14 @@ public long read(ByteBuffer[] dsts, int offset, int length) //perform any tasks if needed if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { tasks(); + } else if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) { + if (getOutboundRemaining() == 0) { + handshakeWrap(true); + } else if (needHandshakeWrap) { + throw new IOException(sm.getString("channel.nio.ssl.handshakeWrapPending")); + } else { + needHandshakeWrap = true; + } } //if we need more network data, then bail out for now. if (unwrap.getStatus() == Status.BUFFER_UNDERFLOW) { @@ -808,6 +825,8 @@ public int write(ByteBuffer src) throws IOException { netOutBuffer.clear(); SSLEngineResult result = sslEngine.wrap(src, netOutBuffer); + // Call to wrap() will have included any required handshake data + needHandshakeWrap = false; // The number of bytes written int written = result.bytesConsumed(); netOutBuffer.flip(); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 3db1ed331c6d..e3d355cad92e 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -119,6 +119,13 @@ + + + + Add support for TLS 1.3 client initiated re-keying. (markt) + + +