All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hibegin.http.server.handler.SslReadWriteSelectorHandler Maven / Gradle / Ivy

Go to download

Simple, flexible, less dependent, more extended. Less memory footprint, can quickly build Web project. Can quickly run embedded, Android devices

There is a newer version: 0.3.162
Show newest version
package com.hibegin.http.server.handler;
/*
 * @(#)ChannelIOSecure.java	1.2 04/07/26
 *
 * Copyright (c) 2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 *
 * -Redistribution 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 Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

import com.hibegin.common.util.BytesUtil;
import com.hibegin.common.util.LoggerUtil;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A helper class which performs I/O using the SSLEngine API.
 * 

* 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. *

*

 * Application Data
 * src      requestBB
 * |           ^
 * |     |     |
 * v     |     |
 * +----+-----|-----+----+
 * |          |          |
 * |       SSL|Engine    |
 * wrap()  |          |          |  unwrap()
 * | OUTBOUND | INBOUND  |
 * |          |          |
 * +----+-----|-----+----+
 * |     |     ^
 * |     |     |
 * v           |
 * outNetBB     inNetBB
 * Net data
 * 
*

* 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. *

* 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. *

* 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. *

* There's lots of room for enhancements and improvement in this example. *

* 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 * @version 1.2, 04/07/26 */ public class SslReadWriteSelectorHandler extends PlainReadWriteSelectorHandler { private static final Logger LOGGER = LoggerUtil.getLogger(SslReadWriteSelectorHandler.class); /** * 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 final ByteBuffer hsBB = ByteBuffer.allocate(0); private final SSLEngine sslEngine; /** * All I/O goes through these buffers. *

* It might be nice to use a cache of ByteBuffers so we're * not alloc/dealloc'ing ByteBuffer's for each new SSLEngine. *

* We use our superclass' requestBB for our application input buffer. * Outbound application data is supplied to us by our callers. */ private final ByteBuffer inNetBB; private final ByteBuffer outNetBB; /** * 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. */ public SslReadWriteSelectorHandler(SocketChannel sc, SelectionKey selectionKey, SSLContext sslContext, int maxRequestBbSize) throws IOException { super(sc, maxRequestBbSize); 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)) { } } /** * Writes bb to the SocketChannel. *

* Returns true when the ByteBuffer has no remaining data. */ private boolean tryFlush(ByteBuffer bb) throws IOException { sc.write(bb); return !bb.hasRemaining(); } /** * Perform any handshaking processing. *

* If a SelectionKey is passed, register for selectable * operations. *

* In the blocking case, our caller will keep calling us until * we finish the handshake. Our reads/writes will block as expected. *

* 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. *

* return: * true when handshake is done. * false while handshake is in progress */ boolean doHandshake(SelectionKey sk) throws IOException { SSLEngineResult result; if (initialHSComplete) { return true; } /* * 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) { /* * Don't need to resize requestBB, since no app data should * be generated here. */ 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: /* * Need to go reread the Channel for more data. */ if (sk != null) { sk.interestOps(SelectionKey.OP_READ); } break needIO; default: // BUFFER_OVERFLOW/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. *

* If we run out of data, we'll return to our caller (possibly using * a Selector) to get notification that more is available. *

* Each call to this method will perform at most one underlying read(). */ @Override public ByteBuffer handleRead() throws IOException { readLock.lock(); try { checkRequestBB(); SSLEngineResult result; if (!initialHSComplete) { throw new IllegalStateException(); } int pos = requestBB.position(); if (sc.read(inNetBB) == -1) { // probably throws exception sslEngine.closeInbound(); throw new EOFException(); } do { // guarantees enough room for unwrap resizeRequestBB(inNetBB.remaining()); 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_UNDERFLOW: 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); int readLength = requestBB.position() - pos; ByteBuffer byteBuffer = ByteBuffer.allocate(readLength); byteBuffer.put(BytesUtil.subBytes(requestBB.array(), pos, readLength)); return byteBuffer; } catch (IOException e) { close(); throw e; } finally { readLock.unlock(); } } /** * Try to flush out any existing outbound data, then try to wrap * anything new contained in the src buffer. *

* 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(); if (result.getStatus() == Status.OK) { if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { doTasks(); } } else { 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. */ tryFlush(src); if (outNetBB.hasRemaining()) { tryFlush(outNetBB); } return retValue; } /** * Flush any remaining data. *

* Return true when the fileChannelBB and outNetBB are empty. */ private 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. *

* Close out the SSLEngine if not already done so, then * wrap our outgoing close_notify message and try to send it on. *

* Return true when we're done passing the shutdown messsages. */ private 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)); } @Override public void handleWrite(ByteBuffer byteBuffer) throws IOException { writeLock.lock(); try { while (byteBuffer.hasRemaining() && sc.isOpen()) { int len = doWrite(byteBuffer); if (len < 0) { throw new EOFException(); } } } finally { writeLock.unlock(); } } @Override public void close() { try { while (!dataFlush()) { } do { } while (!shutdown()); super.close(); } catch (IOException e) { LOGGER.log(Level.SEVERE, "", e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy