OpenSslEngine option to wrap/unwrap multiple packets per call

Motivation:
The JDK SSLEngine documentation says that a call to wrap/unwrap "will attempt to consume one complete SSL/TLS network packet" [1]. This limitation can result in thrashing in the pipeline to decode and encode data that may be spread amongst multiple SSL/TLS network packets.
ReferenceCountedOpenSslEngine also does not correct account for the overhead introduced by each individual SSL_write call if there are multiple ByteBuffers passed to the wrap() method.

Modifications:
- OpenSslEngine and SslHandler supports a mode to not comply with the limitation to only deal with a single SSL/TLS network packet per call
- ReferenceCountedOpenSslEngine correctly accounts for the overhead of each call to SSL_write
- SslHandler shouldn't cache maxPacketBufferSize as aggressively because this value may change before/after the handshake.

Result:
OpenSslEngine and SslHanadler can handle multiple SSL/TLS network packet per call.

[1] https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html
This commit is contained in:
Scott Mitchell 2017-02-03 17:54:13 -08:00
parent df568c739e
commit f7b3caeddc
9 changed files with 748 additions and 251 deletions

View File

@ -45,8 +45,8 @@ public abstract class OpenSslContext extends ReferenceCountedOpenSslContext {
} }
@Override @Override
final SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort) { final SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode) {
return new OpenSslEngine(this, alloc, peerHost, peerPort); return new OpenSslEngine(this, alloc, peerHost, peerPort, jdkCompatibilityMode);
} }
@Override @Override

View File

@ -27,8 +27,9 @@ import javax.net.ssl.SSLEngine;
* and manually release the native memory see {@link ReferenceCountedOpenSslEngine}. * and manually release the native memory see {@link ReferenceCountedOpenSslEngine}.
*/ */
public final class OpenSslEngine extends ReferenceCountedOpenSslEngine { public final class OpenSslEngine extends ReferenceCountedOpenSslEngine {
OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort) { OpenSslEngine(OpenSslContext context, ByteBufAllocator alloc, String peerHost, int peerPort,
super(context, alloc, peerHost, peerPort, false); boolean jdkCompatibilityMode) {
super(context, alloc, peerHost, peerPort, jdkCompatibilityMode, false);
} }
@Override @Override

View File

@ -409,11 +409,21 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
@Override @Override
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) { public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
return newEngine0(alloc, peerHost, peerPort); return newEngine0(alloc, peerHost, peerPort, true);
} }
SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort) { @Override
return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, true); final SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
return new SslHandler(newEngine0(alloc, null, -1, false), startTls, false);
}
@Override
final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
return new SslHandler(newEngine0(alloc, peerHost, peerPort, false), startTls, false);
}
SSLEngine newEngine0(ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode) {
return new ReferenceCountedOpenSslEngine(this, alloc, peerHost, peerPort, jdkCompatibilityMode, true);
} }
abstract OpenSslKeyMaterialManager keyMaterialManager(); abstract OpenSslKeyMaterialManager keyMaterialManager();

View File

