diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties index a0b212fc2240..3158ac8952ee 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 80096da87c2d..f5853086bf89 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; @@ -764,6 +767,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) { @@ -894,6 +902,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) { @@ -959,6 +969,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) { @@ -1074,6 +1089,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) { @@ -1160,6 +1180,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 b169e4c9d6c3..bf94ae433201 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; @@ -624,6 +625,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) { @@ -715,6 +724,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) { @@ -812,6 +829,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 383aecc45a70..839eaf8b92b3 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -430,6 +430,13 @@ + + + + Add support for TLS 1.3 client initiated re-keying. (markt) + + +