diff --git a/libs/nio/src/main/java/org/elasticsearch/nio/ChannelContext.java b/libs/nio/src/main/java/org/elasticsearch/nio/ChannelContext.java index b26636cb1581e..a2663385daa0f 100644 --- a/libs/nio/src/main/java/org/elasticsearch/nio/ChannelContext.java +++ b/libs/nio/src/main/java/org/elasticsearch/nio/ChannelContext.java @@ -95,7 +95,7 @@ public boolean isOpen() { return closeContext.isDone() == false; } - void handleException(Exception e) { + protected void handleException(Exception e) { exceptionHandler.accept(e); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContext.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContext.java index de1259765b9a9..5f7037d57e3f1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContext.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContext.java @@ -16,6 +16,7 @@ import org.elasticsearch.nio.WriteOperation; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.util.LinkedList; @@ -33,7 +34,8 @@ public final class SSLChannelContext extends SocketChannelContext { private static final long CLOSE_TIMEOUT_NANOS = new TimeValue(10, TimeUnit.SECONDS).nanos(); - private static final Runnable DEFAULT_TIMEOUT_CANCELLER = () -> {}; + private static final Runnable DEFAULT_TIMEOUT_CANCELLER = () -> { + }; private final SSLDriver sslDriver; private final InboundChannelBuffer networkReadBuffer; @@ -68,9 +70,17 @@ public void register() throws IOException { public void queueWriteOperation(WriteOperation writeOperation) { getSelector().assertOnSelectorThread(); if (writeOperation instanceof CloseNotifyOperation) { - sslDriver.initiateClose(); - long relativeNanos = CLOSE_TIMEOUT_NANOS + System.nanoTime(); - closeTimeoutCanceller = getSelector().getTaskScheduler().scheduleAtRelativeTime(this::channelCloseTimeout, relativeNanos); + try { + sslDriver.initiateClose(); + SSLOutboundBuffer outboundBuffer = sslDriver.getOutboundBuffer(); + if (outboundBuffer.hasEncryptedBytesToFlush()) { + encryptedFlushes.addLast(outboundBuffer.buildNetworkFlushOperation()); + } + long relativeNanos = CLOSE_TIMEOUT_NANOS + System.nanoTime(); + closeTimeoutCanceller = getSelector().getTaskScheduler().scheduleAtRelativeTime(this::channelCloseTimeout, relativeNanos); + } catch (SSLException e) { + handleException(e); + } } else { super.queueWriteOperation(writeOperation); } @@ -92,39 +102,25 @@ public void flushChannel() throws IOException { } // If the driver is ready for application writes, we can attempt to proceed with any queued writes. - if (sslDriver.readyForApplicationWrites()) { - FlushOperation unencryptedFlush; - while (pendingChannelFlush() == false && (unencryptedFlush = getPendingFlush()) != null) { - if (unencryptedFlush.isFullyFlushed()) { - currentFlushOperationComplete(); - } else { - try { - // Attempt to encrypt application write data. The encrypted data ends up in the - // outbound write buffer. - sslDriver.write(unencryptedFlush); - SSLOutboundBuffer outboundBuffer = sslDriver.getOutboundBuffer(); - if (outboundBuffer.hasEncryptedBytesToFlush() == false) { - break; - } - encryptedFlushes.addLast(outboundBuffer.buildNetworkFlushOperation()); - // Flush the write buffer to the channel - flushEncryptedOperation(); - } catch (IOException e) { - currentFlushOperationFailed(e); - throw e; + FlushOperation unencryptedFlush; + while (pendingChannelFlush() == false && (unencryptedFlush = getPendingFlush()) != null) { + if (unencryptedFlush.isFullyFlushed()) { + currentFlushOperationComplete(); + } else { + try { + // Attempt to encrypt application write data. The encrypted data ends up in the + // outbound write buffer. + sslDriver.write(unencryptedFlush); + SSLOutboundBuffer outboundBuffer = sslDriver.getOutboundBuffer(); + if (outboundBuffer.hasEncryptedBytesToFlush() == false) { + break; } - } - } - } else { - // We are not ready for application writes, check if the driver has non-application writes. We - // only want to continue producing new writes if the outbound write buffer is fully flushed. - while (pendingChannelFlush() == false && sslDriver.needsNonApplicationWrite()) { - sslDriver.nonApplicationWrite(); - // If non-application writes were produced, flush the outbound write buffer. - SSLOutboundBuffer outboundBuffer = sslDriver.getOutboundBuffer(); - if (outboundBuffer.hasEncryptedBytesToFlush()) { - encryptedFlushes.addFirst(outboundBuffer.buildNetworkFlushOperation()); + encryptedFlushes.addLast(outboundBuffer.buildNetworkFlushOperation()); + // Flush the write buffer to the channel flushEncryptedOperation(); + } catch (IOException e) { + currentFlushOperationFailed(e); + throw e; } } } @@ -147,10 +143,10 @@ private void flushEncryptedOperation() throws IOException { @Override public boolean readyForFlush() { getSelector().assertOnSelectorThread(); - if (sslDriver.readyForApplicationWrites()) { + if (sslDriver.readyForApplicationData()) { return pendingChannelFlush() || super.readyForFlush(); } else { - return pendingChannelFlush() || sslDriver.needsNonApplicationWrite(); + return pendingChannelFlush(); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLDriver.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLDriver.java index e54bc9fa16e57..fa0766d08ff8c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLDriver.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLDriver.java @@ -33,17 +33,14 @@ * decrypted and placed into the application buffer passed as an argument. Otherwise, it will be consumed * internally and advance the SSL/TLS close or handshake process. * - * Producing writes for a channel is more complicated. The method {@link #needsNonApplicationWrite()} can be - * called to determine if this driver needs to produce more data to advance the handshake or close process. - * If that method returns true, {@link #nonApplicationWrite()} should be called (and the - * data produced then flushed to the channel) until no further non-application writes are needed. + * When the handshake begins, handshake data is read from the wire, or the channel close initiated, internal + * bytes that need to be written will be produced. The bytes will be placed in the outbound buffer for + * flushing to a channel. * - * If no non-application writes are needed, {@link #readyForApplicationWrites()} can be called to determine - * if the driver is ready to consume application data. (Note: It is possible that - * {@link #readyForApplicationWrites()} and {@link #needsNonApplicationWrite()} can both return false if the - * driver is waiting on non-application data from the peer.) If the driver indicates it is ready for - * application writes, {@link #write(FlushOperation)} can be called. This method will - * encrypt flush operation application data and place it in the outbound buffer for flushing to a channel. + * The method {@link #readyForApplicationData()} can be called to determine if the driver is ready to consume + * application data. If the driver indicates it is ready for application writes, + * {@link #write(FlushOperation)} can be called. This method will encrypt flush operation application data + * and place it in the outbound buffer for flushing to a channel. * * If you are ready to close the channel {@link #initiateClose()} should be called. After that is called, the * driver will start producing non-application writes related to notifying the peer connection that this @@ -62,7 +59,7 @@ public class SSLDriver implements AutoCloseable { private final boolean isClientMode; // This should only be accessed by the network thread associated with this channel, so nothing needs to // be volatile. - private Mode currentMode = new HandshakeMode(); + private Mode currentMode = new RegularMode(); private int packetSize; public SSLDriver(SSLEngine engine, IntFunction pageAllocator, boolean isClientMode) { @@ -76,26 +73,31 @@ public SSLDriver(SSLEngine engine, IntFunction pageAllocator, boolean isCl public void init() throws SSLException { engine.setUseClientMode(isClientMode); - if (currentMode.isHandshake()) { - engine.beginHandshake(); - ((HandshakeMode) currentMode).startHandshake(); - } else { + if (currentMode.isClose()) { throw new AssertionError("Attempted to init outside from non-handshaking mode: " + currentMode.modeName()); + } else { + engine.beginHandshake(); + ((RegularMode) currentMode).startHandshake(); + try { + ((RegularMode) currentMode).handshake(); + } catch (SSLException e) { + currentMode = new CloseMode(((RegularMode) currentMode).isHandshaking, false); + throw e; + } } } /** * Requests a TLS renegotiation. This means the we will request that the peer performs another handshake - * prior to the continued exchange of application data. This can only be requested if we are currently in - * APPLICATION mode. + * prior to the continued exchange of application data. This can only be requested if we are currently + * not closing. * * @throws SSLException if the handshake cannot be initiated */ public void renegotiate() throws SSLException { - if (currentMode.isApplication()) { - currentMode = new HandshakeMode(); + if (currentMode.isClose() == false) { engine.beginHandshake(); - ((HandshakeMode) currentMode).startHandshake(); + ((RegularMode) currentMode).startHandshake(); } else { throw new IllegalStateException("Attempted to renegotiate while in invalid mode: " + currentMode.modeName()); } @@ -105,10 +107,6 @@ public SSLEngine getSSLEngine() { return engine; } - public boolean isHandshaking() { - return currentMode.isHandshake(); - } - public SSLOutboundBuffer getOutboundBuffer() { return outboundBuffer; } @@ -116,43 +114,34 @@ public SSLOutboundBuffer getOutboundBuffer() { public void read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException { networkReadPage = pageAllocator.apply(packetSize); try { - Mode modePriorToRead; - do { - modePriorToRead = currentMode; - currentMode.read(encryptedBuffer, applicationBuffer); - // It is possible that we received multiple SSL packets from the network since the last read. - // If one of those packets causes us to change modes (such as finished handshaking), we need - // to call read in the new mode to handle the remaining packets. - } while (modePriorToRead != currentMode); + boolean continueUnwrap = true; + while (continueUnwrap && encryptedBuffer.getIndex() > 0) { + int bytesConsumed = currentMode.read(encryptedBuffer, applicationBuffer); + continueUnwrap = bytesConsumed > 0; + } } finally { networkReadPage.close(); networkReadPage = null; } } - public boolean readyForApplicationWrites() { - return currentMode.isApplication(); - } - - public boolean needsNonApplicationWrite() { - return currentMode.needsNonApplicationWrite(); + public boolean readyForApplicationData() { + return currentMode.readyForApplicationData(); } public int write(FlushOperation applicationBytes) throws SSLException { - return currentMode.write(applicationBytes); + int totalBytesProduced = 0; + boolean continueWrap = true; + while (continueWrap && applicationBytes.isFullyFlushed() == false) { + int bytesProduced = currentMode.write(applicationBytes); + totalBytesProduced += bytesProduced; + continueWrap = bytesProduced > 0; + } + return totalBytesProduced; } - public void nonApplicationWrite() throws SSLException { - assert currentMode.isApplication() == false : "Should not be called if driver is in application mode"; - if (currentMode.isApplication() == false) { - currentMode.write(EMPTY_FLUSH_OPERATION); - } else { - throw new AssertionError("Attempted to non-application write from invalid mode: " + currentMode.modeName()); - } - } - - public void initiateClose() { - closingInternal(); + public void initiateClose() throws SSLException { + internalClose(); } public boolean isClosed() { @@ -163,7 +152,9 @@ public boolean isClosed() { public void close() throws SSLException { outboundBuffer.close(); ArrayList closingExceptions = new ArrayList<>(2); - closingInternal(); + if (currentMode.isClose() == false) { + currentMode = new CloseMode(((RegularMode) currentMode).isHandshaking, false); + } CloseMode closeMode = (CloseMode) this.currentMode; if (closeMode.needToSendClose) { closingExceptions.add(new SSLException("Closed engine without completely sending the close alert message.")); @@ -172,8 +163,8 @@ public void close() throws SSLException { if (closeMode.needToReceiveClose) { closingExceptions.add(new SSLException("Closed engine without receiving the close alert message.")); - closeMode.closeInboundAndSwallowPeerDidNotCloseException(); } + closeMode.closeInboundAndSwallowPeerDidNotCloseException(); ExceptionsHelper.rethrowAndSuppress(closingExceptions); } @@ -208,7 +199,7 @@ private SSLEngineResult unwrap(InboundChannelBuffer networkBuffer, InboundChanne break; case CLOSED: assert engine.isInboundDone() : "We received close_notify so read should be done"; - closingInternal(); + internalClose(); return result; default: throw new IllegalStateException("Unexpected UNWRAP result: " + result.getStatus()); @@ -236,6 +227,7 @@ private SSLEngineResult wrap(SSLOutboundBuffer outboundBuffer, FlushOperation ap applicationBytes.incrementIndex(result.bytesConsumed()); switch (result.getStatus()) { case OK: + case CLOSED: return result; case BUFFER_UNDERFLOW: throw new IllegalStateException("Should not receive BUFFER_UNDERFLOW on WRAP"); @@ -244,19 +236,16 @@ private SSLEngineResult wrap(SSLOutboundBuffer outboundBuffer, FlushOperation ap // There is not enough space in the network buffer for an entire SSL packet. We will // allocate a buffer with the correct packet size the next time through the loop. break; - case CLOSED: - assert result.bytesProduced() > 0 : "WRAP during close processing should produce close message."; - return result; default: throw new IllegalStateException("Unexpected WRAP result: " + result.getStatus()); } } } - private void closingInternal() { + private void internalClose() throws SSLException { // This check prevents us from attempting to send close_notify twice if (currentMode.isClose() == false) { - currentMode = new CloseMode(currentMode.isHandshake()); + currentMode = new CloseMode(((RegularMode) currentMode).isHandshaking); } } @@ -267,55 +256,42 @@ private void ensureApplicationBufferSize(InboundChannelBuffer applicationBuffer) } } - // There are three potential modes for the driver to be in - HANDSHAKE, APPLICATION, or CLOSE. HANDSHAKE - // is the initial mode. During this mode data that is read and written will be related to the TLS - // handshake process. Application related data cannot be encrypted until the handshake is complete. From - // HANDSHAKE mode the driver can transition to APPLICATION (if the handshake is successful) or CLOSE (if - // an error occurs or we initiate a close). In APPLICATION mode data read from the channel will be - // decrypted and placed into the buffer passed as an argument to the read call. Additionally, application - // writes will be accepted and encrypted into the outbound write buffer. APPLICATION mode will proceed - // until we receive a request for renegotiation (currently unsupported) or the CLOSE mode begins. CLOSE - // mode can begin if we receive a CLOSE_NOTIFY message from the peer or if initiateClose is called. In - // CLOSE mode we attempt to both send and receive an SSL CLOSE_NOTIFY message. The exception to this is - // when we enter CLOSE mode from HANDSHAKE mode. In this scenario we only need to send the alert to the - // peer and then close the channel. Some SSL/TLS implementations do not properly adhere to the full - // two-direction close_notify process. Additionally, in newer TLS specifications it is not required to - // wait to receive close_notify. However, we will make our best attempt to both send and receive as it is - // expected by the java SSLEngine (it throws an exception if close_notify has not been received when - // inbound is closed). + // There are two potential modes for the driver to be in - REGULAR or CLOSE. REGULAR is the initial mode. + // During this mode the initial data that is read and written will be related to the TLS handshake + // process. Application related data cannot be encrypted until the handshake is complete. Once the + // handshake is complete data read from the channel will be decrypted and placed into the buffer passed + // as an argument to the read call. Additionally, application writes will be accepted and encrypted into + // the outbound write buffer. REGULAR mode will proceed until CLOSE mode begins. CLOSE mode can begin if + // we receive a CLOSE_NOTIFY message from the peer or if initiateClose is called. In CLOSE mode we attempt + // to both send and receive an SSL CLOSE_NOTIFY message. The exception to this is when we enter CLOSE mode + // during a handshake. In this scenario we only need to send the alert to the peer and then close the + // channel. Some SSL/TLS implementations do not properly adhere to the full two-direction close_notify + // process. Additionally, in newer TLS specifications it is not required to wait to receive close_notify. + // However, we will make our best attempt to both send and receive as it is expected by the java SSLEngine + // (it throws an exception if close_notify has not been received when inbound is closed). private interface Mode { - void read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException; + int read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException; int write(FlushOperation applicationBytes) throws SSLException; - boolean needsNonApplicationWrite(); - - boolean isHandshake(); - - boolean isApplication(); - boolean isClose(); + boolean readyForApplicationData(); + String modeName(); } - private class HandshakeMode implements Mode { + private class RegularMode implements Mode { private SSLEngineResult.HandshakeStatus handshakeStatus; + private boolean isHandshaking = false; - private void startHandshake() throws SSLException { + private void startHandshake() { handshakeStatus = engine.getHandshakeStatus(); - if (handshakeStatus != SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { - try { - handshake(); - } catch (SSLException e) { - closingInternal(); - throw e; - } - } + isHandshaking = true; } private void handshake() throws SSLException { @@ -323,22 +299,22 @@ private void handshake() throws SSLException { while (continueHandshaking) { switch (handshakeStatus) { case NEED_UNWRAP: + isHandshaking = true; // We UNWRAP as much as possible immediately after a read. Do not need to do it here. continueHandshaking = false; break; case NEED_WRAP: + isHandshaking = true; handshakeStatus = wrap(outboundBuffer).getHandshakeStatus(); break; case NEED_TASK: runTasks(); + isHandshaking = true; handshakeStatus = engine.getHandshakeStatus(); break; case NOT_HANDSHAKING: - maybeFinishHandshake(); - continueHandshaking = false; - break; case FINISHED: - maybeFinishHandshake(); + isHandshaking = false; continueHandshaking = false; break; } @@ -346,58 +322,45 @@ private void handshake() throws SSLException { } @Override - public void read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException { - boolean continueUnwrap = true; - while (continueUnwrap && encryptedBuffer.getIndex() > 0) { - try { - SSLEngineResult result = unwrap(encryptedBuffer, applicationBuffer); - handshakeStatus = result.getHandshakeStatus(); + public int read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException { + try { + SSLEngineResult result = unwrap(encryptedBuffer, applicationBuffer); + handshakeStatus = result.getHandshakeStatus(); + if (result.getStatus() != SSLEngineResult.Status.CLOSED) { handshake(); - // If we are done handshaking we should exit the handshake read - continueUnwrap = result.bytesConsumed() > 0 && currentMode.isHandshake(); - } catch (SSLException e) { - closingInternal(); - throw e; } - } - } - - @Override - public int write(FlushOperation applicationBytes) throws SSLException { - try { - handshake(); + return result.bytesConsumed(); } catch (SSLException e) { - closingInternal(); + handshakeStatus = engine.getHandshakeStatus(); + try { + internalClose(); + } catch (SSLException closeException) { + e.addSuppressed(closeException); + } throw e; } - return 0; - } - - @Override - public boolean needsNonApplicationWrite() { - return handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP - || handshakeStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING - || handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED; } @Override - public boolean isHandshake() { - return true; + public int write(FlushOperation applicationBytes) throws SSLException { + SSLEngineResult result = wrap(outboundBuffer, applicationBytes); + handshakeStatus = result.getHandshakeStatus(); + return result.bytesProduced(); } @Override - public boolean isApplication() { + public boolean isClose() { return false; } @Override - public boolean isClose() { - return false; + public boolean readyForApplicationData() { + return isHandshaking == false; } @Override public String modeName() { - return "HANDSHAKE"; + return "REGULAR"; } private void runTasks() { @@ -406,89 +369,6 @@ private void runTasks() { delegatedTask.run(); } } - - private void maybeFinishHandshake() { - if (engine.isOutboundDone() || engine.isInboundDone()) { - // If the engine is partially closed, immediate transition to close mode. - if (currentMode.isHandshake()) { - currentMode = new CloseMode(true); - } else if (currentMode.isApplication()) { - // It is possible to be in CLOSED mode if the prior UNWRAP call returned CLOSE_NOTIFY. - // However we should not be in application mode at this point. - String message = "Expected to be in handshaking/closed mode. Instead in application mode."; - throw new AssertionError(message); - } - } else { - if (currentMode.isHandshake()) { - currentMode = new ApplicationMode(); - } else { - String message = "Attempted to transition to application mode from non-handshaking mode: " + currentMode; - throw new AssertionError(message); - } - } - } - } - - private class ApplicationMode implements Mode { - - @Override - public void read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException { - boolean continueUnwrap = true; - while (continueUnwrap && encryptedBuffer.getIndex() > 0) { - SSLEngineResult result = unwrap(encryptedBuffer, applicationBuffer); - boolean renegotiationRequested = result.getStatus() != SSLEngineResult.Status.CLOSED - && maybeRenegotiation(result.getHandshakeStatus()); - continueUnwrap = result.bytesProduced() > 0 && renegotiationRequested == false; - } - } - - @Override - public int write(FlushOperation applicationBytes) throws SSLException { - boolean continueWrap = true; - int totalBytesProduced = 0; - while (continueWrap && applicationBytes.isFullyFlushed() == false) { - SSLEngineResult result = wrap(outboundBuffer, applicationBytes); - int bytesProduced = result.bytesProduced(); - totalBytesProduced += bytesProduced; - boolean renegotiationRequested = maybeRenegotiation(result.getHandshakeStatus()); - continueWrap = bytesProduced > 0 && renegotiationRequested == false; - } - return totalBytesProduced; - } - - private boolean maybeRenegotiation(SSLEngineResult.HandshakeStatus newStatus) throws SSLException { - if (newStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING && newStatus != SSLEngineResult.HandshakeStatus.FINISHED) { - renegotiate(); - return true; - } else { - return false; - } - } - - @Override - public boolean needsNonApplicationWrite() { - return false; - } - - @Override - public boolean isHandshake() { - return false; - } - - @Override - public boolean isApplication() { - return true; - } - - @Override - public boolean isClose() { - return false; - } - - @Override - public String modeName() { - return "APPLICATION"; - } } private class CloseMode implements Mode { @@ -496,7 +376,11 @@ private class CloseMode implements Mode { private boolean needToSendClose = true; private boolean needToReceiveClose = true; - private CloseMode(boolean isHandshaking) { + private CloseMode(boolean isHandshaking) throws SSLException { + this(isHandshaking, true); + } + + private CloseMode(boolean isHandshaking, boolean tryToWrap) throws SSLException { if (isHandshaking && engine.isInboundDone() == false) { // If we attempt to close during a handshake either we are sending an alert and inbound // should already be closed or we are sending a close_notify. If we send a close_notify @@ -511,74 +395,61 @@ private CloseMode(boolean isHandshaking) { needToSendClose = false; } else { engine.closeOutbound(); + if (tryToWrap) { + try { + boolean continueWrap = true; + while (continueWrap) { + continueWrap = wrap(outboundBuffer).getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + } finally { + needToSendClose = false; + } + } } } @Override - public void read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException { + public int read(InboundChannelBuffer encryptedBuffer, InboundChannelBuffer applicationBuffer) throws SSLException { if (needToReceiveClose == false) { // There is an issue where receiving handshake messages after initiating the close process // can place the SSLEngine back into handshaking mode. In order to handle this, if we // initiate close during a handshake we do not wait to receive close. As we do not need to // receive close, we will not handle reads. - return; + return 0; } - boolean continueUnwrap = true; - while (continueUnwrap && encryptedBuffer.getIndex() > 0) { - SSLEngineResult result = unwrap(encryptedBuffer, applicationBuffer); - continueUnwrap = result.bytesProduced() > 0 || result.bytesConsumed() > 0; - } + SSLEngineResult result = unwrap(encryptedBuffer, applicationBuffer); if (engine.isInboundDone()) { needToReceiveClose = false; } - } - @Override - public int write(FlushOperation applicationBytes) throws SSLException { - int bytesProduced = 0; - if (engine.isOutboundDone() == false) { - bytesProduced += wrap(outboundBuffer).bytesProduced(); - if (engine.isOutboundDone()) { - needToSendClose = false; - // Close inbound if it is still open and we have decided not to wait for response. - if (needToReceiveClose == false && engine.isInboundDone() == false) { - closeInboundAndSwallowPeerDidNotCloseException(); - } - } - } else { - needToSendClose = false; - } - return bytesProduced; + return result.bytesConsumed(); } @Override - public boolean needsNonApplicationWrite() { - return needToSendClose; + public int write(FlushOperation applicationBytes) { + return 0; } @Override - public boolean isHandshake() { - return false; + public boolean isClose() { + return true; } @Override - public boolean isApplication() { + public boolean readyForApplicationData() { return false; } - @Override - public boolean isClose() { - return true; - } - @Override public String modeName() { return "CLOSE"; } private boolean isCloseDone() { - return needToSendClose == false && needToReceiveClose == false; + // We do as much as possible to generate the outbound messages in the ctor. At this point, we are + // only interested in interrogating if we need to wait to receive the close message. + return needToReceiveClose == false; } private void closeInboundAndSwallowPeerDidNotCloseException() throws SSLException { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLOutboundBuffer.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLOutboundBuffer.java index 2cd28f7d7dc32..56262a51cf056 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLOutboundBuffer.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLOutboundBuffer.java @@ -11,6 +11,7 @@ import java.nio.ByteBuffer; import java.util.ArrayDeque; +import java.util.function.BiConsumer; import java.util.function.IntFunction; public class SSLOutboundBuffer implements AutoCloseable { @@ -29,6 +30,8 @@ void incrementEncryptedBytes(int encryptedBytesProduced) { if (encryptedBytesProduced != 0) { currentPage.byteBuffer().limit(encryptedBytesProduced); pages.addLast(currentPage); + } else if (currentPage != null) { + currentPage.close(); } currentPage = null; } @@ -45,6 +48,10 @@ ByteBuffer nextWriteBuffer(int networkBufferSize) { } FlushOperation buildNetworkFlushOperation() { + return buildNetworkFlushOperation((r, e) -> {}); + } + + FlushOperation buildNetworkFlushOperation(BiConsumer listener) { int pageCount = pages.size(); ByteBuffer[] byteBuffers = new ByteBuffer[pageCount]; Page[] pagesToClose = new Page[pageCount]; @@ -54,7 +61,10 @@ FlushOperation buildNetworkFlushOperation() { byteBuffers[i] = page.byteBuffer(); } - return new FlushOperation(byteBuffers, (r, e) -> IOUtils.closeWhileHandlingException(pagesToClose)); + return new FlushOperation(byteBuffers, (r, e) -> { + IOUtils.closeWhileHandlingException(pagesToClose); + listener.accept(r, e); + }); } boolean hasEncryptedBytesToFlush() { @@ -63,6 +73,7 @@ boolean hasEncryptedBytesToFlush() { @Override public void close() { + IOUtils.closeWhileHandlingException(currentPage); IOUtils.closeWhileHandlingException(pages); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContextTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContextTests.java index 6a380a8fab205..ad91f05868226 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContextTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContextTests.java @@ -21,6 +21,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; +import javax.net.ssl.SSLException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Selector; @@ -186,8 +187,7 @@ public void testSSLDriverClosedOnClose() throws IOException { } public void testQueuedWritesAreIgnoredWhenNotReadyForAppWrites() { - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - when(sslDriver.needsNonApplicationWrite()).thenReturn(false); + when(sslDriver.readyForApplicationData()).thenReturn(false); context.queueWriteOperation(mock(FlushReadyWrite.class)); @@ -195,34 +195,20 @@ public void testQueuedWritesAreIgnoredWhenNotReadyForAppWrites() { } public void testPendingEncryptedFlushMeansWriteInterested() throws Exception { - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - when(sslDriver.needsNonApplicationWrite()).thenReturn(true, false); - doAnswer(getWriteAnswer(1, false)).when(sslDriver).nonApplicationWrite(); + context.queueWriteOperation(mock(FlushReadyWrite.class)); + when(sslDriver.readyForApplicationData()).thenReturn(true); + doAnswer(getWriteAnswer(1, false)).when(sslDriver).write(any(FlushOperation.class)); // Call will put bytes in buffer to flush context.flushChannel(); assertTrue(context.readyForFlush()); } - public void testNeedsNonAppWritesMeansWriteInterested() { - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - when(sslDriver.needsNonApplicationWrite()).thenReturn(true); - - assertTrue(context.readyForFlush()); - } - - public void testNoNonAppWriteInterestInAppMode() { - when(sslDriver.readyForApplicationWrites()).thenReturn(true); - - assertFalse(context.readyForFlush()); - - verify(sslDriver, times(0)).needsNonApplicationWrite(); - } - public void testFirstFlushMustFinishForWriteToContinue() throws Exception { - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - when(sslDriver.needsNonApplicationWrite()).thenReturn(true); - doAnswer(getWriteAnswer(1, false)).when(sslDriver).nonApplicationWrite(); + context.queueWriteOperation(mock(FlushReadyWrite.class)); + context.queueWriteOperation(mock(FlushReadyWrite.class)); + when(sslDriver.readyForApplicationData()).thenReturn(true); + doAnswer(getWriteAnswer(1, false)).when(sslDriver).write(any(FlushOperation.class)); // First call will put bytes in buffer to flush context.flushChannel(); @@ -231,31 +217,7 @@ public void testFirstFlushMustFinishForWriteToContinue() throws Exception { context.flushChannel(); assertTrue(context.readyForFlush()); - verify(sslDriver, times(1)).nonApplicationWrite(); - } - - public void testNonAppWrites() throws Exception { - when(sslDriver.needsNonApplicationWrite()).thenReturn(true, true, false); - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - doAnswer(getWriteAnswer(1, false)).when(sslDriver).nonApplicationWrite(); - when(rawChannel.write(same(selector.getIoBuffer()))).thenReturn(1); - - context.flushChannel(); - - verify(sslDriver, times(2)).nonApplicationWrite(); - verify(rawChannel, times(2)).write(same(selector.getIoBuffer())); - } - - public void testNonAppWritesStopIfBufferNotFullyFlushed() throws Exception { - when(sslDriver.needsNonApplicationWrite()).thenReturn(true); - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - doAnswer(getWriteAnswer(1, false)).when(sslDriver).nonApplicationWrite(); - when(rawChannel.write(same(selector.getIoBuffer()))).thenReturn(0); - - context.flushChannel(); - - verify(sslDriver, times(1)).nonApplicationWrite(); - verify(rawChannel, times(1)).write(same(selector.getIoBuffer())); + verify(sslDriver, times(1)).write(any(FlushOperation.class)); } public void testQueuedWriteIsFlushedInFlushCall() throws Exception { @@ -263,7 +225,7 @@ public void testQueuedWriteIsFlushedInFlushCall() throws Exception { FlushReadyWrite flushOperation = new FlushReadyWrite(context, buffers, listener); context.queueWriteOperation(flushOperation); - when(sslDriver.readyForApplicationWrites()).thenReturn(true); + when(sslDriver.readyForApplicationData()).thenReturn(true); doAnswer(getWriteAnswer(10, true)).when(sslDriver).write(eq(flushOperation)); when(rawChannel.write(same(selector.getIoBuffer()))).thenReturn(10); @@ -279,7 +241,7 @@ public void testPartialFlush() throws IOException { FlushReadyWrite flushOperation = new FlushReadyWrite(context, buffers, listener); context.queueWriteOperation(flushOperation); - when(sslDriver.readyForApplicationWrites()).thenReturn(true); + when(sslDriver.readyForApplicationData()).thenReturn(true); doAnswer(getWriteAnswer(5, true)).when(sslDriver).write(eq(flushOperation)); when(rawChannel.write(same(selector.getIoBuffer()))).thenReturn(4); context.flushChannel(); @@ -299,7 +261,7 @@ public void testMultipleWritesPartialFlushes() throws IOException { context.queueWriteOperation(flushOperation1); context.queueWriteOperation(flushOperation2); - when(sslDriver.readyForApplicationWrites()).thenReturn(true); + when(sslDriver.readyForApplicationData()).thenReturn(true); doAnswer(getWriteAnswer(5, true)).when(sslDriver).write(any(FlushOperation.class)); when(rawChannel.write(same(selector.getIoBuffer()))).thenReturn(5, 5, 2); context.flushChannel(); @@ -316,7 +278,7 @@ public void testWhenIOExceptionThrownListenerIsCalled() throws IOException { context.queueWriteOperation(flushOperation); IOException exception = new IOException(); - when(sslDriver.readyForApplicationWrites()).thenReturn(true); + when(sslDriver.readyForApplicationData()).thenReturn(true); doAnswer(getWriteAnswer(5, true)).when(sslDriver).write(eq(flushOperation)); when(rawChannel.write(any(ByteBuffer.class))).thenThrow(exception); expectThrows(IOException.class, () -> context.flushChannel()); @@ -326,9 +288,9 @@ public void testWhenIOExceptionThrownListenerIsCalled() throws IOException { } public void testWriteIOExceptionMeansChannelReadyToClose() throws Exception { - when(sslDriver.readyForApplicationWrites()).thenReturn(false); - when(sslDriver.needsNonApplicationWrite()).thenReturn(true); - doAnswer(getWriteAnswer(1, false)).when(sslDriver).nonApplicationWrite(); + context.queueWriteOperation(mock(FlushReadyWrite.class)); + when(sslDriver.readyForApplicationData()).thenReturn(true); + doAnswer(getWriteAnswer(1, false)).when(sslDriver).write(any(FlushOperation.class)); context.flushChannel(); @@ -383,7 +345,7 @@ public void testCloseTimeoutIsCancelledOnClose() throws IOException { } } - public void testInitiateCloseFromDifferentThreadSchedulesCloseNotify() { + public void testInitiateCloseFromDifferentThreadSchedulesCloseNotify() throws SSLException { when(selector.isOnCurrentThread()).thenReturn(false, true); context.closeChannel(); @@ -394,7 +356,7 @@ public void testInitiateCloseFromDifferentThreadSchedulesCloseNotify() { verify(sslDriver).initiateClose(); } - public void testInitiateCloseFromSameThreadSchedulesCloseNotify() { + public void testInitiateCloseFromSameThreadSchedulesCloseNotify() throws SSLException { context.closeChannel(); ArgumentCaptor captor = ArgumentCaptor.forClass(WriteOperation.class); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java index 5010f34a6b4be..ab033caedc5ea 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/SSLDriverTests.java @@ -57,7 +57,6 @@ public void testPingPongAndClose() throws Exception { assertEquals(ByteBuffer.wrap("pong".getBytes(StandardCharsets.UTF_8)), applicationBuffer.sliceBuffersTo(4)[0]); applicationBuffer.release(4); - assertFalse(clientDriver.needsNonApplicationWrite()); normalClose(clientDriver, serverDriver); } @@ -82,6 +81,7 @@ public void testRenegotiate() throws Exception { SSLEngine serverEngine = sslContext.createSSLEngine(); SSLEngine clientEngine = sslContext.createSSLEngine(); + // Lock the protocol to 1.2 as 1.3 does not support renegotiation String[] serverProtocols = {"TLSv1.2"}; serverEngine.setEnabledProtocols(serverProtocols); String[] clientProtocols = {"TLSv1.2"}; @@ -98,8 +98,7 @@ public void testRenegotiate() throws Exception { applicationBuffer.release(4); clientDriver.renegotiate(); - assertTrue(clientDriver.isHandshaking()); - assertFalse(clientDriver.readyForApplicationWrites()); + assertFalse(clientDriver.readyForApplicationData()); // This tests that the client driver can still receive data based on the prior handshake ByteBuffer[] buffers2 = {ByteBuffer.wrap("pong".getBytes(StandardCharsets.UTF_8))}; @@ -148,7 +147,6 @@ public void testBigApplicationData() throws Exception { assertEquals(ByteBuffer.wrap("pong".getBytes(StandardCharsets.UTF_8)), applicationBuffer.sliceBuffersTo(4)[0]); applicationBuffer.release(4); - assertFalse(clientDriver.needsNonApplicationWrite()); normalClose(clientDriver, serverDriver); } @@ -156,6 +154,7 @@ public void testHandshakeFailureBecauseProtocolMismatch() throws Exception { SSLContext sslContext = getSSLContext(); SSLEngine clientEngine = sslContext.createSSLEngine(); SSLEngine serverEngine = sslContext.createSSLEngine(); + String[] serverProtocols = {"TLSv1.2"}; serverEngine.setEnabledProtocols(serverProtocols); String[] clientProtocols = {"TLSv1.1"}; @@ -209,26 +208,31 @@ public void testCloseDuringHandshakeJDK11() throws Exception { serverDriver.init(); assertTrue(clientDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); - assertFalse(serverDriver.needsNonApplicationWrite()); sendHandshakeMessages(clientDriver, serverDriver); - sendHandshakeMessages(serverDriver, clientDriver); - assertTrue(clientDriver.isHandshaking()); - assertTrue(serverDriver.isHandshaking()); + // Sometimes send server messages before closing + if (randomBoolean()) { + sendHandshakeMessages(serverDriver, clientDriver); + + if ("TLSv1.3".equals(clientDriver.getSSLEngine().getEnabledProtocols()[0])) { + assertTrue(clientDriver.readyForApplicationData()); + } else { + assertFalse(clientDriver.readyForApplicationData()); + } + } + + assertFalse(serverDriver.readyForApplicationData()); - assertFalse(serverDriver.needsNonApplicationWrite()); serverDriver.initiateClose(); - assertTrue(serverDriver.needsNonApplicationWrite()); - assertFalse(serverDriver.isClosed()); - sendNonApplicationWrites(serverDriver); + assertTrue(serverDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); // We are immediately fully closed due to SSLEngine inconsistency assertTrue(serverDriver.isClosed()); - SSLException sslException = expectThrows(SSLException.class, () -> clientDriver.read(networkReadBuffer, applicationBuffer)); - assertEquals("Received close_notify during handshake", sslException.getMessage()); - sendNonApplicationWrites(clientDriver); + sendNonApplicationWrites(serverDriver); + clientDriver.read(networkReadBuffer, applicationBuffer); assertTrue(clientDriver.isClosed()); + sendNonApplicationWrites(clientDriver); serverDriver.read(networkReadBuffer, applicationBuffer); } @@ -241,17 +245,15 @@ public void testCloseDuringHandshakePreJDK11() throws Exception { clientDriver.init(); serverDriver.init(); - assertTrue(clientDriver.needsNonApplicationWrite()); - assertFalse(serverDriver.needsNonApplicationWrite()); + assertTrue(clientDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); sendHandshakeMessages(clientDriver, serverDriver); sendHandshakeMessages(serverDriver, clientDriver); - assertTrue(clientDriver.isHandshaking()); - assertTrue(serverDriver.isHandshaking()); + assertFalse(clientDriver.readyForApplicationData()); + assertFalse(serverDriver.readyForApplicationData()); - assertFalse(serverDriver.needsNonApplicationWrite()); serverDriver.initiateClose(); - assertTrue(serverDriver.needsNonApplicationWrite()); + assertTrue(serverDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); assertFalse(serverDriver.isClosed()); sendNonApplicationWrites(serverDriver); // We are immediately fully closed due to SSLEngine inconsistency @@ -261,14 +263,14 @@ public void testCloseDuringHandshakePreJDK11() throws Exception { sendNonApplicationWrites(clientDriver); SSLException sslException = expectThrows(SSLException.class, () -> clientDriver.read(networkReadBuffer, applicationBuffer)); assertEquals("Received close_notify during handshake", sslException.getMessage()); - assertTrue(clientDriver.needsNonApplicationWrite()); + assertTrue(clientDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); sendNonApplicationWrites(clientDriver); serverDriver.read(networkReadBuffer, applicationBuffer); assertTrue(clientDriver.isClosed()); } private void failedCloseAlert(SSLDriver sendDriver, SSLDriver receiveDriver, List messages) throws SSLException { - assertTrue(sendDriver.needsNonApplicationWrite()); + assertTrue(sendDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); assertFalse(sendDriver.isClosed()); sendNonApplicationWrites(sendDriver); @@ -278,13 +280,8 @@ private void failedCloseAlert(SSLDriver sendDriver, SSLDriver receiveDriver, Lis SSLException sslException = expectThrows(SSLException.class, () -> receiveDriver.read(networkReadBuffer, applicationBuffer)); assertTrue("Expected one of the following exception messages: " + messages + ". Found: " + sslException.getMessage(), messages.stream().anyMatch(m -> sslException.getMessage().equals(m))); - if (receiveDriver.needsNonApplicationWrite() == false) { - assertTrue(receiveDriver.isClosed()); - receiveDriver.close(); - } else { - assertFalse(receiveDriver.isClosed()); - expectThrows(SSLException.class, receiveDriver::close); - } + assertTrue(receiveDriver.isClosed()); + receiveDriver.close(); } private SSLContext getSSLContext() throws Exception { @@ -295,25 +292,23 @@ private SSLContext getSSLContext() throws Exception { (certPath)))); KeyManager km = CertParsingUtils.keyManager(CertParsingUtils.readCertificates(Collections.singletonList(getDataPath (certPath))), PemUtils.readPrivateKey(getDataPath(keyPath), "testclient"::toCharArray), "testclient".toCharArray()); - sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext = SSLContext.getInstance(randomFrom("TLSv1.2", "TLSv1.3")); sslContext.init(new KeyManager[] { km }, new TrustManager[] { tm }, new SecureRandom()); return sslContext; } private void normalClose(SSLDriver sendDriver, SSLDriver receiveDriver) throws IOException { sendDriver.initiateClose(); - assertFalse(sendDriver.readyForApplicationWrites()); - assertTrue(sendDriver.needsNonApplicationWrite()); + assertFalse(sendDriver.readyForApplicationData()); + assertTrue(sendDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); sendNonApplicationWrites(sendDriver); assertFalse(sendDriver.isClosed()); receiveDriver.read(networkReadBuffer, applicationBuffer); - assertFalse(receiveDriver.isClosed()); + assertTrue(receiveDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); + assertTrue(receiveDriver.isClosed()); - assertFalse(receiveDriver.readyForApplicationWrites()); - assertTrue(receiveDriver.needsNonApplicationWrite()); sendNonApplicationWrites(receiveDriver); - assertTrue(receiveDriver.isClosed()); sendDriver.read(networkReadBuffer, applicationBuffer); assertTrue(sendDriver.isClosed()); @@ -323,15 +318,9 @@ private void normalClose(SSLDriver sendDriver, SSLDriver receiveDriver) throws I assertEquals(0, openPages.get()); } - private void sendNonApplicationWrites(SSLDriver sendDriver) throws SSLException { + private void sendNonApplicationWrites(SSLDriver sendDriver) { SSLOutboundBuffer outboundBuffer = sendDriver.getOutboundBuffer(); - while (sendDriver.needsNonApplicationWrite() || outboundBuffer.hasEncryptedBytesToFlush()) { - if (outboundBuffer.hasEncryptedBytesToFlush()) { - sendData(outboundBuffer.buildNetworkFlushOperation()); - } else { - sendDriver.nonApplicationWrite(); - } - } + sendData(outboundBuffer.buildNetworkFlushOperation()); } private void handshake(SSLDriver clientDriver, SSLDriver serverDriver) throws IOException { @@ -345,48 +334,50 @@ private void handshake(SSLDriver clientDriver, SSLDriver serverDriver, boolean i } assertTrue(clientDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); - assertFalse(serverDriver.needsNonApplicationWrite()); sendHandshakeMessages(clientDriver, serverDriver); - assertTrue(clientDriver.isHandshaking()); - assertTrue(serverDriver.isHandshaking()); + assertFalse(clientDriver.readyForApplicationData()); + assertFalse(serverDriver.readyForApplicationData()); sendHandshakeMessages(serverDriver, clientDriver); - assertTrue(clientDriver.isHandshaking()); - assertTrue(serverDriver.isHandshaking()); + if ("TLSv1.3".equals(clientDriver.getSSLEngine().getEnabledProtocols()[0])) { + assertTrue(clientDriver.readyForApplicationData()); + assertFalse(serverDriver.readyForApplicationData()); - sendHandshakeMessages(clientDriver, serverDriver); + sendHandshakeMessages(clientDriver, serverDriver); - assertTrue(clientDriver.isHandshaking()); + assertTrue(clientDriver.readyForApplicationData()); + assertTrue(serverDriver.readyForApplicationData()); + } else { + assertFalse(clientDriver.readyForApplicationData()); + assertFalse(serverDriver.readyForApplicationData()); + + sendHandshakeMessages(clientDriver, serverDriver); + + assertFalse(clientDriver.readyForApplicationData()); + assertTrue(serverDriver.readyForApplicationData()); + + sendHandshakeMessages(serverDriver, clientDriver); + + assertTrue(clientDriver.readyForApplicationData()); + assertTrue(serverDriver.readyForApplicationData()); + } - sendHandshakeMessages(serverDriver, clientDriver); - assertFalse(clientDriver.isHandshaking()); - assertFalse(serverDriver.isHandshaking()); } private void sendHandshakeMessages(SSLDriver sendDriver, SSLDriver receiveDriver) throws IOException { - assertTrue(sendDriver.needsNonApplicationWrite() || sendDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); + assertTrue(sendDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); - SSLOutboundBuffer outboundBuffer = sendDriver.getOutboundBuffer(); - - while (sendDriver.needsNonApplicationWrite() || outboundBuffer.hasEncryptedBytesToFlush()) { - if (outboundBuffer.hasEncryptedBytesToFlush()) { - sendData(outboundBuffer.buildNetworkFlushOperation()); - receiveDriver.read(networkReadBuffer, applicationBuffer); - } else { - sendDriver.nonApplicationWrite(); - } - } - if (receiveDriver.isHandshaking()) { - assertTrue(receiveDriver.needsNonApplicationWrite() || receiveDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); + sendData(sendDriver.getOutboundBuffer().buildNetworkFlushOperation()); + receiveDriver.read(networkReadBuffer, applicationBuffer); + if (receiveDriver.readyForApplicationData() == false) { + assertTrue(receiveDriver.getOutboundBuffer().hasEncryptedBytesToFlush()); } } private void sendAppData(SSLDriver sendDriver, ByteBuffer[] message) throws IOException { - assertFalse(sendDriver.needsNonApplicationWrite()); - FlushOperation flushOperation = new FlushOperation(message, (r, l) -> {}); while (flushOperation.isFullyFlushed() == false) {