FileDocCategorySizeDatePackage
ChannelIOSecure.javaAPI DocSun JDK 1.5.0 Example17074Sat Jan 08 15:08:43 GMT 2005None

ChannelIOSecure

public class ChannelIOSecure extends ChannelIO
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

Fields Summary
private SSLEngine
sslEngine
private int
appBBSize
private int
netBBSize
private ByteBuffer
inNetBB
private ByteBuffer
outNetBB
private static ByteBuffer
hsBB
private ByteBuffer
fileChannelBB
private HandshakeStatus
initialHSStatus
private boolean
initialHSComplete
private boolean
shutdown
Constructors Summary
protected ChannelIOSecure(SocketChannel sc, boolean blocking, SSLContext sslc)


    /*
     * Constructor for a secure ChannelIO variant.
     */
        
	        
	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;

	netBBSize = sslEngine.getSession().getPacketBufferSize();
	inNetBB = ByteBuffer.allocate(netBBSize);
	outNetBB = ByteBuffer.allocate(netBBSize);
	outNetBB.position(0);
	outNetBB.limit(0);
    
Methods Summary
booleandataFlush()

	boolean fileFlushed = true;

	if ((fileChannelBB != null) && fileChannelBB.hasRemaining()) {
	    doWrite(fileChannelBB);
	    fileFlushed = !fileChannelBB.hasRemaining();
	} else if (outNetBB.hasRemaining()) {
	    tryFlush(outNetBB);
	}

	return (fileFlushed && !outNetBB.hasRemaining());
    
booleandoHandshake()

	return doHandshake(null);
    
booleandoHandshake(java.nio.channels.SelectionKey sk)


	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) {
		/*
		 * 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;
    
private javax.net.ssl.SSLEngineResult$HandshakeStatusdoTasks()


	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();
    
private intdoWrite(java.nio.ByteBuffer src)

	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;
    
static ChannelIOSecuregetInstance(java.nio.channels.SocketChannel sc, boolean blocking, javax.net.ssl.SSLContext sslc)


	ChannelIOSecure cio = new ChannelIOSecure(sc, blocking, sslc);

	cio.appBBSize = cio.sslEngine.getSession().getApplicationBufferSize();
	cio.requestBB = ByteBuffer.allocate(cio.appBBSize);

	return cio;
    
intread()

	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();    // guarantees enough 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_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);

	return (requestBB.position() - pos);
    
protected voidresizeRequestBB()

	resizeRequestBB(appBBSize);
    
booleanshutdown()


	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));
    
longtransferTo(java.nio.channels.FileChannel fc, long pos, long len)


	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;
    
private booleantryFlush(java.nio.ByteBuffer bb)

	super.write(bb);
	return !bb.hasRemaining();
    
intwrite(java.nio.ByteBuffer src)


	if (!initialHSComplete) {
	    throw new IllegalStateException();
	}

	return doWrite(src);