/** * Encrypt provided buffer. Encrypted data returned by getOutNetBuffer(). * * @param src * data to encrypt * @throws SSLException * on errors */ /* no qualifier */void encrypt(ByteBuffer src) throws SSLException { if (!handshakeComplete) { throw new IllegalStateException(); } if (!src.hasRemaining()) { if (outNetBuffer == null) { outNetBuffer = emptyBuffer; } return; } createOutNetBuffer(src.remaining()); // Loop until there is no more data in src while (src.hasRemaining()) { SSLEngineResult result = sslEngine.wrap(src, outNetBuffer.buf()); if (result.getStatus() == SSLEngineResult.Status.OK) { if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) { doTasks(); } } else if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { outNetBuffer.capacity(outNetBuffer.capacity() << 1); outNetBuffer.limit(outNetBuffer.capacity()); } else { throw new SSLException("SSLEngine error during encrypt: " + result.getStatus() + " src: " + src + "outNetBuffer: " + outNetBuffer); } } outNetBuffer.flip(); }
/** * Executes all the tasks needed on the same thread. * @return HandshakeStatus */ protected SSLEngineResult.HandshakeStatus tasks() { Runnable r = null; while ( (r = sslEngine.getDelegatedTask()) != null) { r.run(); } return sslEngine.getHandshakeStatus(); }
/** * Sends a SSL close message, will not physically close the connection here.<br> * To close the connection, you could do something like * <pre><code> * close(); * while (isOpen() && !myTimeoutFunction()) Thread.sleep(25); * if ( isOpen() ) close(true); //forces a close if you timed out * </code></pre> * @throws IOException if an I/O error occurs * @throws IOException if there is data on the outgoing network buffer and we are unable to flush it * TODO Implement this java.io.Closeable method */ @Override public void close() throws IOException { if (closing) return; closing = true; sslEngine.closeOutbound(); if (!flush(netOutBuffer)) { throw new IOException("Remaining data in the network buffer, can't send SSL close message, force a close with close(true) instead"); } //prep the buffer for the close message netOutBuffer.clear(); //perform the close, since we called sslEngine.closeOutbound SSLEngineResult handshake = sslEngine.wrap(getEmptyBuf(), netOutBuffer); //we should be in a close state if (handshake.getStatus() != SSLEngineResult.Status.CLOSED) { throw new IOException("Invalid close state, will not send network data."); } //prepare the buffer for writing netOutBuffer.flip(); //if there is data to be written flush(netOutBuffer); //is the channel closed? closed = (!netOutBuffer.hasRemaining() && (handshake.getHandshakeStatus() != HandshakeStatus.NEED_WRAP)); }
private void runDelegatedTasks(SSLEngineResult result) throws IOException { if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Running delegated task for " + result); } /* * Delegated tasks are just invisible steps inside the sslEngine state machine. * Call them every time they have NEED_TASK otherwise the sslEngine won't make progress */ if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { Runnable runnable; while ((runnable = sslEngine.getDelegatedTask()) != null) { runnable.run(); } HandshakeStatus hsStatus = sslEngine.getHandshakeStatus(); if(logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) { logger.logDebug("Handshake status after delegated tasks " + hsStatus); } if (hsStatus == HandshakeStatus.NEED_TASK) { throw new IOException( "handshake shouldn't need additional tasks"); } } }
synchronized void writeRecord(EngineOutputRecord outputRecord, Authenticator authenticator, CipherBox writeCipher) throws IOException { /* * Only output if we're still open. */ if (outboundClosed) { throw new IOException("writer side was already closed."); } outputRecord.write(authenticator, writeCipher); /* * Did our handshakers notify that we just sent the * Finished message? * * Add an "I'm finished" message to the queue. */ if (outputRecord.isFinishedMsg()) { outboundList.addLast(HandshakeStatus.FINISHED); } }
/** * Checks if the handshake status is finished * Sets the interestOps for the selectionKey. */ private void handshakeFinished() throws IOException { // SSLEngine.getHandshakeStatus is transient and it doesn't record FINISHED status properly. // It can move from FINISHED status to NOT_HANDSHAKING after the handshake is completed. // Hence we also need to check handshakeResult.getHandshakeStatus() if the handshake finished or not if (handshakeResult.getHandshakeStatus() == HandshakeStatus.FINISHED) { //we are complete if we have delivered the last package handshakeComplete = !netWriteBuffer.hasRemaining(); //remove OP_WRITE if we are complete, otherwise we still have data to write if (!handshakeComplete) key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); else { key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); SSLSession session = sslEngine.getSession(); log.debug("SSL handshake completed successfully with peerHost '{}' peerPort {} peerPrincipal '{}' cipherSuite '{}'", session.getPeerHost(), session.getPeerPort(), peerPrincipal(), session.getCipherSuite()); } log.trace("SSLHandshake FINISHED channelId {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {} ", channelId, appReadBuffer.position(), netReadBuffer.position(), netWriteBuffer.position()); } else { throw new IOException("NOT_HANDSHAKING during handshake"); } }
/** * Performs the WRAP function * @param doWrite boolean * @return SSLEngineResult * @throws IOException */ private SSLEngineResult handshakeWrap(boolean doWrite) throws IOException { log.trace("SSLHandshake handshakeWrap {}", channelId); if (netWriteBuffer.hasRemaining()) throw new IllegalStateException("handshakeWrap called with netWriteBuffer not empty"); //this should never be called with a network buffer that contains data //so we can clear it here. netWriteBuffer.clear(); SSLEngineResult result = sslEngine.wrap(emptyBuf, netWriteBuffer); //prepare the results to be written netWriteBuffer.flip(); handshakeStatus = result.getHandshakeStatus(); if (result.getStatus() == SSLEngineResult.Status.OK && result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { handshakeStatus = runDelegatedTasks(); } if (doWrite) flush(netWriteBuffer); return result; }
/** * Perform handshake unwrap * @param doRead boolean * @return SSLEngineResult * @throws IOException */ private SSLEngineResult handshakeUnwrap(boolean doRead) throws IOException { log.trace("SSLHandshake handshakeUnwrap {}", channelId); SSLEngineResult result; if (doRead) { int read = socketChannel.read(netReadBuffer); if (read == -1) throw new EOFException("EOF during handshake."); } boolean cont; do { //prepare the buffer with the incoming data netReadBuffer.flip(); result = sslEngine.unwrap(netReadBuffer, appReadBuffer); netReadBuffer.compact(); handshakeStatus = result.getHandshakeStatus(); if (result.getStatus() == SSLEngineResult.Status.OK && result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { handshakeStatus = runDelegatedTasks(); } cont = result.getStatus() == SSLEngineResult.Status.OK && handshakeStatus == HandshakeStatus.NEED_UNWRAP; log.trace("SSLHandshake handshakeUnwrap: handshakeStatus {} status {}", handshakeStatus, result.getStatus()); } while (netReadBuffer.position() != 0 && cont); return result; }
/** * send the data in the given ByteBuffer. If a handshake is needed * then this is handled within this method. When this call returns, * all of the given user data has been sent and any handshake has been * completed. Caller should check if engine has been closed. */ WrapperResult sendData (ByteBuffer[] src, int offset, int len) throws IOException { WrapperResult r = WrapperResult.createOK(); while (countBytes(src, offset, len) > 0) { r = wrapper.wrapAndSend(src, offset, len, false); Status status = r.result.getStatus(); if (status == Status.CLOSED) { doClosure (); return r; } HandshakeStatus hs_status = r.result.getHandshakeStatus(); if (hs_status != HandshakeStatus.FINISHED && hs_status != HandshakeStatus.NOT_HANDSHAKING) { doHandshake(hs_status); } } return r; }
/** * read data thru the engine into the given ByteBuffer. If the * given buffer was not large enough, a new one is allocated * and returned. This call handles handshaking automatically. * Caller should check if engine has been closed. */ WrapperResult recvData (ByteBuffer dst) throws IOException { /* we wait until some user data arrives */ int mark = dst.position(); WrapperResult r = null; int pos = dst.position(); while (dst.position() == pos) { r = wrapper.recvAndUnwrap (dst); dst = (r.buf != dst) ? r.buf: dst; Status status = r.result.getStatus(); if (status == Status.CLOSED) { doClosure (); return r; } HandshakeStatus hs_status = r.result.getHandshakeStatus(); if (hs_status != HandshakeStatus.FINISHED && hs_status != HandshakeStatus.NOT_HANDSHAKING) { doHandshake (hs_status); } } Utils.flipToMark(dst, mark); return r; }
/** * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} **/ private synchronized ByteBuffer unwrap() throws SSLException { int rem; //There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458) if(readEngineResult.getStatus() == Status.CLOSED && sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING){ try { close(); } catch (IOException e) { //Not really interesting } } do { rem = inData.remaining(); readEngineResult = sslEngine.unwrap( inCrypt, inData ); } while ( readEngineResult.getStatus() == Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); inData.flip(); return inData; }
/** * Checks if the handshake status is finished * Sets the interestOps for the selectionKey. */ private void handshakeFinished() throws IOException { // SSLEngine.getHandshakeStatus is transient and it doesn't record FINISHED status properly. // It can move from FINISHED status to NOT_HANDSHAKING after the handshake is completed. // Hence we also need to check handshakeResult.getHandshakeStatus() if the handshake finished or not if (handshakeResult.getHandshakeStatus() == HandshakeStatus.FINISHED) { //we are complete if we have delivered the last package handshakeComplete = !netWriteBuffer.hasRemaining(); //remove OP_WRITE if we are complete, otherwise we still have data to write if (!handshakeComplete) key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); else key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); log.trace("SSLHandshake FINISHED channelId {}, appReadBuffer pos {}, netReadBuffer pos {}, netWriteBuffer pos {} ", channelId, appReadBuffer.position(), netReadBuffer.position(), netWriteBuffer.position()); } else { throw new IOException("NOT_HANDSHAKING during handshake"); } }
private HandshakeStatus getHandshakeStatusInternal() { if (handshakeFinished) { return HandshakeStatus.NOT_HANDSHAKING; } switch (state) { case STATE_HANDSHAKE_STARTED: return pendingStatus(pendingOutboundEncryptedBytes()); case STATE_HANDSHAKE_COMPLETED: return HandshakeStatus.NEED_WRAP; case STATE_NEW: case STATE_MODE_SET: case STATE_CLOSED: case STATE_CLOSED_INBOUND: case STATE_CLOSED_OUTBOUND: case STATE_READY: case STATE_READY_HANDSHAKE_CUT_THROUGH: return HandshakeStatus.NOT_HANDSHAKING; default: break; } throw new IllegalStateException("Unexpected engine state: " + state); }
public ByteBuf unwrap(SocketChannel channel, ByteBuf src) throws IOException { SSLEngine sslEngine = channel.getSSLEngine(); ByteBuf dst = getTempDst(sslEngine); for (;;) { dst.clear(); SSLEngineResult result = sslEngine.unwrap(src.nioBuffer(), dst.nioBuffer()); HandshakeStatus handshakeStatus = result.getHandshakeStatus(); synchByteBuf(result, src, dst); if (handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) { if (handshakeStatus == HandshakeStatus.NEED_WRAP) { channel.doFlush(forgeFuture.duplicate()); return null; } else if (handshakeStatus == HandshakeStatus.NEED_TASK) { runDelegatedTasks(sslEngine); continue; } else if (handshakeStatus == HandshakeStatus.FINISHED) { channel.finishHandshake(null); return null; } else if (handshakeStatus == HandshakeStatus.NEED_UNWRAP) { return null; } } return gc(channel, dst.flip()); } }
/** * Produce more handshake output. This is called in response to a * call to {@link javax.net.ssl.SSLEngine#wrap}, when the handshake * is still in progress. * * @param record The output record; the callee should put its output * handshake message (or a part of it) in the argument's * <code>fragment</code>, and should set the record length * appropriately. * @return An {@link SSLEngineResult} describing the result. */ public final HandshakeStatus handleOutput (ByteBuffer fragment) throws SSLException { if (!tasks.isEmpty()) return HandshakeStatus.NEED_TASK; int orig = fragment.position(); SSLEngineResult.HandshakeStatus status = implHandleOutput(fragment); if (doHash()) { if (Debug.DEBUG) logger.logv(Component.SSL_HANDSHAKE, "hashing output:\n{0}", Util.hexDump((ByteBuffer) fragment.duplicate().flip().position(orig), " >> ")); sha.update((ByteBuffer) fragment.duplicate().flip().position(orig)); md5.update((ByteBuffer) fragment.duplicate().flip().position(orig)); } return status; }
private void runDelegatedTasks(SSLEngineResult result) { if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { Runnable runnable; while ((runnable = _sslEngine.getDelegatedTask()) != null) { runnable.run(); } HandshakeStatus hsStatus = _sslEngine.getHandshakeStatus(); if (hsStatus == HandshakeStatus.NEED_TASK) { throw new RuntimeException("handshake shouldn't need additional tasks"); } } }
protected HandshakeStatus closeInboundCloseOutbound(SSLEngine engine) { try { engine.closeInbound(); } catch (SSLException e) { boolean debug = false; if (debug) { if (!isShuttingDown) { //if we are shutting down this is not an error. logger.trace("This engine was forced to close inbound, without having received the proper SSL/TLS close notification message from the peer, due to end of stream.", e); } } } engine.closeOutbound(); // After closeOutbound the engine will be set to WRAP state, in order to try to send a close message to the client. return engine.getHandshakeStatus(); }
private boolean processHandshake() throws IOException { final HandshakeStatus hs = engine.getHandshakeStatus(); logger.fine("processHandshake() hs = "+ hs); switch(hs) { case NEED_TASK: synchronousRunDelegatedTasks(); return processHandshake(); case NEED_UNWRAP: return handshakeUnwrap(); case NEED_WRAP: return handshakeWrap(); default: return false; } }
public SSLReadWriteSelectorHandler(SocketChannel sc, SelectionKey selectionKey, SSLContext sslContext) throws IOException { super(sc); sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(false); initialHSStatus = HandshakeStatus.NEED_UNWRAP; initialHSComplete = false; int netBBSize = sslEngine.getSession().getPacketBufferSize(); inNetBB = ByteBuffer.allocate(netBBSize); outNetBB = ByteBuffer.allocate(netBBSize); outNetBB.position(0); outNetBB.limit(0); int appBBSize = sslEngine.getSession().getApplicationBufferSize(); requestBB = ByteBuffer.allocate(appBBSize); while (!doHandshake(selectionKey)) ; }
/** * Runs all tasks needed to continue SSL work. * * @return Handshake status after running all tasks. */ private HandshakeStatus runTasks() { Runnable runnable; while ((runnable = sslEngine.getDelegatedTask()) != null) { if (log.isDebugEnabled()) log.debug("Running SSL engine task: " + runnable + '.'); runnable.run(); } if (log.isDebugEnabled()) log.debug("Finished running SSL engine tasks. HandshakeStatus: " + sslEngine.getHandshakeStatus()); return sslEngine.getHandshakeStatus(); }
private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) throws Exception { if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { Runnable runnable; while ((runnable = engine.getDelegatedTask()) != null) { log("\trunning delegated task..."); runnable.run(); } HandshakeStatus hsStatus = engine.getHandshakeStatus(); if (hsStatus == HandshakeStatus.NEED_TASK) { throw new Exception("handshake shouldn't need additional tasks"); } log("\tnew HandshakeStatus: " + hsStatus); } }
private static void log(String str, SSLEngineResult result) { if (!logging) { return; } if (resultOnce) { resultOnce = false; Logger.simple("The format of the SSLEngineResult is: \n" + "\t\"getStatus() / getHandshakeStatus()\" +\n" + "\t\"bytesConsumed() / bytesProduced()\"\n"); } HandshakeStatus hsStatus = result.getHandshakeStatus(); log(str + result.getStatus() + "/" + hsStatus + ", " + result.bytesConsumed() + "/" + result.bytesProduced() + " bytes"); if (hsStatus == HandshakeStatus.FINISHED) { log("\t...ready for application data"); } }
private void processHandshake(final HandshakeStatus status) throws SSLException { switch(status) { case NOT_HANDSHAKING: //Fix for older android versions, they dont send a finished case FINISHED: { if(handShakeStarted()) { finishHandshake(); } } break; case NEED_TASK: { runTasks(); } break; case NEED_WRAP: { client.write(IOUtils.EMPTY_BYTEBUFFER); } break; default: { } break; } }
private void log(String str, SSLEngineResult result) { if (!logging) { return; } if (resultOnce) { resultOnce = false; Log.info("The format of the SSLEngineResult is: \n" + "\t\"getStatus() / getHandshakeStatus()\" +\n" + "\t\"bytesConsumed() / bytesProduced()\"\n"); } HandshakeStatus hsStatus = result.getHandshakeStatus(); Log.info(str + result.getStatus() + "/" + hsStatus + ", " + result.bytesConsumed() + "/" + result.bytesProduced() + " bytes"); if (hsStatus == HandshakeStatus.FINISHED) { Log.info("\t...ready for application data"); } }
synchronized void writeRecord(EngineOutputRecord outputRecord, MAC writeMAC, CipherBox writeCipher) throws IOException { /* * Only output if we're still open. */ if (outboundClosed) { throw new IOException("writer side was already closed."); } outputRecord.write(writeMAC, writeCipher); /* * Did our handshakers notify that we just sent the * Finished message? * * Add an "I'm finished" message to the queue. */ if (outputRecord.isFinishedMsg()) { outboundList.addLast(HandshakeStatus.FINISHED); } }
/** * This is used to perform the read part of the negotiation. The * read part is where the other side sends information where it * is consumed and is used to determine what action to take. * Typically it is the SSL engine that determines what action is * to be taken depending on the data send from the other side. * * @param count this is the number of times a read can repeat * * @return the next action that should be taken by the handshake */ private Status read(int count) throws IOException { while(count > 0) { SSLEngineResult result = engine.unwrap(input, output); HandshakeStatus status = result.getHandshakeStatus(); switch(status) { case NOT_HANDSHAKING: return COMMIT; case NEED_WRAP: return PRODUCE; case FINISHED: case NEED_UNWRAP: return read(count-1); case NEED_TASK: execute(); } } return CONSUME; }
/** * Attempts to decode SSL/TLS network data into a subsequence of plaintext application data * buffers. Depending on the state of the TLSWrapper, this method may consume network data * without producing any application data (for example, it may consume handshake data.) * * If this TLSWrapper has not yet started its initial handshake, this method will automatically * start the handshake. * * @param net a ByteBuffer containing inbound network data * @param app a ByteBuffer to hold inbound application data * @return a ByteBuffer containing inbound application data * @throws SSLException A problem was encountered while processing the data that caused the * TLSHandler to abort. */ public ByteBuffer unwrap(ByteBuffer net, ByteBuffer app) throws SSLException { ByteBuffer out = app; out = resizeApplicationBuffer(out);// guarantees enough room for unwrap try { tlsEngineResult = tlsEngine.unwrap( net, out ); } catch ( SSLException e ) { if ( e.getMessage().startsWith( "Unsupported record version Unknown-" ) ) { throw new SSLException( "We appear to have received plain text data where we expected encrypted data. A common cause for this is a peer sending us a plain-text error message when it shouldn't send a message, but close the socket instead).", e ); } else { throw e; } } log("server unwrap: ", tlsEngineResult); if (tlsEngineResult.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { // If the result indicates that we have outstanding tasks to do, go // ahead and run them in this thread. doTasks(); } return out; }
/** * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} **/ private synchronized ByteBuffer unwrap() throws SSLException { int rem; //There are some ssl test suites, which get around the selector.select() call, which cause an infinite unwrap and 100% cpu usage (see #459 and #458) if(readEngineResult.getStatus() == SSLEngineResult.Status.CLOSED && sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING){ try { close(); } catch (IOException e) { //Not really interesting } } do { rem = inData.remaining(); readEngineResult = sslEngine.unwrap( inCrypt, inData ); } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); inData.flip(); return inData; }