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:
Norman Maurer 2015-06-11 13:39:28 +02:00
parent 391df0547b
commit bad8e0d6ab
2 changed files with 305 additions and 224 deletions

View File

@ -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) {

View File

@ -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>