@ -43,8 +43,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@ -58,10 +58,10 @@ import javax.net.ssl.SSLSessionContext;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import static io.netty.handler.ssl.OpenSsl.memoryAddress; import static io.netty.handler.ssl.OpenSsl.memoryAddress;
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
import static io.netty.util.internal.EmptyArrays.EMPTY_CERTIFICATES; import static io.netty.util.internal.EmptyArrays.EMPTY_CERTIFICATES;
import static io.netty.util.internal.EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; import static io.netty.util.internal.EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
import static io.netty.util.internal.ObjectUtil.checkNotNull; import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static java.lang.Integer.MAX_VALUE;
import static java.lang.Math.min; import static java.lang.Math.min;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP; import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
@ -98,25 +98,10 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
*/ */
private static final int DEFAULT_HOSTNAME_VALIDATION_FLAGS = 0; private static final int DEFAULT_HOSTNAME_VALIDATION_FLAGS = 0;
static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
/** /**
* This is the maximum overhead when encrypting plaintext as defined by * Depends upon tcnative ... only use if tcnative is available!
* <a href="https://www.ietf.org/rfc/rfc5246.txt">rfc5264</a>,
* <a href="https://www.ietf.org/rfc/rfc5289.txt">rfc5289</a> and openssl implementation itself.
*
* Please note that we use a padding of 16 here as openssl uses PKC#5 which uses 16 bytes while the spec itself
* allow up to 255 bytes. 16 bytes is the max for PKC#5 (which handles it the same way as PKC#7) as we use a block
* size of 16. See <a href="https://tools.ietf.org/html/rfc5652#section-6.3">rfc5652#section-6.3</a>.
*
* TLS Header (5) + 16 (IV) + 48 (MAC) + 1 (Padding_length field) + 15 (Padding) + 1 (ContentType) +
* 2 (ProtocolVersion) + 2 (Length)
*
* TODO: We may need to review this calculation once TLS 1.3 becomes available.
*/ */
static final int MAX_TLS_RECORD_OVERHEAD_LENGTH = SSL_RECORD_HEADER_LENGTH + 16 + 48 + 1 + 15 + 1 + 2 + 2; static final int MAX_PLAINTEXT_LENGTH = SSL.SSL_MAX_PLAINTEXT_LENGTH;
static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_PLAINTEXT_LENGTH + MAX_TLS_RECORD_OVERHEAD_LENGTH;
private static final AtomicIntegerFieldUpdater<ReferenceCountedOpenSslEngine> DESTROYED_UPDATER = private static final AtomicIntegerFieldUpdater<ReferenceCountedOpenSslEngine> DESTROYED_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ReferenceCountedOpenSslEngine.class, "destroyed"); AtomicIntegerFieldUpdater.newUpdater(ReferenceCountedOpenSslEngine.class, "destroyed");
@ -146,7 +131,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
* Started via {@link #beginHandshake()}. * Started via {@link #beginHandshake()}.
*/ */
STARTED_EXPLICITLY, STARTED_EXPLICITLY,
/** /**
* Handshake is finished. * Handshake is finished.
*/ */
@ -198,6 +182,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
private boolean isInboundDone; private boolean isInboundDone;
private boolean outboundClosed; private boolean outboundClosed;
private final boolean jdkCompatibilityMode;
private final boolean clientMode; private final boolean clientMode;
private final ByteBufAllocator alloc; private final ByteBufAllocator alloc;
private final OpenSslEngineMap engineMap; private final OpenSslEngineMap engineMap;
@ -209,6 +194,8 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1];
private final OpenSslKeyMaterialManager keyMaterialManager; private final OpenSslKeyMaterialManager keyMaterialManager;
private final boolean enableOcsp; private final boolean enableOcsp;
private int maxWrapOverhead;
private int maxWrapBufferSize;
// This is package-private as we set it from OpenSslContext if an exception is thrown during // This is package-private as we set it from OpenSslContext if an exception is thrown during
// the verification step. // the verification step.
@ -220,10 +207,14 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
* @param alloc The allocator to use. * @param alloc The allocator to use.
* @param peerHost The peer host name. * @param peerHost The peer host name.
* @param peerPort The peer port. * @param peerPort The peer port.
* @param jdkCompatibilityMode {@code true} to behave like described in
* https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html.
* {@code false} allows for partial and/or multiple packets to be process in a single
* wrap or unwrap call.
* @param leakDetection {@code true} to enable leak detection of this object. * @param leakDetection {@code true} to enable leak detection of this object.
*/ */
ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, ByteBufAllocator alloc, String peerHost, ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, ByteBufAllocator alloc, String peerHost,
int peerPort, boolean leakDetection) { int peerPort, boolean jdkCompatibilityMode, boolean leakDetection) {
super(peerHost, peerPort); super(peerHost, peerPort);
OpenSsl.ensureAvailability(); OpenSsl.ensureAvailability();
leak = leakDetection ? leakDetector.track(this) : null; leak = leakDetection ? leakDetector.track(this) : null;
@ -236,7 +227,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
localCerts = context.keyCertChain; localCerts = context.keyCertChain;
keyMaterialManager = context.keyMaterialManager(); keyMaterialManager = context.keyMaterialManager();
enableOcsp = context.enableOcsp; enableOcsp = context.enableOcsp;
this.jdkCompatibilityMode = jdkCompatibilityMode;
Lock readerLock = context.ctxLock.readLock(); Lock readerLock = context.ctxLock.readLock();
readerLock.lock(); readerLock.lock();
try { try {
@ -244,6 +235,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} finally { } finally {
readerLock.unlock(); readerLock.unlock();
} }
ssl = SSL.newSSL(context.ctx, !context.isClient());
try { try {
networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize()); networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize());
@ -264,6 +256,13 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
if (enableOcsp) { if (enableOcsp) {
SSL.enableOcsp(ssl); SSL.enableOcsp(ssl);
} }
if (!jdkCompatibilityMode) {
SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE);
}
// setMode may impact the overhead.
calculateMaxWrapOverhead();
} catch (Throwable cause) { } catch (Throwable cause) {
SSL.freeSSL(ssl); SSL.freeSSL(ssl);
PlatformDependent.throwException(cause); PlatformDependent.throwException(cause);
@ -461,7 +460,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} }
} else { } else {
final int limit = dst.limit(); final int limit = dst.limit();
final int len = min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos); final int len = min(maxEncryptedPacketLength0(), limit - pos);
final ByteBuf buf = alloc.directBuffer(len); final ByteBuf buf = alloc.directBuffer(len);
try { try {
sslRead = SSL.readFromSSL(ssl, memoryAddress(buf), len); sslRead = SSL.readFromSSL(ssl, memoryAddress(buf), len);
@ -478,6 +477,65 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return sslRead; return sslRead;
} }
/**
* Visible only for testing!
*/
final synchronized int maxWrapOverhead() {
return maxWrapOverhead;
}
/**
* Visible only for testing!
*/
final synchronized int maxEncryptedPacketLength() {
return maxEncryptedPacketLength0();
}
/**
* This method is intentionally not synchronized, only use if you know you are in the EventLoop
* thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks.
*/
final int maxEncryptedPacketLength0() {
return maxWrapOverhead + MAX_PLAINTEXT_LENGTH;
}
/**
* This method is intentionally not synchronized, only use if you know you are in the EventLoop
* thread and visibility on {@link #maxWrapBufferSize} and {@link #maxWrapOverhead} is achieved
* via other synchronized blocks.
*/
final int calculateMaxLengthForWrap(int plaintextLength, int numComponents) {
return (int) min(maxWrapBufferSize, plaintextLength + (long) maxWrapOverhead * numComponents);
}
final synchronized int sslPending() {
return sslPending0();
}
/**
* It is assumed this method is called in a synchronized block (or the constructor)!
*/
private void calculateMaxWrapOverhead() {
maxWrapOverhead = SSL.getMaxWrapOverhead(ssl);
// maxWrapBufferSize must be set after maxWrapOverhead because there is a dependency on this value.
// If jdkCompatibility mode is off we allow enough space to encrypt 16 buffers at a time. This could be
// configurable in the future if necessary.
maxWrapBufferSize = jdkCompatibilityMode ? maxEncryptedPacketLength0() : maxEncryptedPacketLength0() << 4;
}
private int sslPending0() {
// OpenSSL has a limitation where if you call SSL_pending before the handshake is complete OpenSSL will throw a
// "called a function you should not call" error. Using the TLS_method instead of SSLv23_method may solve this
// issue but this API is only available in 1.1.0+ [1].
// [1] https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_new.html
return handshakeState != HandshakeState.FINISHED ? 0 : SSL.sslPending(ssl);
}
private boolean isBytesAvailableEnoughForWrap(int bytesAvailable, int plaintextLength, int numComponents) {
return bytesAvailable - (long) maxWrapOverhead * numComponents >= plaintextLength;
}
@Override @Override
public final SSLEngineResult wrap( public final SSLEngineResult wrap(
final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException { final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException {
@ -602,8 +660,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} }
} }
int srcsLen = 0;
final int endOffset = offset + length; final int endOffset = offset + length;
if (jdkCompatibilityMode) {
int srcsLen = 0;
for (int i = offset; i < endOffset; ++i) { for (int i = offset; i < endOffset; ++i) {
final ByteBuffer src = srcs[i]; final ByteBuffer src = srcs[i];
if (src == null) { if (src == null) {
@ -622,11 +681,12 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} }
} }
// we will only produce a single TLS packet, and we don't aggregate src buffers, // jdkCompatibilityMode will only produce a single TLS packet, and we don't aggregate src buffers,
// so we always fix the number of buffers to 1 when checking if the dst buffer is large enough. // so we always fix the number of buffers to 1 when checking if the dst buffer is large enough.
if (dst.remaining() < calculateOutNetBufSize(srcsLen, 1)) { if (!isBytesAvailableEnoughForWrap(dst.remaining(), srcsLen, 1)) {
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0);
} }
}
// There was no pending data in the network BIO -- encrypt any application data // There was no pending data in the network BIO -- encrypt any application data
int bytesConsumed = 0; int bytesConsumed = 0;
@ -639,8 +699,23 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
continue; continue;
} }
// Write plaintext application data to the SSL engine final int bytesWritten;
int bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed)); if (jdkCompatibilityMode) {
// Write plaintext application data to the SSL engine. We don't have to worry about checking
// if there is enough space if jdkCompatibilityMode because we only wrap at most
// MAX_PLAINTEXT_LENGTH and we loop over the input before hand and check if there is space.
bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed));
} else {
// OpenSSL's SSL_write keeps state between calls. We should make sure the amount we attempt to
// write is guaranteed to succeed so we don't have to worry about keeping state consistent
// between calls.
final int availableCapacityForWrap = dst.remaining() - bytesProduced - maxWrapOverhead;
if (availableCapacityForWrap <= 0) {
return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed,
bytesProduced);
}
bytesWritten = writePlaintextData(src, min(remaining, availableCapacityForWrap));
}
if (bytesWritten > 0) { if (bytesWritten > 0) {
bytesConsumed += bytesWritten; bytesConsumed += bytesWritten;
@ -650,7 +725,9 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
bytesProduced += bioLengthBefore - pendingNow; bytesProduced += bioLengthBefore - pendingNow;
bioLengthBefore = pendingNow; bioLengthBefore = pendingNow;
if (jdkCompatibilityMode || bytesProduced == dst.remaining()) {
return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced);
}
} else { } else {
int sslError = SSL.getError(ssl, bytesWritten); int sslError = SSL.getError(ssl, bytesWritten);
if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { if (sslError == SSL.SSL_ERROR_ZERO_RETURN) {
@ -686,10 +763,10 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// "When using a buffering BIO, like a BIO pair, data must be written into or retrieved // "When using a buffering BIO, like a BIO pair, data must be written into or retrieved
// out of the BIO before being able to continue." // out of the BIO before being able to continue."
// //
// So we attempt to drain the BIO buffer below, but if there is no data this condition // In practice this means the destination buffer doesn't have enough space for OpenSSL
// is undefined and we assume their is a fatal error with the openssl engine and close. // to write encrypted data to. This is an OVERFLOW condition.
// [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html
return newResult(NEED_WRAP, bytesConsumed, bytesProduced); return newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced);
} else { } else {
// Everything else is considered as error // Everything else is considered as error
throw shutdownWithError("SSL_write"); throw shutdownWithError("SSL_write");
@ -835,17 +912,23 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} }
} }
if (len < SSL_RECORD_HEADER_LENGTH) { int sslPending = sslPending0();
int packetLength;
// The JDK implies that only a single SSL packet should be processed per unwrap call [1]. If we are in
// JDK compatibility mode then we should honor this, but if not we just wrap as much as possible. If there
// are multiple records or partial records this may reduce thrashing events through the pipeline.
// [1] https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html
if (jdkCompatibilityMode) {
if (len < SslUtils.SSL_RECORD_HEADER_LENGTH) {
return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0);
} }
int packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset); packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset);
if (packetLength == SslUtils.NOT_ENCRYPTED) { if (packetLength == SslUtils.NOT_ENCRYPTED) {
throw new NotSslRecordException("not an SSL/TLS record"); throw new NotSslRecordException("not an SSL/TLS record");
} }
if (packetLength - SSL_RECORD_HEADER_LENGTH > capacity) { if (packetLength - SslUtils.SSL_RECORD_HEADER_LENGTH > capacity) {
// No enough space in the destination buffer so signal the caller // No enough space in the destination buffer so signal the caller
// that the buffer needs to be increased. // that the buffer needs to be increased.
return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0);
@ -856,6 +939,13 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// the whole packet. // the whole packet.
return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0);
} }
} else if (len == 0 && sslPending <= 0) {
return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0);
} else if (capacity == 0) {
return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0);
} else {
packetLength = (int) min(MAX_VALUE, len);
}
// This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW. // This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW.
assert srcsOffset < srcsEndOffset; assert srcsOffset < srcsEndOffset;
@ -867,24 +957,38 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
int bytesProduced = 0; int bytesProduced = 0;
int bytesConsumed = 0; int bytesConsumed = 0;
try { try {
for (; srcsOffset < srcsEndOffset; ++srcsOffset) { srcLoop:
for (;;) {
ByteBuffer src = srcs[srcsOffset]; ByteBuffer src = srcs[srcsOffset];
int remaining = src.remaining(); int remaining = src.remaining();
final ByteBuf bioWriteCopyBuf;
int pendingEncryptedBytes;
if (remaining == 0) { if (remaining == 0) {
if (sslPending <= 0) {
// We must skip empty buffers as BIO_write will return 0 if asked to write something // We must skip empty buffers as BIO_write will return 0 if asked to write something
// with length 0. // with length 0.
continue; if (++srcsOffset >= srcsEndOffset) {
break;
} }
continue;
} else {
bioWriteCopyBuf = null;
pendingEncryptedBytes = SSL.bioLengthByteBuffer(networkBIO);
}
} else {
// Write more encrypted data into the BIO. Ensure we only read one packet at a time as // Write more encrypted data into the BIO. Ensure we only read one packet at a time as
// stated in the SSLEngine javadocs. // stated in the SSLEngine javadocs.
int pendingEncryptedBytes = min(packetLength, remaining); pendingEncryptedBytes = min(packetLength, remaining);
ByteBuf bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes); bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes);
}
try { try {
readLoop: for (;;) {
for (; dstsOffset < dstsEndOffset; ++dstsOffset) {
ByteBuffer dst = dsts[dstsOffset]; ByteBuffer dst = dsts[dstsOffset];
if (!dst.hasRemaining()) { if (!dst.hasRemaining()) {
// No space left in the destination buffer, skip it. // No space left in the destination buffer, skip it.
if (++dstsOffset >= dstsEndOffset) {
break srcLoop;
}
continue; continue;
} }
@ -903,22 +1007,27 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
bytesProduced += bytesRead; bytesProduced += bytesRead;
if (!dst.hasRemaining()) { if (!dst.hasRemaining()) {
sslPending = sslPending0();
// Move to the next dst buffer as this one is full. // Move to the next dst buffer as this one is full.
continue; if (++dstsOffset >= dstsEndOffset) {
} return sslPending > 0 ?
if (packetLength == 0) { newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced) :
// We read everything return now. newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status,
return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status,
bytesConsumed, bytesProduced); bytesConsumed, bytesProduced);
} }
} else if (packetLength == 0) {
// We read everything return now.
break srcLoop;
} else if (jdkCompatibilityMode) {
// try to write again to the BIO. stop reading from it by break out of the readLoop. // try to write again to the BIO. stop reading from it by break out of the readLoop.
break; break;
}
} else { } else {
int sslError = SSL.getError(ssl, bytesRead); int sslError = SSL.getError(ssl, bytesRead);
if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) {
// break to the outer loop as we want to read more data which means we need to // break to the outer loop as we want to read more data which means we need to
// write more to the BIO. // write more to the BIO.
break readLoop; break;
} else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { } else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) {
// This means the connection was shutdown correctly, close inbound and outbound // This means the connection was shutdown correctly, close inbound and outbound
if (!receivedShutdown) { if (!receivedShutdown) {
@ -933,8 +1042,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
} }
} }
// Either we have no more dst buffers to put the data, or no more data to generate; we are done. if (++srcsOffset >= srcsEndOffset) {
if (dstsOffset >= dstsEndOffset || packetLength == 0) {
break; break;
} }
} finally { } finally {
@ -1054,7 +1162,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
public final Runnable getDelegatedTask() { public final Runnable getDelegatedTask() {
// Currently, we do not delegate SSL computation tasks // Currently, we do not delegate SSL computation tasks
// TODO: in the future, possibly create tasks to do encrypt / decrypt async // TODO: in the future, possibly create tasks to do encrypt / decrypt async
return null; return null;
} }
@ -1329,6 +1436,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
// for renegotiation. // for renegotiation.
handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user, handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user,
calculateMaxWrapOverhead();
// we should raise an exception. // we should raise an exception.
break; break;
case STARTED_EXPLICITLY: case STARTED_EXPLICITLY:
@ -1375,6 +1483,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
case NOT_STARTED: case NOT_STARTED:
handshakeState = HandshakeState.STARTED_EXPLICITLY; handshakeState = HandshakeState.STARTED_EXPLICITLY;
handshake(); handshake();
calculateMaxWrapOverhead();
break; break;
default: default:
throw new Error(); throw new Error();
@ -1667,11 +1776,6 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
return destroyed != 0; return destroyed != 0;
} }
static int calculateOutNetBufSize(int pendingBytes, int numComponents) {
return (int) min(MAX_ENCRYPTED_PACKET_LENGTH,
pendingBytes + (long) MAX_TLS_RECORD_OVERHEAD_LENGTH * numComponents);
}
final boolean checkSniHostnameMatch(String hostname) { final boolean checkSniHostnameMatch(String hostname) {
return Java8SslUtils.checkSniHostnameMatch(matchers, hostname); return Java8SslUtils.checkSniHostnameMatch(matchers, hostname);
} }
@ -1819,6 +1923,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
initPeerCerts(); initPeerCerts();
selectApplicationProtocol(); selectApplicationProtocol();
calculateMaxWrapOverhead();
handshakeState = HandshakeState.FINISHED; handshakeState = HandshakeState.FINISHED;
} else { } else {
@ -2026,7 +2131,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
@Override @Override
public int getPacketBufferSize() { public int getPacketBufferSize() {
return MAX_ENCRYPTED_PACKET_LENGTH; return maxEncryptedPacketLength();
} }
@Override @Override

View File

@ -882,11 +882,11 @@ public abstract class SslContext {
* Creates a new {@link SslHandler}. * Creates a new {@link SslHandler}.
* <p>If {@link SslProvider#OPENSSL_REFCNT} is used then the returned {@link SslHandler} will release the engine * <p>If {@link SslProvider#OPENSSL_REFCNT} is used then the returned {@link SslHandler} will release the engine
* that is wrapped. If the returned {@link SslHandler} is not inserted into a pipeline then you may leak native * that is wrapped. If the returned {@link SslHandler} is not inserted into a pipeline then you may leak native
* memory!</p> * memory!
* <p><b>Beware</b>: the underlying generated {@link SSLEngine} won't have * <p><b>Beware</b>: the underlying generated {@link SSLEngine} won't have
* <a href="https://wiki.openssl.org/index.php/Hostname_validation">hostname verification</a> enabled by default. * <a href="https://wiki.openssl.org/index.php/Hostname_validation">hostname verification</a> enabled by default.
* If you create {@link SslHandler} for the client side and want proper security, we advice that you configure * If you create {@link SslHandler} for the client side and want proper security, we advice that you configure
* the {@link SSLEngine} (see {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}):</p> * the {@link SSLEngine} (see {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}):
* <pre> * <pre>
* SSLEngine sslEngine = sslHandler.engine(); * SSLEngine sslEngine = sslHandler.engine();
* SSLParameters sslParameters = sslEngine.getSSLParameters(); * SSLParameters sslParameters = sslEngine.getSSLParameters();
@ -894,12 +894,22 @@ public abstract class SslContext {
* sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); * sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
* sslEngine.setSSLParameters(sslParameters); * sslEngine.setSSLParameters(sslParameters);
* </pre> * </pre>
* * <p>
* The underlying {@link SSLEngine} may not follow the restrictions imposed by the
* <a href="https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html">SSLEngine javadocs</a> which
* limits wrap/unwrap to operate on a single SSL/TLS packet.
* @param alloc If supported by the SSLEngine then the SSLEngine will use this to allocate ByteBuf objects. * @param alloc If supported by the SSLEngine then the SSLEngine will use this to allocate ByteBuf objects.
*
* @return a new {@link SslHandler} * @return a new {@link SslHandler}
*/ */
public final SslHandler newHandler(ByteBufAllocator alloc) { public final SslHandler newHandler(ByteBufAllocator alloc) {
return newHandler(alloc, startTls);
}
/**
* Create a new SslHandler.
* @see #newHandler(io.netty.buffer.ByteBufAllocator)
*/
SslHandler newHandler(ByteBufAllocator alloc, boolean startTls) {
return new SslHandler(newEngine(alloc), startTls); return new SslHandler(newEngine(alloc), startTls);
} }
@ -907,11 +917,11 @@ public abstract class SslContext {
* Creates a new {@link SslHandler} with advisory peer information. * Creates a new {@link SslHandler} with advisory peer information.
* <p>If {@link SslProvider#OPENSSL_REFCNT} is used then the returned {@link SslHandler} will release the engine * <p>If {@link SslProvider#OPENSSL_REFCNT} is used then the returned {@link SslHandler} will release the engine
* that is wrapped. If the returned {@link SslHandler} is not inserted into a pipeline then you may leak native * that is wrapped. If the returned {@link SslHandler} is not inserted into a pipeline then you may leak native
* memory!</p> * memory!
* <p><b>Beware</b>: the underlying generated {@link SSLEngine} won't have * <p><b>Beware</b>: the underlying generated {@link SSLEngine} won't have
* <a href="https://wiki.openssl.org/index.php/Hostname_validation">hostname verification</a> enabled by default. * <a href="https://wiki.openssl.org/index.php/Hostname_validation">hostname verification</a> enabled by default.
* If you create {@link SslHandler} for the client side and want proper security, we advice that you configure * If you create {@link SslHandler} for the client side and want proper security, we advice that you configure
* the {@link SSLEngine} (see {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}):</p> * the {@link SSLEngine} (see {@link javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String)}):
* <pre> * <pre>
* SSLEngine sslEngine = sslHandler.engine(); * SSLEngine sslEngine = sslHandler.engine();
* SSLParameters sslParameters = sslEngine.getSSLParameters(); * SSLParameters sslParameters = sslEngine.getSSLParameters();
@ -919,7 +929,10 @@ public abstract class SslContext {
* sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); * sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
* sslEngine.setSSLParameters(sslParameters); * sslEngine.setSSLParameters(sslParameters);
* </pre> * </pre>
* * <p>
* The underlying {@link SSLEngine} may not follow the restrictions imposed by the
* <a href="https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html">SSLEngine javadocs</a> which
* limits wrap/unwrap to operate on a single SSL/TLS packet.
* @param alloc If supported by the SSLEngine then the SSLEngine will use this to allocate ByteBuf objects. * @param alloc If supported by the SSLEngine then the SSLEngine will use this to allocate ByteBuf objects.
* @param peerHost the non-authoritative name of the host * @param peerHost the non-authoritative name of the host
* @param peerPort the non-authoritative port * @param peerPort the non-authoritative port
@ -927,6 +940,14 @@ public abstract class SslContext {
* @return a new {@link SslHandler} * @return a new {@link SslHandler}
*/ */
public final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort) { public final SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort) {
return newHandler(alloc, peerHost, peerPort, startTls);
}
/**
* Create a new SslHandler.
* @see #newHandler(io.netty.buffer.ByteBufAllocator, String, int, boolean)
*/
SslHandler newHandler(ByteBufAllocator alloc, String peerHost, int peerPort, boolean startTls) {
return new SslHandler(newEngine(alloc, peerHost, peerPort), startTls); return new SslHandler(newEngine(alloc, peerHost, peerPort), startTls);
} }

View File

@ -46,12 +46,6 @@ import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import java.io.IOException; import java.io.IOException;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -65,6 +59,12 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import static io.netty.handler.ssl.SslUtils.getEncryptedPacketLength; import static io.netty.handler.ssl.SslUtils.getEncryptedPacketLength;
@ -210,9 +210,21 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
return result; return result;
} }
@Override
int getPacketBufferSize(SslHandler handler) {
return ((ReferenceCountedOpenSslEngine) handler.engine).maxEncryptedPacketLength0();
}
@Override @Override
int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) { int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) {
return ReferenceCountedOpenSslEngine.calculateOutNetBufSize(pendingBytes, numComponents); return ((ReferenceCountedOpenSslEngine) handler.engine).calculateMaxLengthForWrap(pendingBytes,
numComponents);
}
@Override
int calculatePendingData(SslHandler handler, int guess) {
int sslPending = ((ReferenceCountedOpenSslEngine) handler.engine).sslPending();
return sslPending > 0 ? sslPending : guess;
} }
}, },
CONSCRYPT(true, COMPOSITE_CUMULATOR) { CONSCRYPT(true, COMPOSITE_CUMULATOR) {
@ -246,6 +258,11 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) { int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) {
return ((ConscryptAlpnSslEngine) handler.engine).calculateOutNetBufSize(pendingBytes, numComponents); return ((ConscryptAlpnSslEngine) handler.engine).calculateOutNetBufSize(pendingBytes, numComponents);
} }
@Override
int calculatePendingData(SslHandler handler, int guess) {
return guess;
}
}, },
JDK(false, MERGE_CUMULATOR) { JDK(false, MERGE_CUMULATOR) {
@Override @Override
@ -260,18 +277,18 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
@Override @Override
int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) { int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents) {
return handler.maxPacketBufferSize; return handler.engine.getSession().getPacketBufferSize();
}
@Override
int calculatePendingData(SslHandler handler, int guess) {
return guess;
} }
}; };
static SslEngineType forEngine(SSLEngine engine) { static SslEngineType forEngine(SSLEngine engine) {
if (engine instanceof ReferenceCountedOpenSslEngine) { return engine instanceof ReferenceCountedOpenSslEngine ? TCNATIVE :
return TCNATIVE; engine instanceof ConscryptAlpnSslEngine ? CONSCRYPT : JDK;
}
if (engine instanceof ConscryptAlpnSslEngine) {
return CONSCRYPT;
}
return JDK;
} }
SslEngineType(boolean wantsDirectBuffer, Cumulator cumulator) { SslEngineType(boolean wantsDirectBuffer, Cumulator cumulator) {
@ -279,11 +296,17 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
this.cumulator = cumulator; this.cumulator = cumulator;
} }
int getPacketBufferSize(SslHandler handler) {
return handler.engine.getSession().getPacketBufferSize();
}
abstract SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out) abstract SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len, ByteBuf out)
throws SSLException; throws SSLException;
abstract int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents); abstract int calculateWrapBufferCapacity(SslHandler handler, int pendingBytes, int numComponents);
abstract int calculatePendingData(SslHandler handler, int guess);
// BEGIN Platform-dependent flags // BEGIN Platform-dependent flags
/** /**
@ -307,8 +330,8 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
private volatile ChannelHandlerContext ctx; private volatile ChannelHandlerContext ctx;
private final SSLEngine engine; private final SSLEngine engine;
private final SslEngineType engineType; private final SslEngineType engineType;
private final int maxPacketBufferSize;
private final Executor delegatedTaskExecutor; private final Executor delegatedTaskExecutor;
private final boolean jdkCompatibilityMode;
/** /**
* Used if {@link SSLEngine#wrap(ByteBuffer[], ByteBuffer)} and {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer[])} * Used if {@link SSLEngine#wrap(ByteBuffer[], ByteBuffer)} and {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer[])}
@ -380,6 +403,14 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
*/ */
@Deprecated @Deprecated
public SslHandler(SSLEngine engine, boolean startTls, Executor delegatedTaskExecutor) { public SslHandler(SSLEngine engine, boolean startTls, Executor delegatedTaskExecutor) {
this(engine, startTls, true, delegatedTaskExecutor);
}
SslHandler(SSLEngine engine, boolean startTls, boolean jdkCompatibilityMode) {
this(engine, startTls, jdkCompatibilityMode, ImmediateExecutor.INSTANCE);
}
SslHandler(SSLEngine engine, boolean startTls, boolean jdkCompatibilityMode, Executor delegatedTaskExecutor) {
if (engine == null) { if (engine == null) {
throw new NullPointerException("engine"); throw new NullPointerException("engine");
} }
@ -390,7 +421,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
engineType = SslEngineType.forEngine(engine); engineType = SslEngineType.forEngine(engine);
this.delegatedTaskExecutor = delegatedTaskExecutor; this.delegatedTaskExecutor = delegatedTaskExecutor;
this.startTls = startTls; this.startTls = startTls;
maxPacketBufferSize = engine.getSession().getPacketBufferSize(); this.jdkCompatibilityMode = jdkCompatibilityMode;
setCumulator(engineType.cumulator); setCumulator(engineType.cumulator);
} }
@ -878,7 +909,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
switch (result.getStatus()) { switch (result.getStatus()) {
case BUFFER_OVERFLOW: case BUFFER_OVERFLOW:
out.ensureWritable(maxPacketBufferSize); out.ensureWritable(engine.getSession().getPacketBufferSize());
break; break;
default: default:
return result; return result;
@ -1014,75 +1045,63 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
return getEncryptedPacketLength(buffer, buffer.readerIndex()) != SslUtils.NOT_ENCRYPTED; return getEncryptedPacketLength(buffer, buffer.readerIndex()) != SslUtils.NOT_ENCRYPTED;
} }
@Override private void decodeJdkCompatible(ChannelHandlerContext ctx, ByteBuf in) throws NotSslRecordException {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws SSLException { int packetLength = this.packetLength;
final int startOffset = in.readerIndex();
final int endOffset = in.writerIndex();
int offset = startOffset;
int totalLength = 0;
// If we calculated the length of the current SSL record before, use that information. // If we calculated the length of the current SSL record before, use that information.
if (packetLength > 0) { if (packetLength > 0) {
if (endOffset - startOffset < packetLength) { if (in.readableBytes() < packetLength) {
return; return;
}
} else { } else {
offset += packetLength; // Get the packet length and wait until we get a packets worth of data to unwrap.
totalLength = packetLength; final int readableBytes = in.readableBytes();
packetLength = 0;
}
}
boolean nonSslRecord = false;
while (totalLength < ReferenceCountedOpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) {
final int readableBytes = endOffset - offset;
if (readableBytes < SslUtils.SSL_RECORD_HEADER_LENGTH) { if (readableBytes < SslUtils.SSL_RECORD_HEADER_LENGTH) {
break; return;
} }
packetLength = getEncryptedPacketLength(in, in.readerIndex());
final int packetLength = getEncryptedPacketLength(in, offset);
if (packetLength == SslUtils.NOT_ENCRYPTED) { if (packetLength == SslUtils.NOT_ENCRYPTED) {
nonSslRecord = true; // Not an SSL/TLS packet
break; NotSslRecordException e = new NotSslRecordException(
"not an SSL/TLS record: " + ByteBufUtil.hexDump(in));
in.skipBytes(in.readableBytes());
// First fail the handshake promise as we may need to have access to the SSLEngine which may
// be released because the user will remove the SslHandler in an exceptionCaught(...) implementation.
setHandshakeFailure(ctx, e);
throw e;
} }
assert packetLength > 0; assert packetLength > 0;
if (packetLength > readableBytes) { if (packetLength > readableBytes) {
// wait until the whole packet can be read // wait until the whole packet can be read
this.packetLength = packetLength; this.packetLength = packetLength;
break; return;
}
} }
int newTotalLength = totalLength + packetLength; // Reset the state of this class so we can get the length of the next packet. We assume the entire packet will
if (newTotalLength > ReferenceCountedOpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) { // be consumed by the SSLEngine.
// Don't read too much. this.packetLength = 0;
break;
}
// We have a whole packet.
// Increment the offset to handle the next packet.
offset += packetLength;
totalLength = newTotalLength;
}
if (totalLength > 0) {
// The buffer contains one or more full SSL records.
// Slice out the whole packet so unwrap will only be called with complete packets.
// Also directly reset the packetLength. This is needed as unwrap(..) may trigger
// decode(...) again via:
// 1) unwrap(..) is called
// 2) wrap(...) is called from within unwrap(...)
// 3) wrap(...) calls unwrapLater(...)
// 4) unwrapLater(...) calls decode(...)
//
// See https://github.com/netty/netty/issues/1534
in.skipBytes(totalLength);
try { try {
firedChannelRead = unwrap(ctx, in, startOffset, totalLength) || firedChannelRead; int bytesConsumed = unwrap(ctx, in, in.readerIndex(), packetLength);
assert bytesConsumed == packetLength || engine.isInboundDone() :
"we feed the SSLEngine a packets worth of data: " + packetLength + " but it only consumed: " +
bytesConsumed;
in.skipBytes(bytesConsumed);
} catch (Throwable cause) { } catch (Throwable cause) {
handleUnwrapThrowable(ctx, cause);
}
}
private void decodeNonJdkCompatible(ChannelHandlerContext ctx, ByteBuf in) {
try {
in.skipBytes(unwrap(ctx, in, in.readerIndex(), in.readableBytes()));
} catch (Throwable cause) {
handleUnwrapThrowable(ctx, cause);
}
}
private void handleUnwrapThrowable(ChannelHandlerContext ctx, Throwable cause) {
try { try {
// We need to flush one time as there may be an alert that we should send to the remote peer because // We need to flush one time as there may be an alert that we should send to the remote peer because
// of the SSLException reported here. // of the SSLException reported here.
@ -1095,19 +1114,13 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
} }
PlatformDependent.throwException(cause); PlatformDependent.throwException(cause);
} }
}
if (nonSslRecord) { @Override
// Not an SSL/TLS packet protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws SSLException {
NotSslRecordException e = new NotSslRecordException( if (jdkCompatibilityMode) {
"not an SSL/TLS record: " + ByteBufUtil.hexDump(in)); decodeJdkCompatible(ctx, in);
in.skipBytes(in.readableBytes()); } else {
decodeNonJdkCompatible(ctx, in);
// First fail the handshake promise as we may need to have access to the SSLEngine which may
// be released because the user will remove the SslHandler in an exceptionCaught(...) implementation.
setHandshakeFailure(ctx, e);
throw e;
} }
} }
@ -1148,10 +1161,9 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
/** /**
* Unwraps inbound SSL records. * Unwraps inbound SSL records.
*/ */
private boolean unwrap( private int unwrap(
ChannelHandlerContext ctx, ByteBuf packet, int offset, int length) throws SSLException { ChannelHandlerContext ctx, ByteBuf packet, int offset, int length) throws SSLException {
final int originalLength = length;
boolean decoded = false;
boolean wrapLater = false; boolean wrapLater = false;
boolean notifyClosure = false; boolean notifyClosure = false;
ByteBuf decodeOut = allocate(ctx, length); ByteBuf decodeOut = allocate(ctx, length);
@ -1174,7 +1186,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
int readableBytes = decodeOut.readableBytes(); int readableBytes = decodeOut.readableBytes();
int bufferSize = engine.getSession().getApplicationBufferSize() - readableBytes; int bufferSize = engine.getSession().getApplicationBufferSize() - readableBytes;
if (readableBytes > 0) { if (readableBytes > 0) {
decoded = true; firedChannelRead = true;
ctx.fireChannelRead(decodeOut); ctx.fireChannelRead(decodeOut);
// This buffer was handled, null it out. // This buffer was handled, null it out.
@ -1194,7 +1206,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
// Allocate a new buffer which can hold all the rest data and loop again. // Allocate a new buffer which can hold all the rest data and loop again.
// TODO: We may want to reconsider how we calculate the length here as we may // TODO: We may want to reconsider how we calculate the length here as we may
// have more then one ssl message to decode. // have more then one ssl message to decode.
decodeOut = allocate(ctx, bufferSize); decodeOut = allocate(ctx, engineType.calculatePendingData(this, bufferSize));
continue; continue;
case CLOSED: case CLOSED:
// notify about the CLOSED state of the SSLEngine. See #137 // notify about the CLOSED state of the SSLEngine. See #137
@ -1268,7 +1280,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
} finally { } finally {
if (decodeOut != null) { if (decodeOut != null) {
if (decodeOut.isReadable()) { if (decodeOut.isReadable()) {
decoded = true; firedChannelRead = true;
ctx.fireChannelRead(decodeOut); ctx.fireChannelRead(decodeOut);
} else { } else {
@ -1276,7 +1288,7 @@ public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundH
} }
} }
} }
return decoded; return originalLength - length;
} }
private static ByteBuffer toByteBuffer(ByteBuf out, int index, int len) { private static ByteBuffer toByteBuffer(ByteBuf out, int index, int len) {

View File

@ -43,8 +43,6 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLParameters;
import static io.netty.handler.ssl.OpenSslTestUtils.checkShouldUseKeyManagerFactory; import static io.netty.handler.ssl.OpenSslTestUtils.checkShouldUseKeyManagerFactory;
import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH;
import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_TLS_RECORD_OVERHEAD_LENGTH;
import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH; import static io.netty.handler.ssl.ReferenceCountedOpenSslEngine.MAX_PLAINTEXT_LENGTH;
import static io.netty.internal.tcnative.SSL.SSL_CVERIFY_IGNORED; import static io.netty.internal.tcnative.SSL.SSL_CVERIFY_IGNORED;
import static java.lang.Integer.MAX_VALUE; import static java.lang.Integer.MAX_VALUE;
@ -218,9 +216,9 @@ public class OpenSslEngineTest extends SSLEngineTest {
ByteBuffer src = allocateBuffer(srcLen); ByteBuffer src = allocateBuffer(srcLen);
ByteBuffer dstTooSmall = allocateBuffer( ByteBuffer dstTooSmall = allocateBuffer(
src.capacity() + MAX_TLS_RECORD_OVERHEAD_LENGTH - 1); src.capacity() + ((ReferenceCountedOpenSslEngine) clientEngine).maxWrapOverhead() - 1);
ByteBuffer dst = allocateBuffer( ByteBuffer dst = allocateBuffer(
src.capacity() + MAX_TLS_RECORD_OVERHEAD_LENGTH); src.capacity() + ((ReferenceCountedOpenSslEngine) clientEngine).maxWrapOverhead());
// Check that we fail to wrap if the dst buffers capacity is not at least // Check that we fail to wrap if the dst buffers capacity is not at least
// src.capacity() + ReferenceCountedOpenSslEngine.MAX_TLS_RECORD_OVERHEAD_LENGTH // src.capacity() + ReferenceCountedOpenSslEngine.MAX_TLS_RECORD_OVERHEAD_LENGTH
@ -267,7 +265,7 @@ public class OpenSslEngineTest extends SSLEngineTest {
ByteBuffer src2 = src.duplicate(); ByteBuffer src2 = src.duplicate();
ByteBuffer dst = allocateBuffer(src.capacity() ByteBuffer dst = allocateBuffer(src.capacity()
+ MAX_TLS_RECORD_OVERHEAD_LENGTH); + ((ReferenceCountedOpenSslEngine) clientEngine).maxWrapOverhead());
SSLEngineResult result = clientEngine.wrap(new ByteBuffer[] { src, src2 }, dst); SSLEngineResult result = clientEngine.wrap(new ByteBuffer[] { src, src2 }, dst);
assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus());
@ -311,8 +309,8 @@ public class OpenSslEngineTest extends SSLEngineTest {
} }
ByteBuffer[] srcs = srcList.toArray(new ByteBuffer[srcList.size()]); ByteBuffer[] srcs = srcList.toArray(new ByteBuffer[srcList.size()]);
ByteBuffer dst = allocateBuffer(
ByteBuffer dst = allocateBuffer(MAX_ENCRYPTED_PACKET_LENGTH - 1); ((ReferenceCountedOpenSslEngine) clientEngine).maxEncryptedPacketLength() - 1);
SSLEngineResult result = clientEngine.wrap(srcs, dst); SSLEngineResult result = clientEngine.wrap(srcs, dst);
assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus()); assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus());
@ -330,21 +328,34 @@ public class OpenSslEngineTest extends SSLEngineTest {
} }
@Test @Test
public void testCalculateOutNetBufSizeOverflow() { public void testCalculateOutNetBufSizeOverflow() throws SSLException {
assertEquals(MAX_ENCRYPTED_PACKET_LENGTH, clientSslCtx = SslContextBuilder.forClient()
ReferenceCountedOpenSslEngine.calculateOutNetBufSize(MAX_VALUE, 1)); .trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslClientProvider())
.build();
SSLEngine clientEngine = null;
try {
clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
int value = ((ReferenceCountedOpenSslEngine) clientEngine).calculateMaxLengthForWrap(MAX_VALUE, 1);
assertTrue("unexpected value: " + value, value > 0);
} finally {
cleanupClientSslEngine(clientEngine);
}
} }
@Test @Test
public void testCalculateOutNetBufSize0() { public void testCalculateOutNetBufSize0() throws SSLException {
assertEquals(MAX_TLS_RECORD_OVERHEAD_LENGTH, clientSslCtx = SslContextBuilder.forClient()
ReferenceCountedOpenSslEngine.calculateOutNetBufSize(0, 1)); .trustManager(InsecureTrustManagerFactory.INSTANCE)
.sslProvider(sslClientProvider())
.build();
SSLEngine clientEngine = null;
try {
clientEngine = clientSslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);
assertTrue(((ReferenceCountedOpenSslEngine) clientEngine).calculateMaxLengthForWrap(0, 1) > 0);
} finally {
cleanupClientSslEngine(clientEngine);
} }
@Test
public void testCalculateOutNetBufSizeMaxEncryptedPacketLength() {
assertEquals(MAX_ENCRYPTED_PACKET_LENGTH,
ReferenceCountedOpenSslEngine.calculateOutNetBufSize(MAX_ENCRYPTED_PACKET_LENGTH + 1, 2));
} }
@Override @Override
@ -533,6 +544,323 @@ public class OpenSslEngineTest extends SSLEngineTest {
testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ECDHE-RSA-RC4-SHA"); testWrapWithDifferentSizes(OpenSsl.PROTOCOL_SSL_V3, "ECDHE-RSA-RC4-SHA");
} }
@Test
public void testMultipleRecordsInOneBufferWithNonZeroPositionJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
// Choose buffer size small enough that we can put multiple buffers into one buffer and pass it into the
// unwrap call without exceed MAX_ENCRYPTED_PACKET_LENGTH.
final int plainClientOutLen = 1024;
ByteBuffer plainClientOut = allocateBuffer(plainClientOutLen);
ByteBuffer plainServerOut = allocateBuffer(server.getSession().getApplicationBufferSize());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
int positionOffset = 1;
// We need to be able to hold 2 records + positionOffset
ByteBuffer combinedEncClientToServer = allocateBuffer(
encClientToServer.capacity() * 2 + positionOffset);
combinedEncClientToServer.position(positionOffset);
handshake(client, server);
plainClientOut.limit(plainClientOut.capacity());
SSLEngineResult result = client.wrap(plainClientOut, encClientToServer);
assertEquals(plainClientOut.capacity(), result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
encClientToServer.flip();
// Copy the first record into the combined buffer
combinedEncClientToServer.put(encClientToServer);
plainClientOut.clear();
encClientToServer.clear();
result = client.wrap(plainClientOut, encClientToServer);
assertEquals(plainClientOut.capacity(), result.bytesConsumed());
assertTrue(result.bytesProduced() > 0);
encClientToServer.flip();
// Copy the first record into the combined buffer
combinedEncClientToServer.put(encClientToServer);
encClientToServer.clear();
combinedEncClientToServer.flip();
combinedEncClientToServer.position(positionOffset);
// Make sure the limit takes positionOffset into account to the content we are looking at is correct.
combinedEncClientToServer.limit(
combinedEncClientToServer.limit() - positionOffset);
final int combinedEncClientToServerLen = combinedEncClientToServer.remaining();
result = server.unwrap(combinedEncClientToServer, plainServerOut);
assertEquals(0, combinedEncClientToServer.remaining());
assertEquals(combinedEncClientToServerLen, result.bytesConsumed());
assertEquals(plainClientOutLen, result.bytesProduced());
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Test
public void testInputTooBigAndFillsUpBuffersJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
ByteBuffer plainClient = allocateBuffer(MAX_PLAINTEXT_LENGTH + 100);
ByteBuffer plainClient2 = allocateBuffer(512);
ByteBuffer plainClientTotal = allocateBuffer(plainClient.capacity() + plainClient2.capacity());
plainClientTotal.put(plainClient);
plainClientTotal.put(plainClient2);
plainClient.clear();
plainClient2.clear();
plainClientTotal.flip();
// The capacity is designed to trigger an overflow condition.
ByteBuffer encClientToServerTooSmall = allocateBuffer(MAX_PLAINTEXT_LENGTH + 28);
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getApplicationBufferSize());
ByteBuffer encClientToServerTotal = allocateBuffer(client.getSession().getApplicationBufferSize() << 1);
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize() << 1);
handshake(client, server);
int plainClientRemaining = plainClient.remaining();
int encClientToServerTooSmallRemaining = encClientToServerTooSmall.remaining();
SSLEngineResult result = client.wrap(plainClient, encClientToServerTooSmall);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(plainClientRemaining - plainClient.remaining(), result.bytesConsumed());
assertEquals(encClientToServerTooSmallRemaining - encClientToServerTooSmall.remaining(),
result.bytesProduced());
result = client.wrap(plainClient, encClientToServerTooSmall);
assertEquals(SSLEngineResult.Status.BUFFER_OVERFLOW, result.getStatus());
assertEquals(0, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
plainClientRemaining = plainClient.remaining();
int encClientToServerRemaining = encClientToServer.remaining();
result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(plainClientRemaining, result.bytesConsumed());
assertEquals(encClientToServerRemaining - encClientToServer.remaining(), result.bytesProduced());
assertEquals(0, plainClient.remaining());
final int plainClient2Remaining = plainClient2.remaining();
encClientToServerRemaining = encClientToServer.remaining();
result = client.wrap(plainClient2, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(plainClient2Remaining, result.bytesConsumed());
assertEquals(encClientToServerRemaining - encClientToServer.remaining(), result.bytesProduced());
// Concatenate the too small buffer
encClientToServerTooSmall.flip();
encClientToServer.flip();
encClientToServerTotal.put(encClientToServerTooSmall);
encClientToServerTotal.put(encClientToServer);
encClientToServerTotal.flip();
// Unwrap in a single call.
final int encClientToServerTotalRemaining = encClientToServerTotal.remaining();
result = server.unwrap(encClientToServerTotal, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(encClientToServerTotalRemaining, result.bytesConsumed());
plainServer.flip();
assertEquals(plainClientTotal, plainServer);
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Test
public void testPartialPacketUnwrapJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
ByteBuffer plainClient = allocateBuffer(1024);
ByteBuffer plainClient2 = allocateBuffer(512);
ByteBuffer plainClientTotal = allocateBuffer(plainClient.capacity() + plainClient2.capacity());
plainClientTotal.put(plainClient);
plainClientTotal.put(plainClient2);
plainClient.clear();
plainClient2.clear();
plainClientTotal.flip();
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize());
handshake(client, server);
SSLEngineResult result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), plainClient.capacity());
final int encClientLen = result.bytesProduced();
result = client.wrap(plainClient2, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), plainClient2.capacity());
final int encClientLen2 = result.bytesProduced();
// Flip so we can read it.
encClientToServer.flip();
// Consume a partial TLS packet.
ByteBuffer encClientFirstHalf = encClientToServer.duplicate();
encClientFirstHalf.limit(encClientLen / 2);
result = server.unwrap(encClientFirstHalf, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), encClientLen / 2);
encClientToServer.position(result.bytesConsumed());
// We now have half of the first packet and the whole second packet, so lets decode all but the last byte.
ByteBuffer encClientAllButLastByte = encClientToServer.duplicate();
final int encClientAllButLastByteLen = encClientAllButLastByte.remaining() - 1;
encClientAllButLastByte.limit(encClientAllButLastByte.limit() - 1);
result = server.unwrap(encClientAllButLastByte, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), encClientAllButLastByteLen);
encClientToServer.position(encClientToServer.position() + result.bytesConsumed());
// Read the last byte and verify the original content has been decrypted.
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), 1);
plainServer.flip();
assertEquals(plainClientTotal, plainServer);
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
@Test
public void testBufferUnderFlowAvoidedIfJDKCompatabilityModeOff() throws Exception {
SelfSignedCertificate cert = new SelfSignedCertificate();
clientSslCtx = SslContextBuilder
.forClient()
.trustManager(cert.cert())
.sslProvider(sslClientProvider())
.build();
SSLEngine client = clientSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
serverSslCtx = SslContextBuilder
.forServer(cert.certificate(), cert.privateKey())
.sslProvider(sslServerProvider())
.build();
SSLEngine server = serverSslCtx.newHandler(UnpooledByteBufAllocator.DEFAULT).engine();
try {
ByteBuffer plainClient = allocateBuffer(1024);
plainClient.limit(plainClient.capacity());
ByteBuffer encClientToServer = allocateBuffer(client.getSession().getPacketBufferSize());
ByteBuffer plainServer = allocateBuffer(server.getSession().getApplicationBufferSize());
handshake(client, server);
SSLEngineResult result = client.wrap(plainClient, encClientToServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(result.bytesConsumed(), plainClient.capacity());
// Flip so we can read it.
encClientToServer.flip();
int remaining = encClientToServer.remaining();
// We limit the buffer so we have less then the header to read, this should result in an BUFFER_UNDERFLOW.
encClientToServer.limit(SslUtils.SSL_RECORD_HEADER_LENGTH - 1);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(SslUtils.SSL_RECORD_HEADER_LENGTH - 1, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// We limit the buffer so we can read the header but not the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(SslUtils.SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(1, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// We limit the buffer so we can read the header and partly the rest, this should result in an
// BUFFER_UNDERFLOW.
encClientToServer.limit(
SslUtils.SSL_RECORD_HEADER_LENGTH + remaining - 1 - SslUtils.SSL_RECORD_HEADER_LENGTH);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(encClientToServer.limit() - SslUtils.SSL_RECORD_HEADER_LENGTH, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
remaining -= result.bytesConsumed();
// Reset limit so we can read the full record.
encClientToServer.limit(remaining);
assertEquals(0, encClientToServer.remaining());
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.BUFFER_UNDERFLOW, result.getStatus());
assertEquals(0, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
encClientToServer.position(0);
result = server.unwrap(encClientToServer, plainServer);
assertEquals(SSLEngineResult.Status.OK, result.getStatus());
assertEquals(remaining, result.bytesConsumed());
assertEquals(0, result.bytesProduced());
} finally {
cert.delete();
cleanupClientSslEngine(client);
cleanupServerSslEngine(server);
}
}
private void testWrapWithDifferentSizes(String protocol, String cipher) throws Exception { private void testWrapWithDifferentSizes(String protocol, String cipher) throws Exception {
assumeTrue(OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocol)); assumeTrue(OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocol));
if (!OpenSsl.isCipherSuiteAvailable(cipher)) { if (!OpenSsl.isCipherSuiteAvailable(cipher)) {
@ -573,7 +901,7 @@ public class OpenSslEngineTest extends SSLEngineTest {
private void testWrapDstBigEnough(SSLEngine engine, int srcLen) throws SSLException { private void testWrapDstBigEnough(SSLEngine engine, int srcLen) throws SSLException {
ByteBuffer src = allocateBuffer(srcLen); ByteBuffer src = allocateBuffer(srcLen);
ByteBuffer dst = allocateBuffer(srcLen + MAX_TLS_RECORD_OVERHEAD_LENGTH); ByteBuffer dst = allocateBuffer(srcLen + ((ReferenceCountedOpenSslEngine) engine).maxWrapOverhead());
SSLEngineResult result = engine.wrap(src, dst); SSLEngineResult result = engine.wrap(src, dst);
assertEquals(SSLEngineResult.Status.OK, result.getStatus()); assertEquals(SSLEngineResult.Status.OK, result.getStatus());

View File

@ -16,26 +16,9 @@
package io.netty.handler.ssl; package io.netty.handler.ssl;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import org.junit.Test;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -63,9 +46,27 @@ import io.netty.util.ReferenceCounted;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ObjectUtil; import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil; import io.netty.util.internal.StringUtil;
import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class SniHandlerTest { public class SniHandlerTest {
@ -251,14 +252,27 @@ public class SniHandlerTest {
// invalid // invalid
byte[] message = {22, 3, 1, 0, 0}; byte[] message = {22, 3, 1, 0, 0};
try { try {
// Push the handshake message. // Push the handshake message.
ch.writeInbound(Unpooled.wrappedBuffer(message)); ch.writeInbound(Unpooled.wrappedBuffer(message));
// TODO(scott): This should fail becasue the engine should reject zero length records during handshake.
// See https://github.com/netty/netty/issues/6348.
// fail();
} catch (Exception e) { } catch (Exception e) {
// expected // expected
} }
ch.close();
// When the channel is closed the SslHandler will write an empty buffer to the channel.
ByteBuf buf = ch.readOutbound();
// TODO(scott): if the engine is shutdown correctly then this buffer shouldn't be null!
// See https://github.com/netty/netty/issues/6348.
if (buf != null) {
assertFalse(buf.isReadable());
buf.release();
}
assertThat(ch.finish(), is(false)); assertThat(ch.finish(), is(false));
assertThat(handler.hostname(), nullValue()); assertThat(handler.hostname(), nullValue());
assertThat(handler.sslContext(), is(nettyContext)); assertThat(handler.sslContext(), is(nettyContext));

View File

@ -388,8 +388,14 @@ public class SslHandlerTest {
SslHandler handler = new SslHandler(SSLContext.getDefault().createSSLEngine()); SslHandler handler = new SslHandler(SSLContext.getDefault().createSSLEngine());
EmbeddedChannel ch = new EmbeddedChannel(handler); EmbeddedChannel ch = new EmbeddedChannel(handler);
// Closing the Channel will also produce a close_notify so it is expected to return true. ch.close();
assertTrue(ch.finishAndReleaseAll());
// When the channel is closed the SslHandler will write an empty buffer to the channel.
ByteBuf buf = ch.readOutbound();
assertFalse(buf.isReadable());
buf.release();
assertFalse(ch.finishAndReleaseAll());
assertTrue(handler.handshakeFuture().cause() instanceof ClosedChannelException); assertTrue(handler.handshakeFuture().cause() instanceof ClosedChannelException);
assertTrue(handler.sslCloseFuture().cause() instanceof ClosedChannelException); assertTrue(handler.sslCloseFuture().cause() instanceof ClosedChannelException);