//
// ========================================================================
-// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
private final ByteBufferPool _bufferPool;
- private final SSLEngine _sslEngine;
+ private SSLEngine _sslEngine;
+ private final SslReconfigurator _sslFactory;
private final DecryptedEndPoint _decryptedEndPoint;
private ByteBuffer _decryptedInput;
private ByteBuffer _encryptedInput;
private boolean _renegotiationAllowed;
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
+ {
+ this(byteBufferPool, executor, endPoint, sslEngine, null);
+ }
+ public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine, SslReconfigurator fact)
{
// This connection does not execute calls to onfillable, so they will be called by the selector thread.
// onfillable does not block and will only wakeup another thread to do the actual reading and handling.
super(endPoint, executor, !EXECUTE_ONFILLABLE);
this._bufferPool = byteBufferPool;
this._sslEngine = sslEngine;
+ this._sslFactory = fact;
this._decryptedEndPoint = newDecryptedEndPoint();
}
private boolean _cannotAcceptMoreAppDataToFlush;
private boolean _handshaken;
private boolean _underFlown;
+ private boolean _peeking = _sslFactory != null;
private final Callback _writeCallback = new Callback()
{
{
@Override
public void succeeded()
- {
+ {
}
@Override
getFillInterest().onFail(x);
getWriteFlusher().onFail(x);
}
-
+
},x);
}
};
// all data could be wrapped. So either we need to write some encrypted data,
// OR if we are handshaking we need to read some encrypted data OR
// if neither then we should just try the flush again.
- boolean flush = false;
+ boolean try_again = false;
synchronized (DecryptedEndPoint.this)
{
if (DEBUG)
}
else
{
- flush = true;
+ // We can get here because the WriteFlusher might not see progress
+ // when it has just flushed the encrypted data, but not consumed anymore
+ // of the application buffers. This is mostly avoided by another iteration
+ // within DecryptedEndPoint flush(), but I cannot convince myself that
+ // this is never ever the case.
+ try_again = true;
}
}
- if (flush)
+
+
+ if (try_again)
{
// If the output is closed,
if (isOutputShutdown())
else
{
// try to flush what is pending
- getWriteFlusher().completeWrite();
+ // because this is a special case (see above) we could probably
+ // avoid the dispatch, but best to be sure
+ getExecutor().execute(_runCompletWrite);
}
}
}
// We will need a network buffer
if (_encryptedInput == null)
_encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
- else
+ else if(!_peeking)
BufferUtil.compact(_encryptedInput);
// We also need an app buffer, but can use the passed buffer if it is big enough
// Let's unwrap even if we have no net data because in that
// case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
- SSLEngineResult unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
- BufferUtil.flipToFlush(app_in, pos);
+ SSLEngineResult unwrapResult;
+ try
+ {
+ unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(app_in, pos);
+ }
if (DEBUG)
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
Status unwrapResultStatus = unwrapResult.getStatus();
- _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW;
+ // Extra check on unwrapResultStatus == OK with zero length buffer is due
+ // to SSL client on android (see bug #454773)
+ _underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW || unwrapResultStatus == Status.OK && unwrapResult.bytesConsumed()==0 && unwrapResult.bytesProduced()==0;
if (_underFlown)
{
// or shutting down the output.
return -1;
}
+ case NEED_UNWRAP:
+ {
+ // We expected to read more, but we got closed.
+ // Return -1 to indicate to the application to drive the close.
+ return -1;
+ }
default:
{
throw new IllegalStateException();
case NEED_TASK:
{
_sslEngine.getDelegatedTask().run();
+ if(_peeking)
+ {
+ _sslEngine = _sslFactory.restartSSL(_sslEngine.getHandshakeSession());
+ _encryptedInput.position(0);
+ _peeking = false;
+ continue decryption;
+ }
continue;
}
case NEED_WRAP:
}
catch (Exception e)
{
- getEndPoint().close();
+ close();
throw e;
}
finally
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
BufferUtil.compact(_encryptedOutput);
int pos = BufferUtil.flipToFill(_encryptedOutput);
- SSLEngineResult wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
+ SSLEngineResult wrapResult;
+ try
+ {
+ wrapResult=_sslEngine.wrap(appOuts, _encryptedOutput);
+ }
+ finally
+ {
+ BufferUtil.flipToFlush(_encryptedOutput, pos);
+ }
+
if (DEBUG)
LOG.debug("{} wrap {}", SslConnection.this, wrapResult);
- BufferUtil.flipToFlush(_encryptedOutput, pos);
if (wrapResult.bytesConsumed()>0)
consumed+=wrapResult.bytesConsumed();
+ Status wrapResultStatus = wrapResult.getStatus();
boolean allConsumed=true;
- // clear empty buffers to prevent position creeping up the buffer
for (ByteBuffer b : appOuts)
- {
- if (BufferUtil.isEmpty(b))
- BufferUtil.clear(b);
- else
+ if (BufferUtil.hasContent(b))
allConsumed=false;
- }
-
- Status wrapResultStatus = wrapResult.getStatus();
// and deal with the results returned from the sslEngineWrap
switch (wrapResultStatus)
{
if (DEBUG)
LOG.debug("{} renegotiation denied", SslConnection.this);
- shutdownOutput();
+ getEndPoint().shutdownOutput();
return allConsumed;
}
// if we have net bytes, let's try to flush them
if (BufferUtil.hasContent(_encryptedOutput))
- getEndPoint().flush(_encryptedOutput);
+ if (!getEndPoint().flush(_encryptedOutput))
+ getEndPoint().flush(_encryptedOutput); // one retry
// But we also might have more to do for the handshaking state.
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
- // Return with the number of bytes consumed (which may be 0)
+ // If we have not consumed all and had just finished handshaking, then we may
+ // have just flushed the last handshake in the encrypted buffers, so we should
+ // try again.
+ if (!allConsumed && wrapResult.getHandshakeStatus()==HandshakeStatus.FINISHED && BufferUtil.isEmpty(_encryptedOutput))
+ continue;
+
+ // Return true if we consumed all the bytes and encrypted are all flushed
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case NEED_TASK:
}
}
}
- catch (Exception e)
- {
- getEndPoint().close();
- throw e;
- }
finally
{
if (DEBUG)