| /* |
| * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * - Neither the name of Oracle nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* |
| * This source code is provided to illustrate the usage of a given feature |
| * or technique and has been deliberately simplified. Additional steps |
| * required for a production-quality application, such as security checks, |
| * input validation and proper error handling, might not be present in |
| * this sample code. |
| */ |
| |
| |
| import java.io.*; |
| import java.nio.*; |
| import java.nio.channels.*; |
| import javax.net.ssl.*; |
| import javax.net.ssl.SSLEngineResult.*; |
| |
| /** |
| * A helper class which performs I/O using the SSLEngine API. |
| * <P> |
| * Each connection has a SocketChannel and a SSLEngine that is |
| * used through the lifetime of the Channel. We allocate byte buffers |
| * for use as the outbound and inbound network buffers. |
| * |
| * <PRE> |
| * Application Data |
| * src requestBB |
| * | ^ |
| * | | | |
| * v | | |
| * +----+-----|-----+----+ |
| * | | | |
| * | SSL|Engine | |
| * wrap() | | | unwrap() |
| * | OUTBOUND | INBOUND | |
| * | | | |
| * +----+-----|-----+----+ |
| * | | ^ |
| * | | | |
| * v | |
| * outNetBB inNetBB |
| * Net data |
| * </PRE> |
| * |
| * These buffers handle all of the intermediary data for the SSL |
| * connection. To make things easy, we'll require outNetBB be |
| * completely flushed before trying to wrap any more data, but we |
| * could certainly remove that restriction by using larger buffers. |
| * <P> |
| * There are many, many ways to handle compute and I/O strategies. |
| * What follows is a relatively simple one. The reader is encouraged |
| * to develop the strategy that best fits the application. |
| * <P> |
| * In most of the non-blocking operations in this class, we let the |
| * Selector tell us when we're ready to attempt an I/O operation (by the |
| * application repeatedly calling our methods). Another option would be |
| * to attempt the operation and return from the method when no forward |
| * progress can be made. |
| * <P> |
| * There's lots of room for enhancements and improvement in this example. |
| * <P> |
| * We're checking for SSL/TLS end-of-stream truncation attacks via |
| * sslEngine.closeInbound(). When you reach the end of a input stream |
| * via a read() returning -1 or an IOException, we call |
| * sslEngine.closeInbound() to signal to the sslEngine that no more |
| * input will be available. If the peer's close_notify message has not |
| * yet been received, this could indicate a trucation attack, in which |
| * an attacker is trying to prematurely close the connection. The |
| * closeInbound() will throw an exception if this condition were |
| * present. |
| * |
| * @author Brad R. Wetmore |
| * @author Mark Reinhold |
| */ |
| class ChannelIOSecure extends ChannelIO { |
| |
| private SSLEngine sslEngine = null; |
| |
| private int appBBSize; |
| private int netBBSize; |
| |
| /* |
| * All I/O goes through these buffers. |
| * <P> |
| * It might be nice to use a cache of ByteBuffers so we're |
| * not alloc/dealloc'ing ByteBuffer's for each new SSLEngine. |
| * <P> |
| * We use our superclass' requestBB for our application input buffer. |
| * Outbound application data is supplied to us by our callers. |
| */ |
| private ByteBuffer inNetBB; |
| private ByteBuffer outNetBB; |
| |
| /* |
| * An empty ByteBuffer for use when one isn't available, say |
| * as a source buffer during initial handshake wraps or for close |
| * operations. |
| */ |
| private static ByteBuffer hsBB = ByteBuffer.allocate(0); |
| |
| /* |
| * The FileChannel we're currently transferTo'ing (reading). |
| */ |
| private ByteBuffer fileChannelBB = null; |
| |
| /* |
| * During our initial handshake, keep track of the next |
| * SSLEngine operation that needs to occur: |
| * |
| * NEED_WRAP/NEED_UNWRAP |
| * |
| * Once the initial handshake has completed, we can short circuit |
| * handshake checks with initialHSComplete. |
| */ |
| private HandshakeStatus initialHSStatus; |
| private boolean initialHSComplete; |
| |
| /* |
| * We have received the shutdown request by our caller, and have |
| * closed our outbound side. |
| */ |
| private boolean shutdown = false; |
| |
| /* |
| * 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); |
| } |
| |
| /* |
| * Static factory method for creating a secure ChannelIO object. |
| * <P> |
| * We need to allocate different sized application data buffers |
| * based on whether we're secure or not. We can't determine |
| * this until our sslEngine is created. |
| */ |
| static ChannelIOSecure getInstance(SocketChannel sc, boolean blocking, |
| SSLContext sslc) throws IOException { |
| |
| ChannelIOSecure cio = new ChannelIOSecure(sc, blocking, sslc); |
| |
| // Create a buffer using the normal expected application size we'll |
| // be getting. This may change, depending on the peer's |
| // SSL implementation. |
| cio.appBBSize = cio.sslEngine.getSession().getApplicationBufferSize(); |
| cio.requestBB = ByteBuffer.allocate(cio.appBBSize); |
| |
| return cio; |
| } |
| |
| /* |
| * Calls up to the superclass to adjust the buffer size |
| * by an appropriate increment. |
| */ |
| protected void resizeRequestBB() { |
| resizeRequestBB(appBBSize); |
| } |
| |
| /* |
| * Adjust the inbount network buffer to an appropriate size. |
| */ |
| private void resizeResponseBB() { |
| ByteBuffer bb = ByteBuffer.allocate(netBBSize); |
| inNetBB.flip(); |
| bb.put(inNetBB); |
| inNetBB = bb; |
| } |
| |
| /* |
| * Writes bb to the SocketChannel. |
| * <P> |
| * Returns true when the ByteBuffer has no remaining data. |
| */ |
| private boolean tryFlush(ByteBuffer bb) throws IOException { |
| super.write(bb); |
| return !bb.hasRemaining(); |
| } |
| |
| /* |
| * Perform any handshaking processing. |
| * <P> |
| * This variant is for Servers without SelectionKeys (e.g. |
| * blocking). |
| */ |
| boolean doHandshake() throws IOException { |
| return doHandshake(null); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * 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(); |
| } |
| |
| /* |
| * 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); |
| } |
| |
| /* |
| * Try to write out as much as possible from the src buffer. |
| */ |
| int write(ByteBuffer src) throws IOException { |
| |
| if (!initialHSComplete) { |
| throw new IllegalStateException(); |
| } |
| |
| return doWrite(src); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * Perform a FileChannel.TransferTo on the socket channel. |
| * <P> |
| * We have to copy the data into an intermediary app ByteBuffer |
| * first, then send it through the SSLEngine. |
| * <P> |
| * We return the number of bytes actually read out of the |
| * filechannel. However, the data may actually be stuck |
| * in the fileChannelBB or the outNetBB. The caller |
| * is responsible for making sure to call dataFlush() |
| * before shutting down. |
| */ |
| long transferTo(FileChannel fc, long pos, long len) throws IOException { |
| |
| if (!initialHSComplete) { |
| throw new IllegalStateException(); |
| } |
| |
| if (fileChannelBB == null) { |
| fileChannelBB = ByteBuffer.allocate(appBBSize); |
| fileChannelBB.limit(0); |
| } |
| |
| fileChannelBB.compact(); |
| int fileRead = fc.read(fileChannelBB); |
| fileChannelBB.flip(); |
| |
| /* |
| * We ignore the return value here, we return the |
| * number of bytes actually consumed from the the file. |
| * We'll flush the output buffer before we start shutting down. |
| */ |
| doWrite(fileChannelBB); |
| |
| return fileRead; |
| } |
| |
| /* |
| * Flush any remaining data. |
| * <P> |
| * Return true when the fileChannelBB and outNetBB are empty. |
| */ |
| boolean dataFlush() throws IOException { |
| boolean fileFlushed = true; |
| |
| if ((fileChannelBB != null) && fileChannelBB.hasRemaining()) { |
| doWrite(fileChannelBB); |
| fileFlushed = !fileChannelBB.hasRemaining(); |
| } else if (outNetBB.hasRemaining()) { |
| tryFlush(outNetBB); |
| } |
| |
| return (fileFlushed && !outNetBB.hasRemaining()); |
| } |
| |
| /* |
| * 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)); |
| } |
| |
| /* |
| * close() is not overridden |
| */ |
| } |