/*
*
* 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
*
* - 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.
*/
/**
* 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>
* 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
*/
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
* <P>
* We use our superclass' requestBB for our application input buffer.
* Outbound application data is supplied to us by our callers.
*/
/*
* An empty ByteBuffer for use when one isn't available, say
* as a source buffer during initial handshake wraps or for close
* operations.
*/
/*
* The FileChannel we're currently transferTo'ing (reading).
*/
/*
* During our initial handshake, keep track of the next
* SSLEngine operation that needs to occur:
*
*
* Once the initial handshake has completed, we can short circuit
* handshake checks with initialHSComplete.
*/
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.
*/
/*
*
* The first call for a server is a NEED_UNWRAP.
*/
sslEngine.setUseClientMode(false);
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.
}
/*
* 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.
*/
// Create a buffer using the normal expected application size we'll
// be getting. This may change, depending on the peer's
// SSL implementation.
return cio;
}
/*
* Calls up to the superclass to adjust the buffer size
* by an appropriate increment.
*/
protected void resizeRequestBB() {
}
/*
* Adjust the inbount network buffer to an appropriate size.
*/
private void resizeResponseBB() {
}
/*
* Writes bb to the SocketChannel.
* <P>
* Returns true when the ByteBuffer has no remaining data.
*/
return !bb.hasRemaining();
}
/*
* Perform any handshaking processing.
* <P>
* This variant is for Servers without SelectionKeys (e.g.
* blocking).
*/
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
* <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
*/
if (initialHSComplete) {
return initialHSComplete;
}
/*
* Flush out the outgoing buffer, if there's anything left in
* it.
*/
if (outNetBB.hasRemaining()) {
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:
}
break;
}
return initialHSComplete;
}
switch (initialHSStatus) {
case NEED_UNWRAP:
return initialHSComplete;
}
resizeRequestBB(); // expected room for unwrap
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.
}
/*
* Need to go reread the Channel for more data.
*/
}
break needIO;
case BUFFER_OVERFLOW:
// Reset the application buffer size.
break;
default: //CLOSED:
"during initial handshaking");
}
} // "needIO" block.
/*
* Just transitioned from read to write.
*/
break;
}
// Fall through and fill the write buffers.
case NEED_WRAP:
/*
* The flush above guarantees the out buffer to be empty
*/
case OK:
initialHSStatus = doTasks();
}
}
break;
default: // BUFFER_OVERFLOW/BUFFER_UNDERFLOW/CLOSED:
"during initial handshaking");
}
break;
default: // NOT_HANDSHAKING/NEED_TASK/FINISHED
throw new RuntimeException("Invalid Handshaking State" +
} // switch
return initialHSComplete;
}
/*
* Do all the outstanding handshake tasks in the current Thread.
*/
/*
* We could run this in a separate thread, but
* do in the current for now.
*/
}
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().
*/
if (!initialHSComplete) {
throw new IllegalStateException();
}
return -1;
}
do {
resizeRequestBB(); // expected room for unwrap
/*
* Could check here for a renegotation, but we're only
* transitions to do a complete handshake, so ignore that
* possibility.
*/
case BUFFER_OVERFLOW:
// Reset the application buffer size.
break;
case BUFFER_UNDERFLOW:
// Resize buffer if needed.
break; // break, next read will support larger buffer.
}
case OK:
doTasks();
}
break;
default:
throw new IOException("sslEngine error during data read: " +
}
}
/*
* Try to write out as much as possible from the src buffer.
*/
if (!initialHSComplete) {
throw new IllegalStateException();
}
}
/*
* 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.
*/
int retValue = 0;
return retValue;
}
/*
* The data buffer is empty, we can reuse the entire buffer.
*/
case OK:
doTasks();
}
break;
default:
throw new IOException("sslEngine error during data write: " +
}
/*
* 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()) {
}
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.
*/
if (!initialHSComplete) {
throw new IllegalStateException();
}
if (fileChannelBB == null) {
}
/*
* 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.
*/
return fileRead;
}
/*
* Flush any remaining data.
* <P>
* Return true when the fileChannelBB and outNetBB are empty.
*/
boolean fileFlushed = true;
} else if (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.
*/
if (!shutdown) {
shutdown = true;
}
return false;
}
/*
* By RFC 2616, we can "fire and forget" our close_notify
* message, so that's what we'll do here.
*/
throw new SSLException("Improper close state");
}
/*
* 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()) {
}
return (!outNetBB.hasRemaining() &&
}
/*
* close() is not overridden
*/
}