Correctly handle errors when using OpenSSL
Motivation: We used ERR_get_error() to detect errors and missed to handle different errors. Also we missed to clear the error queue for a thread before invoke SSL operations, this could lead to detecting errors on different OpenSslEngines then the one in which the error actual happened. Modifications: Explicit handle errors via SSL.get_error and clear the error code before SSL operations. Result: Correctly handle errors and no false-positives in different OpenSslEngines then the one which detected an error.
This commit is contained in:
parent
391df0547b
commit
bad8e0d6ab
@ -28,6 +28,7 @@ import org.apache.tomcat.jni.SSL;
|
|||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLEngineResult;
|
import javax.net.ssl.SSLEngineResult;
|
||||||
|
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
@ -132,11 +133,27 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
private long ssl;
|
private long ssl;
|
||||||
private long networkBIO;
|
private long networkBIO;
|
||||||
|
|
||||||
/**
|
private enum HandshakeState {
|
||||||
* 0 - not accepted, 1 - accepted implicitly via wrap()/unwrap(), 2 - accepted explicitly via beginHandshake() call
|
/**
|
||||||
*/
|
* Not started yet.
|
||||||
private int accepted;
|
*/
|
||||||
private boolean handshakeFinished;
|
NOT_STARTED,
|
||||||
|
/**
|
||||||
|
* Started via unwrap/wrap.
|
||||||
|
*/
|
||||||
|
STARTED_IMPLICITLY,
|
||||||
|
/**
|
||||||
|
* Started via {@link #beginHandshake()}.
|
||||||
|
*/
|
||||||
|
STARTED_EXPLICITLY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handshake is finished.
|
||||||
|
*/
|
||||||
|
FINISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
private HandshakeState handshakeState = HandshakeState.NOT_STARTED;
|
||||||
private boolean receivedShutdown;
|
private boolean receivedShutdown;
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private volatile int destroyed;
|
private volatile int destroyed;
|
||||||
@ -220,7 +237,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SSLSession getHandshakeSession() {
|
public SSLSession getHandshakeSession() {
|
||||||
if (accepted > 0) {
|
if (handshakeState != HandshakeState.NOT_STARTED) {
|
||||||
// handshake started we are able to return the session.
|
// handshake started we are able to return the session.
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
@ -250,6 +267,9 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
// internal errors can cause shutdown without marking the engine closed
|
// internal errors can cause shutdown without marking the engine closed
|
||||||
isInboundDone = isOutboundDone = engineClosed = true;
|
isInboundDone = isOutboundDone = engineClosed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On shutdown clear all errors
|
||||||
|
SSL.clearError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -268,7 +288,6 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
sslWrote = SSL.writeToSSL(ssl, addr, len);
|
sslWrote = SSL.writeToSSL(ssl, addr, len);
|
||||||
if (sslWrote > 0) {
|
if (sslWrote > 0) {
|
||||||
src.position(pos + sslWrote);
|
src.position(pos + sslWrote);
|
||||||
return sslWrote;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ByteBuf buf = alloc.directBuffer(len);
|
ByteBuf buf = alloc.directBuffer(len);
|
||||||
@ -283,7 +302,6 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
sslWrote = SSL.writeToSSL(ssl, addr, len);
|
sslWrote = SSL.writeToSSL(ssl, addr, len);
|
||||||
if (sslWrote > 0) {
|
if (sslWrote > 0) {
|
||||||
src.position(pos + sslWrote);
|
src.position(pos + sslWrote);
|
||||||
return sslWrote;
|
|
||||||
} else {
|
} else {
|
||||||
src.position(pos);
|
src.position(pos);
|
||||||
}
|
}
|
||||||
@ -291,8 +309,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return sslWrote;
|
||||||
throw new IllegalStateException("SSL.writeToSSL() returned a non-positive value: " + sslWrote);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -301,12 +318,12 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
private int writeEncryptedData(final ByteBuffer src) {
|
private int writeEncryptedData(final ByteBuffer src) {
|
||||||
final int pos = src.position();
|
final int pos = src.position();
|
||||||
final int len = src.remaining();
|
final int len = src.remaining();
|
||||||
|
final int netWrote;
|
||||||
if (src.isDirect()) {
|
if (src.isDirect()) {
|
||||||
final long addr = Buffer.address(src) + pos;
|
final long addr = Buffer.address(src) + pos;
|
||||||
final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
|
netWrote = SSL.writeToBIO(networkBIO, addr, len);
|
||||||
if (netWrote >= 0) {
|
if (netWrote >= 0) {
|
||||||
src.position(pos + netWrote);
|
src.position(pos + netWrote);
|
||||||
return netWrote;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final ByteBuf buf = alloc.directBuffer(len);
|
final ByteBuf buf = alloc.directBuffer(len);
|
||||||
@ -315,10 +332,9 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
buf.setBytes(0, src);
|
buf.setBytes(0, src);
|
||||||
|
|
||||||
final int netWrote = SSL.writeToBIO(networkBIO, addr, len);
|
netWrote = SSL.writeToBIO(networkBIO, addr, len);
|
||||||
if (netWrote >= 0) {
|
if (netWrote >= 0) {
|
||||||
src.position(pos + netWrote);
|
src.position(pos + netWrote);
|
||||||
return netWrote;
|
|
||||||
} else {
|
} else {
|
||||||
src.position(pos);
|
src.position(pos);
|
||||||
}
|
}
|
||||||
@ -327,21 +343,21 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return netWrote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read plaintext data from the OpenSSL internal BIO
|
* Read plaintext data from the OpenSSL internal BIO
|
||||||
*/
|
*/
|
||||||
private int readPlaintextData(final ByteBuffer dst) {
|
private int readPlaintextData(final ByteBuffer dst) {
|
||||||
|
final int sslRead;
|
||||||
if (dst.isDirect()) {
|
if (dst.isDirect()) {
|
||||||
final int pos = dst.position();
|
final int pos = dst.position();
|
||||||
final long addr = Buffer.address(dst) + pos;
|
final long addr = Buffer.address(dst) + pos;
|
||||||
final int len = dst.limit() - pos;
|
final int len = dst.limit() - pos;
|
||||||
final int sslRead = SSL.readFromSSL(ssl, addr, len);
|
sslRead = SSL.readFromSSL(ssl, addr, len);
|
||||||
if (sslRead > 0) {
|
if (sslRead > 0) {
|
||||||
dst.position(pos + sslRead);
|
dst.position(pos + sslRead);
|
||||||
return sslRead;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final int pos = dst.position();
|
final int pos = dst.position();
|
||||||
@ -351,29 +367,30 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
try {
|
try {
|
||||||
final long addr = memoryAddress(buf);
|
final long addr = memoryAddress(buf);
|
||||||
|
|
||||||
final int sslRead = SSL.readFromSSL(ssl, addr, len);
|
sslRead = SSL.readFromSSL(ssl, addr, len);
|
||||||
if (sslRead > 0) {
|
if (sslRead > 0) {
|
||||||
dst.limit(pos + sslRead);
|
dst.limit(pos + sslRead);
|
||||||
buf.getBytes(0, dst);
|
buf.getBytes(0, dst);
|
||||||
dst.limit(limit);
|
dst.limit(limit);
|
||||||
return sslRead;
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return sslRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read encrypted data from the OpenSSL network BIO
|
* Read encrypted data from the OpenSSL network BIO
|
||||||
*/
|
*/
|
||||||
private int readEncryptedData(final ByteBuffer dst, final int pending) {
|
private int readEncryptedData(final ByteBuffer dst, final int pending) {
|
||||||
|
final int bioRead;
|
||||||
|
|
||||||
if (dst.isDirect() && dst.remaining() >= pending) {
|
if (dst.isDirect() && dst.remaining() >= pending) {
|
||||||
final int pos = dst.position();
|
final int pos = dst.position();
|
||||||
final long addr = Buffer.address(dst) + pos;
|
final long addr = Buffer.address(dst) + pos;
|
||||||
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
||||||
if (bioRead > 0) {
|
if (bioRead > 0) {
|
||||||
dst.position(pos + bioRead);
|
dst.position(pos + bioRead);
|
||||||
return bioRead;
|
return bioRead;
|
||||||
@ -383,7 +400,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
try {
|
try {
|
||||||
final long addr = memoryAddress(buf);
|
final long addr = memoryAddress(buf);
|
||||||
|
|
||||||
final int bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
bioRead = SSL.readFromBIO(networkBIO, addr, pending);
|
||||||
if (bioRead > 0) {
|
if (bioRead > 0) {
|
||||||
int oldLimit = dst.limit();
|
int oldLimit = dst.limit();
|
||||||
dst.limit(dst.position() + bioRead);
|
dst.limit(dst.position() + bioRead);
|
||||||
@ -396,7 +413,44 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return bioRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLEngineResult readPendingBytesFromBIO(ByteBuffer dst, int bytesConsumed, int bytesProduced)
|
||||||
|
throws SSLException {
|
||||||
|
// Check to see if the engine wrote data into the network BIO
|
||||||
|
int pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
|
||||||
|
if (pendingNet > 0) {
|
||||||
|
|
||||||
|
// Do we have enough room in dst to write encrypted data?
|
||||||
|
int capacity = dst.remaining();
|
||||||
|
if (capacity < pendingNet) {
|
||||||
|
return new SSLEngineResult(BUFFER_OVERFLOW, mayFinishHandshake(getHandshakeStatus(pendingNet)),
|
||||||
|
bytesConsumed, bytesProduced);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the pending data from the network BIO into the dst buffer
|
||||||
|
int produced = readEncryptedData(dst, pendingNet);
|
||||||
|
|
||||||
|
if (produced <= 0) {
|
||||||
|
// We ignore BIO_* errors here as we use in memory BIO anyway and will do another SSL_* call later
|
||||||
|
// on in which we will produce an exception in case of an error
|
||||||
|
SSL.clearError();
|
||||||
|
} else {
|
||||||
|
bytesProduced += produced;
|
||||||
|
pendingNet -= produced;
|
||||||
|
}
|
||||||
|
// If isOuboundDone is set, then the data from the network BIO
|
||||||
|
// was the close_notify message -- we are not required to wait
|
||||||
|
// for the receipt the peer's close_notify message -- shutdown.
|
||||||
|
if (isOutboundDone) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SSLEngineResult(getEngineStatus(), mayFinishHandshake(getHandshakeStatus(pendingNet)),
|
||||||
|
bytesConsumed, bytesProduced);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -427,51 +481,23 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare OpenSSL to work in server mode and receive handshake
|
// Prepare OpenSSL to work in server mode and receive handshake
|
||||||
if (accepted == 0) {
|
if (handshakeState != HandshakeState.FINISHED) {
|
||||||
beginHandshakeImplicitly();
|
if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
|
||||||
}
|
// Update accepted so we know we triggered the handshake via wrap
|
||||||
|
handshakeState = HandshakeState.STARTED_IMPLICITLY;
|
||||||
|
}
|
||||||
|
|
||||||
// In handshake or close_notify stages, check if call to wrap was made
|
HandshakeStatus status = handshake();
|
||||||
// without regard to the handshake status.
|
if (status == NEED_UNWRAP) {
|
||||||
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus0();
|
|
||||||
|
|
||||||
if (handshakeStatus == NEED_UNWRAP) {
|
|
||||||
if (!handshakeFinished) {
|
|
||||||
return NEED_UNWRAP_OK;
|
return NEED_UNWRAP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (engineClosed) {
|
if (engineClosed) {
|
||||||
return NEED_UNWRAP_CLOSED;
|
return NEED_UNWRAP_CLOSED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytesProduced = 0;
|
int bytesProduced = 0;
|
||||||
int pendingNet;
|
|
||||||
|
|
||||||
// Check for pending data in the network BIO
|
|
||||||
pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
|
|
||||||
if (pendingNet > 0) {
|
|
||||||
// Do we have enough room in dst to write encrypted data?
|
|
||||||
int capacity = dst.remaining();
|
|
||||||
if (capacity < pendingNet) {
|
|
||||||
return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus, 0, bytesProduced);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the pending data from the network BIO into the dst buffer
|
|
||||||
try {
|
|
||||||
bytesProduced += readEncryptedData(dst, pendingNet);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If isOuboundDone is set, then the data from the network BIO
|
|
||||||
// was the close_notify message -- we are not required to wait
|
|
||||||
// for the receipt the peer's close_notify message -- shutdown.
|
|
||||||
if (isOutboundDone) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), 0, bytesProduced);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
||||||
@ -484,42 +510,44 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
while (src.hasRemaining()) {
|
while (src.hasRemaining()) {
|
||||||
|
|
||||||
// Write plaintext application data to the SSL engine
|
// Write plaintext application data to the SSL engine
|
||||||
try {
|
int result = writePlaintextData(src);
|
||||||
bytesConsumed += writePlaintextData(src);
|
if (result > 0) {
|
||||||
} catch (Exception e) {
|
bytesConsumed += result;
|
||||||
throw new SSLException(e);
|
} else {
|
||||||
|
int sslError = SSL.getError(ssl, result);
|
||||||
|
switch (sslError) {
|
||||||
|
case SSL.SSL_ERROR_ZERO_RETURN:
|
||||||
|
// This means the connection was shutdown correctly, close inbound and outbound
|
||||||
|
if (!receivedShutdown) {
|
||||||
|
closeAll();
|
||||||
|
}
|
||||||
|
// fall-trough!
|
||||||
|
case SSL.SSL_ERROR_WANT_READ:
|
||||||
|
case SSL.SSL_ERROR_WANT_WRITE:
|
||||||
|
// Break here as this means we need check if there is something pending in the BIO
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Everything else is considered as error
|
||||||
|
shutdownWithError("SSL_write");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if the engine wrote data into the network BIO
|
SSLEngineResult pendingNetResult = readPendingBytesFromBIO(dst, bytesConsumed, bytesProduced);
|
||||||
pendingNet = SSL.pendingWrittenBytesInBIO(networkBIO);
|
if (pendingNetResult != null) {
|
||||||
if (pendingNet > 0) {
|
return pendingNetResult;
|
||||||
// Do we have enough room in dst to write encrypted data?
|
|
||||||
int capacity = dst.remaining();
|
|
||||||
if (capacity < pendingNet) {
|
|
||||||
return new SSLEngineResult(
|
|
||||||
BUFFER_OVERFLOW, handshakeStatus0(), bytesConsumed, bytesProduced);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the pending data from the network BIO into the dst buffer
|
|
||||||
try {
|
|
||||||
bytesProduced += readEncryptedData(dst, pendingNet);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), bytesConsumed, bytesProduced);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// We need to check if pendingWrittenBytesInBIO was checked yet, as we may not checked if the srcs was empty,
|
||||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), bytesConsumed, bytesProduced);
|
// or only contained empty buffers.
|
||||||
}
|
if (bytesConsumed == 0) {
|
||||||
|
SSLEngineResult pendingNetResult = readPendingBytesFromBIO(dst, 0, bytesProduced);
|
||||||
private SSLException newSSLException(String msg) {
|
if (pendingNetResult != null) {
|
||||||
if (!handshakeFinished) {
|
return pendingNetResult;
|
||||||
return new SSLHandshakeException(msg);
|
}
|
||||||
}
|
}
|
||||||
return new SSLException(msg);
|
|
||||||
|
return newResult(bytesConsumed, bytesProduced);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPendingHandshakeException() throws SSLHandshakeException {
|
private void checkPendingHandshakeException() throws SSLHandshakeException {
|
||||||
@ -531,6 +559,27 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the error, shutdown the engine and throw an exception.
|
||||||
|
*/
|
||||||
|
private void shutdownWithError(String operations) throws SSLException {
|
||||||
|
String err = SSL.getLastError();
|
||||||
|
shutdownWithError(operations, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownWithError(String operation, String err) throws SSLException {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("{} failed: OpenSSL error: {}", operation, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// There was an internal error -- shutdown
|
||||||
|
shutdown();
|
||||||
|
if (handshakeState == HandshakeState.FINISHED) {
|
||||||
|
throw new SSLException(err);
|
||||||
|
}
|
||||||
|
throw new SSLHandshakeException(err);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized SSLEngineResult unwrap(
|
public synchronized SSLEngineResult unwrap(
|
||||||
final ByteBuffer[] srcs, int srcsOffset, final int srcsLength,
|
final ByteBuffer[] srcs, int srcsOffset, final int srcsLength,
|
||||||
final ByteBuffer[] dsts, final int dstsOffset, final int dstsLength) throws SSLException {
|
final ByteBuffer[] dsts, final int dstsOffset, final int dstsLength) throws SSLException {
|
||||||
@ -572,15 +621,14 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare OpenSSL to work in server mode and receive handshake
|
// Prepare OpenSSL to work in server mode and receive handshake
|
||||||
if (accepted == 0) {
|
if (handshakeState != HandshakeState.FINISHED) {
|
||||||
beginHandshakeImplicitly();
|
if (handshakeState != HandshakeState.STARTED_EXPLICITLY) {
|
||||||
}
|
// Update accepted so we know we triggered the handshake via wrap
|
||||||
|
handshakeState = HandshakeState.STARTED_IMPLICITLY;
|
||||||
|
}
|
||||||
|
|
||||||
// In handshake or close_notify stages, check if call to unwrap was made
|
HandshakeStatus status = handshake();
|
||||||
// without regard to the handshake status.
|
if (status == NEED_WRAP) {
|
||||||
SSLEngineResult.HandshakeStatus handshakeStatus = handshakeStatus0();
|
|
||||||
if (handshakeStatus == NEED_WRAP) {
|
|
||||||
if (!handshakeFinished) {
|
|
||||||
return NEED_WRAP_OK;
|
return NEED_WRAP_OK;
|
||||||
}
|
}
|
||||||
if (engineClosed) {
|
if (engineClosed) {
|
||||||
@ -608,70 +656,75 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write encrypted data to network BIO
|
// Write encrypted data to network BIO
|
||||||
int bytesConsumed = -1;
|
int bytesConsumed = 0;
|
||||||
try {
|
if (srcsOffset < srcsEndOffset) {
|
||||||
while (srcsOffset < srcsEndOffset) {
|
do {
|
||||||
ByteBuffer src = srcs[srcsOffset];
|
ByteBuffer src = srcs[srcsOffset];
|
||||||
int remaining = src.remaining();
|
int remaining = src.remaining();
|
||||||
int written = writeEncryptedData(src);
|
int written = writeEncryptedData(src);
|
||||||
if (written >= 0) {
|
if (written > 0) {
|
||||||
if (bytesConsumed == -1) {
|
bytesConsumed += written;
|
||||||
bytesConsumed = written;
|
|
||||||
} else {
|
|
||||||
bytesConsumed += written;
|
|
||||||
}
|
|
||||||
if (written == remaining) {
|
if (written == remaining) {
|
||||||
srcsOffset ++;
|
srcsOffset ++;
|
||||||
} else if (written == 0) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// BIO_write returned a negative or zero number, this means we could not complete the write
|
||||||
|
// operation and should retry later.
|
||||||
|
// We ignore BIO_* errors here as we use in memory BIO anyway and will do another SSL_* call later
|
||||||
|
// on in which we will produce an exception in case of an error
|
||||||
|
SSL.clearError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} while (srcsOffset < srcsEndOffset);
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException(e);
|
|
||||||
}
|
|
||||||
if (bytesConsumed >= 0) {
|
|
||||||
int lastPrimingReadResult = SSL.readFromSSL(ssl, EMPTY_ADDR, 0); // priming read
|
int lastPrimingReadResult = SSL.readFromSSL(ssl, EMPTY_ADDR, 0); // priming read
|
||||||
|
|
||||||
// check if SSL_read returned <= 0. In this case we need to check the error and see if it was something
|
// check if SSL_read returned <= 0. In this case we need to check the error and see if it was something
|
||||||
// fatal.
|
// fatal.
|
||||||
if (lastPrimingReadResult <= 0) {
|
if (lastPrimingReadResult <= 0) {
|
||||||
// Check for OpenSSL errors caused by the priming read
|
int sslError = SSL.getError(ssl, lastPrimingReadResult);
|
||||||
long error = SSL.getLastErrorNumber();
|
switch (sslError) {
|
||||||
if (OpenSsl.isError(error)) {
|
case SSL.SSL_ERROR_NONE:
|
||||||
String err = SSL.getErrorString(error);
|
case SSL.SSL_ERROR_WANT_ACCEPT:
|
||||||
if (logger.isDebugEnabled()) {
|
case SSL.SSL_ERROR_WANT_CONNECT:
|
||||||
logger.debug(
|
case SSL.SSL_ERROR_WANT_WRITE:
|
||||||
"SSL_read failed: primingReadResult: " + lastPrimingReadResult +
|
case SSL.SSL_ERROR_WANT_READ:
|
||||||
"; OpenSSL error: '" + err + '\'');
|
case SSL.SSL_ERROR_WANT_X509_LOOKUP:
|
||||||
|
case SSL.SSL_ERROR_ZERO_RETURN:
|
||||||
|
// Nothing to do here
|
||||||
|
break;
|
||||||
|
case SSL.SSL_ERROR_SSL:
|
||||||
|
case SSL.SSL_ERROR_SYSCALL:
|
||||||
|
int err = SSL.getLastErrorNumber();
|
||||||
|
if (OpenSsl.isError(err)) {
|
||||||
|
shutdownWithError("SSL_read", SSL.getErrorString(err));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// fall-through
|
||||||
|
default:
|
||||||
|
// Clear the error queue
|
||||||
|
SSL.clearError();
|
||||||
|
|
||||||
// There was an internal error -- shutdown
|
|
||||||
shutdown();
|
|
||||||
throw newSSLException(err);
|
|
||||||
} else {
|
|
||||||
checkPendingHandshakeException();
|
checkPendingHandshakeException();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rejectRemoteInitiatedRenegation();
|
rejectRemoteInitiatedRenegation();
|
||||||
} else {
|
|
||||||
// Reset to 0 as -1 is used to signal that nothing was written and no priming read needs to be done
|
|
||||||
bytesConsumed = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// There won't be any application data until we're done handshaking
|
// There won't be any application data until we're done handshaking
|
||||||
//
|
//
|
||||||
// We first check handshakeFinished to eliminate the overhead of extra JNI call if possible.
|
// We first check handshakeFinished to eliminate the overhead of extra JNI call if possible.
|
||||||
int pendingApp = (handshakeFinished || SSL.isInInit(ssl) == 0) ? SSL.pendingReadableBytesInSSL(ssl) : 0;
|
int pendingApp = handshakeState == HandshakeState.FINISHED ? SSL.pendingReadableBytesInSSL(ssl) : 0;
|
||||||
int bytesProduced = 0;
|
int bytesProduced = 0;
|
||||||
|
|
||||||
if (pendingApp > 0) {
|
if (pendingApp > 0) {
|
||||||
// Do we have enough room in dsts to write decrypted data?
|
// Do we have enough room in dsts to write decrypted data?
|
||||||
if (capacity < pendingApp) {
|
if (capacity < pendingApp) {
|
||||||
return new SSLEngineResult(BUFFER_OVERFLOW, handshakeStatus0(), bytesConsumed, 0);
|
return new SSLEngineResult(
|
||||||
|
BUFFER_OVERFLOW, mayFinishHandshake(getHandshakeStatus()), bytesConsumed, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write decrypted data to dsts buffers
|
// Write decrypted data to dsts buffers
|
||||||
@ -687,35 +740,55 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytesRead;
|
int bytesRead = readPlaintextData(dst);
|
||||||
try {
|
|
||||||
bytesRead = readPlaintextData(dst);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SSLException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectRemoteInitiatedRenegation();
|
rejectRemoteInitiatedRenegation();
|
||||||
|
|
||||||
if (bytesRead == 0) {
|
if (bytesRead > 0) {
|
||||||
break;
|
bytesProduced += bytesRead;
|
||||||
}
|
pendingApp -= bytesRead;
|
||||||
bytesProduced += bytesRead;
|
|
||||||
pendingApp -= bytesRead;
|
|
||||||
|
|
||||||
if (!dst.hasRemaining()) {
|
if (!dst.hasRemaining()) {
|
||||||
idx ++;
|
idx ++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int sslError = SSL.getError(ssl, bytesRead);
|
||||||
|
switch (sslError) {
|
||||||
|
case SSL.SSL_ERROR_ZERO_RETURN:
|
||||||
|
// This means the connection was shutdown correctly, close inbound and outbound
|
||||||
|
if (!receivedShutdown) {
|
||||||
|
closeAll();
|
||||||
|
}
|
||||||
|
// fall-trough!
|
||||||
|
case SSL.SSL_ERROR_WANT_READ:
|
||||||
|
case SSL.SSL_ERROR_WANT_WRITE:
|
||||||
|
// break to the outer loop
|
||||||
|
return newResult(bytesConsumed, bytesProduced);
|
||||||
|
default:
|
||||||
|
// Everything else is considered as error so shutdown and throw an exceptions
|
||||||
|
shutdownWithError("SSL_read");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if we received a close_notify message from the peer
|
// Check to see if we received a close_notify message from the peer
|
||||||
if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
|
if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
|
||||||
receivedShutdown = true;
|
closeAll();
|
||||||
closeOutbound();
|
|
||||||
closeInbound();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SSLEngineResult(getEngineStatus(), handshakeStatus0(), bytesConsumed, bytesProduced);
|
return newResult(bytesConsumed, bytesProduced);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLEngineResult newResult(int bytesConsumed, int bytesProduced) throws SSLException {
|
||||||
|
return new SSLEngineResult(
|
||||||
|
getEngineStatus(), mayFinishHandshake(getHandshakeStatus()), bytesConsumed, bytesProduced);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeAll() throws SSLException {
|
||||||
|
receivedShutdown = true;
|
||||||
|
closeOutbound();
|
||||||
|
closeInbound();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rejectRemoteInitiatedRenegation() throws SSLHandshakeException {
|
private void rejectRemoteInitiatedRenegation() throws SSLHandshakeException {
|
||||||
@ -756,7 +829,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
shutdown();
|
shutdown();
|
||||||
|
|
||||||
if (accepted != 0 && !receivedShutdown) {
|
if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) {
|
||||||
throw new SSLException(
|
throw new SSLException(
|
||||||
"Inbound closed before receiving peer's close_notify: possible truncation attack?");
|
"Inbound closed before receiving peer's close_notify: possible truncation attack?");
|
||||||
}
|
}
|
||||||
@ -776,10 +849,35 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
isOutboundDone = true;
|
isOutboundDone = true;
|
||||||
engineClosed = true;
|
engineClosed = true;
|
||||||
|
|
||||||
if (accepted != 0 && destroyed == 0) {
|
if (handshakeState != HandshakeState.NOT_STARTED && destroyed == 0) {
|
||||||
int mode = SSL.getShutdown(ssl);
|
int mode = SSL.getShutdown(ssl);
|
||||||
if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
|
if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
|
||||||
SSL.shutdownSSL(ssl);
|
int err = SSL.shutdownSSL(ssl);
|
||||||
|
if (err < 0) {
|
||||||
|
int sslErr = SSL.getError(ssl, err);
|
||||||
|
switch (sslErr) {
|
||||||
|
case SSL.SSL_ERROR_NONE:
|
||||||
|
case SSL.SSL_ERROR_WANT_ACCEPT:
|
||||||
|
case SSL.SSL_ERROR_WANT_CONNECT:
|
||||||
|
case SSL.SSL_ERROR_WANT_WRITE:
|
||||||
|
case SSL.SSL_ERROR_WANT_READ:
|
||||||
|
case SSL.SSL_ERROR_WANT_X509_LOOKUP:
|
||||||
|
case SSL.SSL_ERROR_ZERO_RETURN:
|
||||||
|
// Nothing to do here
|
||||||
|
break;
|
||||||
|
case SSL.SSL_ERROR_SYSCALL:
|
||||||
|
case SSL.SSL_ERROR_SSL:
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("SSL_shutdown failed: OpenSSL error: {}", SSL.getLastError());
|
||||||
|
}
|
||||||
|
// There was an internal error -- shutdown
|
||||||
|
shutdown();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SSL.clearError();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// engine closing before initial handshake
|
// engine closing before initial handshake
|
||||||
@ -961,63 +1059,62 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void beginHandshake() throws SSLException {
|
public synchronized void beginHandshake() throws SSLException {
|
||||||
if (engineClosed || destroyed != 0) {
|
switch (handshakeState) {
|
||||||
throw ENGINE_CLOSED;
|
case NOT_STARTED:
|
||||||
}
|
|
||||||
switch (accepted) {
|
|
||||||
case 0:
|
|
||||||
handshake();
|
handshake();
|
||||||
accepted = 2;
|
handshakeState = HandshakeState.STARTED_EXPLICITLY;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case STARTED_IMPLICITLY:
|
||||||
|
checkEngineClosed();
|
||||||
|
|
||||||
// A user did not start handshake by calling this method by him/herself,
|
// A user did not start handshake by calling this method by him/herself,
|
||||||
// but handshake has been started already by wrap() or unwrap() implicitly.
|
// but handshake has been started already by wrap() or unwrap() implicitly.
|
||||||
// Because it's the user's first time to call this method, it is unfair to
|
// Because it's the user's first time to call this method, it is unfair to
|
||||||
// raise an exception. From the user's standpoint, he or she never asked
|
// raise an exception. From the user's standpoint, he or she never asked
|
||||||
// for renegotiation.
|
// for renegotiation.
|
||||||
|
|
||||||
accepted = 2; // Next time this method is invoked by the user, we should raise an exception.
|
handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user,
|
||||||
|
// we should raise an exception.
|
||||||
break;
|
break;
|
||||||
case 2:
|
case STARTED_EXPLICITLY:
|
||||||
throw RENEGOTIATION_UNSUPPORTED;
|
throw RENEGOTIATION_UNSUPPORTED;
|
||||||
default:
|
default:
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginHandshakeImplicitly() throws SSLException {
|
private void checkEngineClosed() throws SSLException {
|
||||||
if (engineClosed || destroyed != 0) {
|
if (engineClosed || destroyed != 0) {
|
||||||
throw ENGINE_CLOSED;
|
throw ENGINE_CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accepted == 0) {
|
|
||||||
handshake();
|
|
||||||
accepted = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handshake() throws SSLException {
|
private static HandshakeStatus pendingStatus(int pendingStatus) {
|
||||||
|
// Depending on if there is something left in the BIO we need to WRAP or UNWRAP
|
||||||
|
return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HandshakeStatus handshake() throws SSLException {
|
||||||
|
checkEngineClosed();
|
||||||
int code = SSL.doHandshake(ssl);
|
int code = SSL.doHandshake(ssl);
|
||||||
if (code <= 0) {
|
if (code <= 0) {
|
||||||
// Check for OpenSSL errors caused by the handshake
|
|
||||||
long error = SSL.getLastErrorNumber();
|
|
||||||
if (OpenSsl.isError(error)) {
|
|
||||||
String err = SSL.getErrorString(error);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(
|
|
||||||
"SSL_do_handshake failed: OpenSSL error: '" + err + '\'');
|
|
||||||
}
|
|
||||||
|
|
||||||
// There was an internal error -- shutdown
|
|
||||||
shutdown();
|
|
||||||
throw newSSLException(err);
|
|
||||||
}
|
|
||||||
checkPendingHandshakeException();
|
checkPendingHandshakeException();
|
||||||
} else {
|
|
||||||
// if SSL_do_handshake returns > 0 it means the handshake was finished. This means we can update
|
int sslError = SSL.getError(ssl, code);
|
||||||
// handshakeFinished directly and so eliminate uncessary calls to SSL.isInInit(...)
|
|
||||||
handshakeFinished();
|
switch (sslError) {
|
||||||
|
case SSL.SSL_ERROR_WANT_READ:
|
||||||
|
case SSL.SSL_ERROR_WANT_WRITE:
|
||||||
|
return pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO));
|
||||||
|
default:
|
||||||
|
// Everything else is considered as error
|
||||||
|
shutdownWithError("SSL_do_handshake");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished.
|
||||||
|
handshakeFinished();
|
||||||
|
return FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long memoryAddress(ByteBuf buf) {
|
private static long memoryAddress(ByteBuf buf) {
|
||||||
@ -1061,7 +1158,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
default:
|
default:
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
handshakeFinished = true;
|
handshakeState = HandshakeState.FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String selectApplicationProtocol(List<String> protocols,
|
private static String selectApplicationProtocol(List<String> protocols,
|
||||||
@ -1088,54 +1185,38 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
return engineClosed? CLOSED : OK;
|
return engineClosed? CLOSED : OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SSLEngineResult.HandshakeStatus handshakeStatus0() throws SSLException {
|
private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status)
|
||||||
SSLEngineResult.HandshakeStatus status = getHandshakeStatus();
|
throws SSLException {
|
||||||
if (status == FINISHED) {
|
if (status == NOT_HANDSHAKING && handshakeState != HandshakeState.FINISHED) {
|
||||||
handshakeFinished();
|
// If the status was NOT_HANDSHAKING and we not finished the handshake we need to call
|
||||||
|
// SSL_do_handshake() again
|
||||||
|
return handshake();
|
||||||
}
|
}
|
||||||
checkPendingHandshakeException();
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
|
||||||
if (accepted == 0 || destroyed != 0) {
|
// Check if we are in the initial handshake phase or shutdown phase
|
||||||
return NOT_HANDSHAKING;
|
if (needPendingStatus()) {
|
||||||
|
return pendingStatus(SSL.pendingWrittenBytesInBIO(networkBIO));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are in the initial handshake phase
|
|
||||||
if (!handshakeFinished) {
|
|
||||||
// There is pending data in the network BIO -- call wrap
|
|
||||||
if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) {
|
|
||||||
return NEED_WRAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No pending data to be sent to the peer
|
|
||||||
// Check to see if we have finished handshaking
|
|
||||||
if (SSL.isInInit(ssl) == 0) {
|
|
||||||
return FINISHED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No pending data and still handshaking
|
|
||||||
// Must be waiting on the peer to send more data
|
|
||||||
return NEED_UNWRAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are in the shutdown phase
|
|
||||||
if (engineClosed) {
|
|
||||||
// Waiting to send the close_notify message
|
|
||||||
if (SSL.pendingWrittenBytesInBIO(networkBIO) != 0) {
|
|
||||||
return NEED_WRAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be waiting to receive the close_notify message
|
|
||||||
return NEED_UNWRAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NOT_HANDSHAKING;
|
return NOT_HANDSHAKING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) {
|
||||||
|
// Check if we are in the initial handshake phase or shutdown phase
|
||||||
|
if (needPendingStatus()) {
|
||||||
|
return pendingStatus(pending);
|
||||||
|
}
|
||||||
|
return NOT_HANDSHAKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean needPendingStatus() {
|
||||||
|
return handshakeState != HandshakeState.NOT_STARTED && destroyed == 0
|
||||||
|
&& (handshakeState != HandshakeState.FINISHED || engineClosed);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the specified OpenSSL cipher suite to the Java cipher suite.
|
* Converts the specified OpenSSL cipher suite to the Java cipher suite.
|
||||||
*/
|
*/
|
||||||
@ -1460,7 +1541,7 @@ public final class OpenSslEngine extends SSLEngine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCipherSuite() {
|
public String getCipherSuite() {
|
||||||
if (!handshakeFinished) {
|
if (handshakeState != HandshakeState.FINISHED) {
|
||||||
return INVALID_CIPHER;
|
return INVALID_CIPHER;
|
||||||
}
|
}
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
|
2
pom.xml
2
pom.xml
@ -686,7 +686,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>netty-tcnative</artifactId>
|
<artifactId>netty-tcnative</artifactId>
|
||||||
<version>1.1.33.Fork2</version>
|
<version>1.1.33.Fork3</version>
|
||||||
<classifier>${os.detected.classifier}</classifier>
|
<classifier>${os.detected.classifier}</classifier>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user