/* * Begin the shutdown process. * <P> * Close out the SSLEngine if not already done so, then * wrap our outgoing close_notify message and try to send it on. * <P> * Return true when we're done passing the shutdown messsages. */ boolean shutdown() throws IOException { if (!shutdown) { sslEngine.closeOutbound(); shutdown = true; } if (outNetBB.hasRemaining() && tryFlush(outNetBB)) { return false; } /* * By RFC 2616, we can "fire and forget" our close_notify * message, so that's what we'll do here. */ outNetBB.clear(); SSLEngineResult result = sslEngine.wrap(hsBB, outNetBB); if (result.getStatus() != Status.CLOSED) { throw new SSLException("Improper close state"); } outNetBB.flip(); /* * We won't wait for a select here, but if this doesn't work, * we'll cycle back through on the next select. */ if (outNetBB.hasRemaining()) { tryFlush(outNetBB); } return (!outNetBB.hasRemaining() && (result.getHandshakeStatus() != HandshakeStatus.NEED_WRAP)); }
/* * Read the channel for more information, then unwrap the * (hopefully application) data we get. * <P> * If we run out of data, we'll return to our caller (possibly using * a Selector) to get notification that more is available. * <P> * Each call to this method will perform at most one underlying read(). */ int read() throws IOException { SSLEngineResult result; if (!initialHSComplete) { throw new IllegalStateException(); } int pos = requestBB.position(); if (sc.read(inNetBB) == -1) { sslEngine.closeInbound(); // probably throws exception return -1; } do { resizeRequestBB(); // expected room for unwrap inNetBB.flip(); result = sslEngine.unwrap(inNetBB, requestBB); inNetBB.compact(); /* * Could check here for a renegotation, but we're only * doing a simple read/write, and won't have enough state * transitions to do a complete handshake, so ignore that * possibility. */ switch (result.getStatus()) { case BUFFER_OVERFLOW: // Reset the application buffer size. appBBSize = sslEngine.getSession().getApplicationBufferSize(); break; case BUFFER_UNDERFLOW: // Resize buffer if needed. netBBSize = sslEngine.getSession().getPacketBufferSize(); if (netBBSize > inNetBB.capacity()) { resizeResponseBB(); break; // break, next read will support larger buffer. } case OK: if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { doTasks(); } break; default: throw new IOException("sslEngine error during data read: " + result.getStatus()); } } while ((inNetBB.position() != 0) && result.getStatus() != Status.BUFFER_UNDERFLOW); return (requestBB.position() - pos); }
/* * Do all the outstanding handshake tasks in the current Thread. */ private SSLEngineResult.HandshakeStatus doTasks() { Runnable runnable; /* * We could run this in a separate thread, but * do in the current for now. */ while ((runnable = sslEngine.getDelegatedTask()) != null) { runnable.run(); } return sslEngine.getHandshakeStatus(); }
/* * If the result indicates that we have outstanding tasks to do, * go ahead and run them in this thread. */ 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); } }
/* * Using the SSLContext created during object creation, * create/configure the SSLEngines we'll use for this test. */ private void createSSLEngines() throws Exception { /* * Configure the serverEngine to act as a server in the SSL/TLS * handshake. Also, require SSL client authentication. */ serverEngine = sslc.createSSLEngine(); serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); /* * Similar to above, but using client mode instead. */ clientEngine = sslc.createSSLEngine("client", 80); clientEngine.setUseClientMode(true); }
/* * Create and size the buffers appropriately. */ private void createBuffers() { /* * We'll assume the buffer sizes are the same * between client and server. */ SSLSession session = clientEngine.getSession(); int appBufferMax = session.getApplicationBufferSize(); int netBufferMax = session.getPacketBufferSize(); /* * We'll make the input buffers a bit bigger than the max needed * size, so that unwrap()s following a successful data transfer * won't generate BUFFER_OVERFLOWS. * * We'll use a mix of direct and indirect ByteBuffers for * tutorial purposes only. In reality, only use direct * ByteBuffers when they give a clear performance enhancement. */ clientIn = ByteBuffer.allocate(appBufferMax + 50); serverIn = ByteBuffer.allocate(appBufferMax + 50); cTOs = ByteBuffer.allocateDirect(netBufferMax); sTOc = ByteBuffer.allocateDirect(netBufferMax); clientOut = ByteBuffer.wrap("Hi Server, I'm Client".getBytes()); serverOut = ByteBuffer.wrap("Hello Client, I'm Server".getBytes()); }
private void checkResult( SSLEngine engine, SSLEngineResult result, HandshakeStatus rqdHsStatus, boolean consumed, boolean produced) throws Exception { HandshakeStatus hsStatus = result.getHandshakeStatus(); if (hsStatus == HandshakeStatus.NEED_TASK) { Runnable runnable; while ((runnable = engine.getDelegatedTask()) != null) { runnable.run(); } hsStatus = engine.getHandshakeStatus(); } if (hsStatus != rqdHsStatus) { throw new Exception("Required " + rqdHsStatus + ", got " + hsStatus); } int bc = result.bytesConsumed(); int bp = result.bytesProduced(); if (consumed) { if (bc <= 0) { throw new Exception("Should have consumed bytes"); } } else { if (bc > 0) { throw new Exception("Should not have consumed bytes"); } } if (produced) { if (bp <= 0) { throw new Exception("Should have produced bytes"); } } else { if (bp > 0) { throw new Exception("Should not have produced bytes"); } } }
/* * Constructor for a secure ChannelIO variant. */ protected ChannelIOSecure(SocketChannel sc, boolean blocking, SSLContext sslc) throws IOException { super(sc, blocking); /* * We're a server, so no need to use host/port variant. * * The first call for a server is a NEED_UNWRAP. */ sslEngine = sslc.createSSLEngine(); sslEngine.setUseClientMode(false); initialHSStatus = HandshakeStatus.NEED_UNWRAP; initialHSComplete = false; // Create a buffer using the normal expected packet size we'll // be getting. This may change, depending on the peer's // SSL implementation. netBBSize = sslEngine.getSession().getPacketBufferSize(); inNetBB = ByteBuffer.allocate(netBBSize); outNetBB = ByteBuffer.allocate(netBBSize); outNetBB.position(0); outNetBB.limit(0); }
/* * Try to flush out any existing outbound data, then try to wrap * anything new contained in the src buffer. * <P> * Return the number of bytes actually consumed from the buffer, * but the data may actually be still sitting in the output buffer, * waiting to be flushed. */ private int doWrite(ByteBuffer src) throws IOException { int retValue = 0; if (outNetBB.hasRemaining() && !tryFlush(outNetBB)) { return retValue; } /* * The data buffer is empty, we can reuse the entire buffer. */ outNetBB.clear(); SSLEngineResult result = sslEngine.wrap(src, outNetBB); retValue = result.bytesConsumed(); outNetBB.flip(); switch (result.getStatus()) { case OK: if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { doTasks(); } break; default: throw new IOException("sslEngine error during data write: " + result.getStatus()); } /* * Try to flush the data, regardless of whether or not * it's been selected. Odds of a write buffer being full * is less than a read buffer being empty. */ if (outNetBB.hasRemaining()) { tryFlush(outNetBB); } return retValue; }
private static boolean isEngineClosed(SSLEngine engine) { return (engine.isOutboundDone() && engine.isInboundDone()); }
/* * Run the test. * * Sit in a tight loop, both engines calling wrap/unwrap regardless * of whether data is available or not. We do this until both engines * report back they are closed. * * The main loop handles all of the I/O phases of the SSLEngine's * lifetime: * * initial handshaking * application data transfer * engine closing * * One could easily separate these phases into separate * sections of code. */ private SSLSession runTest() throws Exception { boolean dataDone = false; createSSLEngines(); createBuffers(); SSLEngineResult clientResult; // results from client's last operation SSLEngineResult serverResult; // results from server's last operation /* * Examining the SSLEngineResults could be much more involved, * and may alter the overall flow of the application. * * For example, if we received a BUFFER_OVERFLOW when trying * to write to the output pipe, we could reallocate a larger * pipe, but instead we wait for the peer to drain it. */ while (!isEngineClosed(clientEngine) || !isEngineClosed(serverEngine)) { log("================"); clientResult = clientEngine.wrap(clientOut, cTOs); log("client wrap: ", clientResult); runDelegatedTasks(clientResult, clientEngine); serverResult = serverEngine.wrap(serverOut, sTOc); log("server wrap: ", serverResult); runDelegatedTasks(serverResult, serverEngine); cTOs.flip(); sTOc.flip(); log("----"); clientResult = clientEngine.unwrap(sTOc, clientIn); log("client unwrap: ", clientResult); runDelegatedTasks(clientResult, clientEngine); serverResult = serverEngine.unwrap(cTOs, serverIn); log("server unwrap: ", serverResult); runDelegatedTasks(serverResult, serverEngine); cTOs.compact(); sTOc.compact(); /* * After we've transfered all application data between the client * and server, we close the clientEngine's outbound stream. * This generates a close_notify handshake message, which the * server engine receives and responds by closing itself. */ if (!dataDone && (clientOut.limit() == serverIn.position()) && (serverOut.limit() == clientIn.position())) { /* * A sanity check to ensure we got what was sent. */ checkTransfer(serverOut, clientIn); checkTransfer(clientOut, serverIn); log("\tClosing clientEngine's *OUTBOUND*..."); clientEngine.closeOutbound(); dataDone = true; } } return clientEngine.getSession(); }
private SSLSession runRehandshake() throws Exception { log("\n\n=============================================="); log("Staring actual test."); createSSLEngines(); createBuffers(); SSLEngineResult result; log("Client's ClientHello"); checkResult( clientEngine, clientEngine.wrap(clientOut, cTOs), HandshakeStatus.NEED_UNWRAP, false, true); cTOs.flip(); checkResult( serverEngine, serverEngine.unwrap(cTOs, serverIn), HandshakeStatus.NEED_WRAP, true, false); cTOs.compact(); log("Server's ServerHello/ServerHelloDone"); checkResult( serverEngine, serverEngine.wrap(serverOut, sTOc), HandshakeStatus.NEED_WRAP, false, true); sTOc.flip(); checkResult( clientEngine, clientEngine.unwrap(sTOc, clientIn), HandshakeStatus.NEED_UNWRAP, true, false); sTOc.compact(); log("Server's CCS"); checkResult( serverEngine, serverEngine.wrap(serverOut, sTOc), HandshakeStatus.NEED_WRAP, false, true); sTOc.flip(); checkResult( clientEngine, clientEngine.unwrap(sTOc, clientIn), HandshakeStatus.NEED_UNWRAP, true, false); sTOc.compact(); log("Server's FINISHED"); checkResult( serverEngine, serverEngine.wrap(serverOut, sTOc), HandshakeStatus.NEED_UNWRAP, false, true); sTOc.flip(); checkResult( clientEngine, clientEngine.unwrap(sTOc, clientIn), HandshakeStatus.NEED_WRAP, true, false); sTOc.compact(); log("Client's CCS"); checkResult( clientEngine, clientEngine.wrap(clientOut, cTOs), HandshakeStatus.NEED_WRAP, false, true); cTOs.flip(); checkResult( serverEngine, serverEngine.unwrap(cTOs, serverIn), HandshakeStatus.NEED_UNWRAP, true, false); cTOs.compact(); log("Client's FINISHED should trigger FINISHED messages all around."); checkResult( clientEngine, clientEngine.wrap(clientOut, cTOs), HandshakeStatus.FINISHED, false, true); cTOs.flip(); checkResult( serverEngine, serverEngine.unwrap(cTOs, serverIn), HandshakeStatus.FINISHED, true, false); cTOs.compact(); return clientEngine.getSession(); }
/* * Perform any handshaking processing. * <P> * If a SelectionKey is passed, register for selectable * operations. * <P> * In the blocking case, our caller will keep calling us until * we finish the handshake. Our reads/writes will block as expected. * <P> * In the non-blocking case, we just received the selection notification * that this channel is ready for whatever the operation is, so give * it a try. * <P> * return: * true when handshake is done. * false while handshake is in progress */ boolean doHandshake(SelectionKey sk) throws IOException { SSLEngineResult result; if (initialHSComplete) { return initialHSComplete; } /* * Flush out the outgoing buffer, if there's anything left in * it. */ if (outNetBB.hasRemaining()) { if (!tryFlush(outNetBB)) { return false; } // See if we need to switch from write to read mode. switch (initialHSStatus) { /* * Is this the last buffer? */ case FINISHED: initialHSComplete = true; // Fall-through to reregister need for a Read. case NEED_UNWRAP: if (sk != null) { sk.interestOps(SelectionKey.OP_READ); } break; } return initialHSComplete; } switch (initialHSStatus) { case NEED_UNWRAP: if (sc.read(inNetBB) == -1) { sslEngine.closeInbound(); return initialHSComplete; } needIO: while (initialHSStatus == HandshakeStatus.NEED_UNWRAP) { resizeRequestBB(); // expected room for unwrap inNetBB.flip(); result = sslEngine.unwrap(inNetBB, requestBB); inNetBB.compact(); initialHSStatus = result.getHandshakeStatus(); switch (result.getStatus()) { case OK: switch (initialHSStatus) { case NOT_HANDSHAKING: throw new IOException("Not handshaking during initial handshake"); case NEED_TASK: initialHSStatus = doTasks(); break; case FINISHED: initialHSComplete = true; break needIO; } break; case BUFFER_UNDERFLOW: // Resize buffer if needed. netBBSize = sslEngine.getSession().getPacketBufferSize(); if (netBBSize > inNetBB.capacity()) { resizeResponseBB(); } /* * Need to go reread the Channel for more data. */ if (sk != null) { sk.interestOps(SelectionKey.OP_READ); } break needIO; case BUFFER_OVERFLOW: // Reset the application buffer size. appBBSize = sslEngine.getSession().getApplicationBufferSize(); break; default: // CLOSED: throw new IOException("Received" + result.getStatus() + "during initial handshaking"); } } // "needIO" block. /* * Just transitioned from read to write. */ if (initialHSStatus != HandshakeStatus.NEED_WRAP) { break; } // Fall through and fill the write buffers. case NEED_WRAP: /* * The flush above guarantees the out buffer to be empty */ outNetBB.clear(); result = sslEngine.wrap(hsBB, outNetBB); outNetBB.flip(); initialHSStatus = result.getHandshakeStatus(); switch (result.getStatus()) { case OK: if (initialHSStatus == HandshakeStatus.NEED_TASK) { initialHSStatus = doTasks(); } if (sk != null) { sk.interestOps(SelectionKey.OP_WRITE); } break; default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED: throw new IOException("Received" + result.getStatus() + "during initial handshaking"); } break; default: // NOT_HANDSHAKING/NEED_TASK/FINISHED throw new RuntimeException("Invalid Handshaking State" + initialHSStatus); } // switch return initialHSComplete; }