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

com.sun.grizzly.SSLConnectorHandler Maven / Gradle / Ivy

/*
 * 
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2007-2008 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 */
package com.sun.grizzly;

import com.sun.grizzly.async.AsyncQueueDataProcessor;
import com.sun.grizzly.async.AsyncQueueReadUnit;
import com.sun.grizzly.async.AsyncQueueReadable;
import com.sun.grizzly.async.AsyncQueueWritable;
import com.sun.grizzly.async.AsyncQueueWriteUnit;
import com.sun.grizzly.async.AsyncReadCallbackHandler;
import com.sun.grizzly.async.AsyncReadCondition;
import com.sun.grizzly.async.AsyncWriteCallbackHandler;
import com.sun.grizzly.async.ByteBufferCloner;
import com.sun.grizzly.util.DefaultThreadPool;
import com.sun.grizzly.util.OutputWriter;
import com.sun.grizzly.util.SSLOutputWriter;
import com.sun.grizzly.util.SSLUtils;
import com.sun.grizzly.util.WorkerThreadImpl;
import java.io.EOFException;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
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.SSLException;

/**
 * 

* Non blocking SSL Connector Handler. The recommended way to use this class * is by creating an external Controller and share the same SelectorHandler * instance. *

* Recommended * ----------- *


 * Controller controller = new Controller();
 * // new SSLSelectorHandler(true) means the Selector will be used only
 * // for client operation (OP_READ, OP_WRITE, OP_CONNECT).
 * SSLSelectorHandler sslSelectorHandler = new SSLSelectorHandler(true);
 * controller.setSelectorHandler(sslSelectorHandler);
 * SSLConnectorHandler sslConnectorHandler = new SSLConnectorHandler();
 * sslConnectorHandler.connect(localhost,port, new SSLCallbackHandler(){...},
 *                             sslSelectorHandler);
 * SSLConnectorHandler sslConnectorHandler2 = new SSLConnectorHandler();
 * sslConnectorHandler2.connect(localhost,port, new SSLCallbackHandler(){...},
 *                             sslSelectorHandler);
 * 

* Not recommended (but still works) * --------------------------------- *


 * SSLConnectorHandler sslConnectorHandler = new SSLConnectorHandler();
 * sslConnectorHandler.connect(localhost,port);
 *
 * Internally, an new Controller will be created everytime connect(localhost,port)
 * is invoked, which has an impact on performance.
 *
 * As common comment: developer should be very careful if dealing directly with
 * SSLConnectorHandler's underlying socket channel! In most cases
 * there is no need to do this, but use read, write methods provided
 * by SSLConnectorHandler
 * 

* * @author Alexey Stashok * @author Jeanfrancois Arcand */ public class SSLConnectorHandler implements ConnectorHandler, AsyncQueueWritable, AsyncQueueReadable { /** * Default Logger. */ private static Logger logger = Logger.getLogger("grizzly"); /** * The underlying SSLSelectorHandler used to mange SelectionKeys. */ protected SSLSelectorHandler selectorHandler; /** * A {@link SSLCallbackHandler} handler invoked by the SSLSelectorHandler * when a non blocking operation is ready to be processed. */ private SSLCallbackHandler callbackHandler; /* * An empty ByteBuffer used for handshaking */ private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0); /** * Input buffer for reading encrypted data from channel */ private ByteBuffer securedInputBuffer; /** * Output buffer, which contains encrypted data ready for writing to channel */ private ByteBuffer securedOutputBuffer; /** * Buffer, where application data could be written during a asynchronous handshaking. * It is set when user application calls: SSLConnectorHandler.handshake(appDataBuffer) * and references appDataBuffer. */ private ByteBuffer asyncHandshakeBuffer; /** * The connection's SocketChannel. */ private SocketChannel socketChannel; /** * Default {@link SSLContext}, created on * top of default SSLConfiguration */ private static volatile SSLContext defaultSSLContext; /** * Is the connection established. */ private volatile boolean isConnected; /** * Is the handshake phase completed */ private volatile boolean isHandshakeDone; /** * The internal Controller used (in case not specified). */ private Controller controller; /** * IsConnected Latch related */ private CountDownLatch isConnectedLatch; /** * Are we creating a controller every run. */ private boolean isStandalone = false; /** * Is async handshake in progress */ private boolean isProcessingAsyncHandshake; /** * Result of last{@link SSLEngine} operation */ private SSLEngineResult sslLastOperationResult; /** * Current handshake status */ private SSLEngineResult.HandshakeStatus handshakeStatus; /** * Current{@link SSLEngine} status */ private SSLEngineResult.Status sslEngineStatus = null; /** * Are we creating a controller every run. */ private boolean delegateSSLTasks; /** * Connector's{@link SSLEngine} */ private SSLEngine sslEngine; /** * Connector's {@link SSLContext} */ private SSLContext sslContext; /** * SSL read postprocessor for AsyncQueueReadable */ private volatile AsyncQueueDataProcessor sslReadPostProcessor; /** * SSL write preprocessor for AsyncQueueWritable */ private volatile AsyncQueueDataProcessor sslWritePreProcessor; /** * Connector's write mode */ private boolean isAsyncWriteQueueMode; /** * Connector's read mode */ private boolean isAsyncReadQueueMode; private Object lock = new Object(); public SSLConnectorHandler() { this(defaultSSLContext); } public SSLConnectorHandler(SSLConfig sslConfig) { this(sslConfig.createSSLContext()); } public SSLConnectorHandler(SSLContext sslContext) { if (sslContext == null) { if (defaultSSLContext == null) { synchronized (SSLConnectorHandler.class) { if (defaultSSLContext == null) { defaultSSLContext = SSLConfig.DEFAULT_CONFIG.createSSLContext(); } } } sslContext = defaultSSLContext; } this.sslContext = sslContext; } public boolean getDelegateSSLTasks() { return delegateSSLTasks; } public void setDelegateSSLTasks(boolean delegateSSLTasks) { this.delegateSSLTasks = delegateSSLTasks; } /** * Connect to hostname:port. When an aysnchronous event happens (e.g * OP_READ or OP_WRITE), the {@link Controller} will invoke * the {@link CallbackHandler}. * @param remoteAddress remote address to connect * @param callbackHandler the handler invoked by its associated {@link SelectorHandler} when * a non blocking operation is ready to be handled. When null, all * read and write operation will be delegated to the default * {@link ProtocolChain} and its list of {@link ProtocolFilter} * . When null, this {@link ConnectorHandler} will create an instance of {@link DefaultCallbackHandler}. * @throws java.io.IOException */ public void connect(SocketAddress remoteAddress, SSLCallbackHandler callbackHandler) throws IOException { connect(remoteAddress, null, callbackHandler); } /** * Connect to hostname:port. When an aysnchronous event happens (e.g * OP_READ or OP_WRITE), the {@link Controller} will invoke * the {@link CallbackHandler}. * @param remoteAddress remote address to connect * @param localAddress local address to bind * @param callbackHandler the handler invoked by its associated {@link SelectorHandler} when * a non blocking operation is ready to be handled. When null, all * read and write operation will be delegated to the default * {@link ProtocolChain} and its list of {@link ProtocolFilter} * . When null, this {@link ConnectorHandler} will create an instance of {@link DefaultCallbackHandler}. * @throws java.io.IOException */ public void connect(SocketAddress remoteAddress, SocketAddress localAddress, SSLCallbackHandler callbackHandler) throws IOException { if (controller == null) { throw new IllegalStateException("Controller cannot be null"); } connect(remoteAddress, localAddress, callbackHandler, (SSLSelectorHandler) controller.getSelectorHandler(protocol())); } /** * Connect to hostname:port. When an aysnchronous event happens (e.g * OP_READ or OP_WRITE), the {@link Controller} will invoke * the {@link CallbackHandler}. * @param remoteAddress remote address to connect * @param callbackHandler the handler invoked by the Controller when * an non blocking operation is ready to be handled. * @param selectorHandler an instance of SelectorHandler. * @throws java.io.IOException */ public void connect(SocketAddress remoteAddress, SSLCallbackHandler callbackHandler, SSLSelectorHandler selectorHandler) throws IOException { connect(remoteAddress, null, callbackHandler, selectorHandler); } /** * Connect to hostname:port. When an aysnchronous event happens (e.g * OP_READ or OP_WRITE), the {@link Controller} will invoke * the {@link CallbackHandler}. * @param remoteAddress remote address to connect * @param localAddress local address to bin * @param callbackHandler the handler invoked by its associated {@link SelectorHandler} when * a non blocking operation is ready to be handled. When null, all * read and write operation will be delegated to the default * {@link ProtocolChain} and its list of {@link ProtocolFilter} * . When null, this {@link ConnectorHandler} will create an instance of {@link DefaultCallbackHandler}. * @param selectorHandler an instance of SelectorHandler. * @throws java.io.IOException */ public void connect(SocketAddress remoteAddress, SocketAddress localAddress, SSLCallbackHandler callbackHandler, SSLSelectorHandler selectorHandler) throws IOException { if (isConnected) { throw new AlreadyConnectedException(); } if (controller == null) { throw new IllegalStateException("Controller cannot be null"); } if (selectorHandler == null) { throw new IllegalStateException("SelectorHandler cannot be null"); } this.selectorHandler = selectorHandler; if (callbackHandler == null){ callbackHandler = new DefaultCallbackHandler(this); } else { this.callbackHandler = callbackHandler; } // Wait for the onConnect to be invoked. isConnectedLatch = new CountDownLatch(1); selectorHandler.connect(remoteAddress, localAddress, new SSLInternalCallbackHandler()); try { isConnectedLatch.await(30, TimeUnit.SECONDS); } catch (InterruptedException ex) { throw new IOException(ex.getMessage()); } } /** * Connect to hostname:port. Internally an instance of Controller and * its default SelectorHandler will be created everytime this method is * called. This method should be used only and only if no external * Controller has been initialized. * @param remoteAddress remote address to connect * @throws java.io.IOException */ public void connect(SocketAddress remoteAddress) throws IOException { connect(remoteAddress, (SocketAddress) null); } /** * Connect to hostname:port. Internally an instance of Controller and * its default SelectorHandler will be created everytime this method is * called. This method should be used only and only if no external * Controller has been initialized. * @param remoteAddress remote address to connect * @throws java.io.IOException * @param localAddress local address to bin */ public void connect(SocketAddress remoteAddress, SocketAddress localAddress) throws IOException { if (isConnected) { throw new AlreadyConnectedException(); } if (controller == null) { isStandalone = true; controller = new Controller(); controller.setSelectorHandler(new SSLSelectorHandler(true)); ExecutorService threadPool = new DefaultThreadPool(); controller.setThreadPool(threadPool); final CountDownLatch latch = new CountDownLatch(1); controller.addStateListener(new ControllerStateListenerAdapter() { @Override public void onReady() { latch.countDown(); } @Override public void onException(Throwable e) { if (latch.getCount() > 0) { logger.log(Level.SEVERE, "Error occured on Controller startup: ", e); } latch.countDown(); } }); callbackHandler = new DefaultCallbackHandler(this,false); new WorkerThreadImpl("GrizzlySSLConnectorHandler-Controller",controller).start(); try { latch.await(); } catch (InterruptedException ex) { } } connect(remoteAddress, localAddress, callbackHandler, (SSLSelectorHandler) controller.getSelectorHandler(protocol())); } /** * Initiate SSL handshake phase. * Handshake is required to be done after connection established. * * @param byteBuffer Application {@link ByteBuffer}, where application data * will be stored * @param blocking true, if handshake should be done in blocking mode, for non-blocking false * @return If blocking parameter is true - method should always return true if handshake is done, * or throw IOException otherwise. For non-blocking mode method returns true if handshake is done, or false * if handshake will be completed in non-blocking manner. * If False returned - SSLConnectorHandler will call callbackHandler.onHandshake() to notify * about finishing handshake phase. * @throws java.io.IOException if some error occurs during processing I/O operations/ */ public boolean handshake(ByteBuffer byteBuffer, boolean blocking) throws IOException { sslEngine.beginHandshake(); handshakeStatus = sslEngine.getHandshakeStatus(); if (blocking) { SSLUtils.doHandshake(socketChannel, byteBuffer, securedInputBuffer, securedOutputBuffer, sslEngine, handshakeStatus); securedOutputBuffer.limit(securedOutputBuffer.position()); finishHandshake(); // Sync should be always done return true; } else { return doAsyncHandshake(byteBuffer); } } /** * Read bytes. If blocking is set to true, a pool of temporary * {@link Selector} will be used to read bytes. * @param byteBuffer The byteBuffer to store bytes. * @param blocking true if a a pool of temporary Selector * is required to handle a blocking read. * @return number of bytes read from a channel. * Be careful, because return value represents the length of encrypted data, * which was read from a channel. Don't use return value to determine the * availability of a decrypted data to process, but use byteBuffer.remaining(). * @throws java.io.IOException */ public long read(ByteBuffer byteBuffer, boolean blocking) throws IOException { if (!isConnected) { throw new NotYetConnectedException(); } if (blocking) { return SSLUtils.doSecureRead(socketChannel, sslEngine, byteBuffer, securedInputBuffer); } else { isAsyncReadQueueMode = false; int nRead = doReadAsync(byteBuffer); if (nRead == 0) { registerSelectionKeyFor(SelectionKey.OP_READ); } return nRead; } } /** * Writes bytes. If blocking is set to true, a pool of temporary * {@link Selector} will be used to writes bytes. * @param byteBuffer The byteBuffer to write. * @param blocking true if a a pool of temporary Selector * is required to handle a blocking write. * @return number of bytes written on a channel. * Be careful, as non-crypted data is passed, but crypted data is written * on channel. Don't use return value to determine the * number of bytes from original buffer, which were written. * @throws java.io.IOException */ public long write(ByteBuffer byteBuffer, boolean blocking) throws IOException { if (!isConnected) { throw new NotYetConnectedException(); } if (blocking) { long nWrite = SSLOutputWriter.flushChannel(socketChannel, byteBuffer, securedOutputBuffer, sslEngine); // Mark securedOutputBuffer as empty securedOutputBuffer.position(securedOutputBuffer.limit()); return nWrite; } else { if (callbackHandler == null) { throw new IllegalStateException("Non blocking write needs a CallbackHandler"); } isAsyncWriteQueueMode = false; int nWrite = 1; int totalWrite = 0; while (nWrite > 0 && (byteBuffer.hasRemaining() || securedOutputBuffer.hasRemaining())) { nWrite = doWriteAsync(byteBuffer); totalWrite += nWrite; } if (byteBuffer.hasRemaining() || securedOutputBuffer.hasRemaining()) { registerSelectionKeyFor(SelectionKey.OP_WRITE); } return totalWrite; } } /** * {@inheritDoc} */ public Future readFromAsyncQueue(ByteBuffer buffer, AsyncReadCallbackHandler callbackHandler) throws IOException { return readFromAsyncQueue(buffer, callbackHandler, null); } /** * {@inheritDoc} */ public Future readFromAsyncQueue(ByteBuffer buffer, AsyncReadCallbackHandler callbackHandler, AsyncReadCondition condition) throws IOException { return readFromAsyncQueue(buffer, callbackHandler, condition, obtainSSLReadPostProcessor()); } /** * {@inheritDoc} */ public Future readFromAsyncQueue(ByteBuffer buffer, AsyncReadCallbackHandler callbackHandler, AsyncReadCondition condition, AsyncQueueDataProcessor readPostProcessor) throws IOException { isAsyncReadQueueMode = true; return selectorHandler.getAsyncQueueReader().read( socketChannel.keyFor(selectorHandler.getSelector()), buffer, callbackHandler, condition, readPostProcessor); } /** * {@inheritDoc} */ public Future writeToAsyncQueue(ByteBuffer buffer) throws IOException { return writeToAsyncQueue(buffer, null); } /** * {@inheritDoc} */ public Future writeToAsyncQueue(ByteBuffer buffer, AsyncWriteCallbackHandler callbackHandler) throws IOException { return writeToAsyncQueue(buffer, callbackHandler, obtainSSLWritePreProcessor()); } /** * {@inheritDoc} */ public Future writeToAsyncQueue(ByteBuffer buffer, AsyncWriteCallbackHandler callbackHandler, AsyncQueueDataProcessor writePreProcessor) throws IOException { return writeToAsyncQueue(buffer, callbackHandler, writePreProcessor, null); } /** * {@inheritDoc} */ public Future writeToAsyncQueue(ByteBuffer buffer, AsyncWriteCallbackHandler callbackHandler, AsyncQueueDataProcessor writePreProcessor, ByteBufferCloner cloner) throws IOException { isAsyncWriteQueueMode = true; return selectorHandler.getAsyncQueueWriter().write( socketChannel.keyFor(selectorHandler.getSelector()), buffer, callbackHandler, writePreProcessor, cloner); } /** * {@inheritDoc} */ public Future writeToAsyncQueue( SocketAddress dstAddress, ByteBuffer buffer) throws IOException { return writeToAsyncQueue(dstAddress, buffer,null); } /** * {@inheritDoc} */ public Future writeToAsyncQueue( SocketAddress dstAddress, ByteBuffer buffer, AsyncWriteCallbackHandler callbackHandler) throws IOException { return writeToAsyncQueue(dstAddress, buffer, callbackHandler, obtainSSLWritePreProcessor()); } /** * {@inheritDoc} */ public Future writeToAsyncQueue( SocketAddress dstAddress, ByteBuffer buffer, AsyncWriteCallbackHandler callbackHandler, AsyncQueueDataProcessor writePreProcessor) throws IOException { return writeToAsyncQueue(dstAddress, buffer, callbackHandler, writePreProcessor); } /** * {@inheritDoc} */ public Future writeToAsyncQueue( SocketAddress dstAddress, ByteBuffer buffer, AsyncWriteCallbackHandler callbackHandler, AsyncQueueDataProcessor writePreProcessor, ByteBufferCloner cloner) throws IOException { isAsyncWriteQueueMode = true; return selectorHandler.getAsyncQueueWriter().write( socketChannel.keyFor(selectorHandler.getSelector()), dstAddress, buffer, callbackHandler, writePreProcessor, cloner); } /** * Close the underlying connection. */ public void close() throws IOException { if (logger.isLoggable(Level.FINE)) { IOException ioe = new IOException("Logging stacktrace..."); logger.log(Level.FINE, "Closing SSLConnectorHandler " + this + " Channel: " + socketChannel + " engine: " + sslEngine, ioe); } if (socketChannel != null) { if (isConnected) { try { if (securedOutputBuffer.hasRemaining()) { // if there is something is securedOutputBuffer - flush it OutputWriter.flushChannel(socketChannel, securedOutputBuffer); } // Close secure outbound channel and flush data sslEngine.closeOutbound(); SSLUtils.wrap(EMPTY_BUFFER, securedOutputBuffer, sslEngine); OutputWriter.flushChannel(socketChannel, securedOutputBuffer); } catch (IOException e) { logger.log(Level.FINE, "IOException during closing the connector.", e); } } if (selectorHandler != null) { SelectionKey key = socketChannel.keyFor(selectorHandler.getSelector()); if (key == null) { return; } selectorHandler.getSelectionKeyHandler().close(key); } socketChannel.close(); } if (controller != null && isStandalone) { controller.stop(); controller = null; } sslEngine = null; asyncHandshakeBuffer = null; isStandalone = false; isConnected = false; isHandshakeDone = false; } /** * Finish handling the OP_CONNECT interest ops. * @param key - a {@link SelectionKey} */ public void finishConnect(SelectionKey key) throws IOException{ try { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Finish connect"); } socketChannel = (SocketChannel) key.channel(); socketChannel.finishConnect(); isConnected = socketChannel.isConnected(); if (isConnected) { initSSLEngineIfRequired(); } } catch (IOException e) { throw e; } finally { isConnectedLatch.countDown(); } } /** * Changes SSLConnectorHandler state, after handshake operation is done. * Normally should not be called by outside Grizzly, just in case when custom * handshake code was used. */ public void finishHandshake() { isProcessingAsyncHandshake = false; isHandshakeDone = true; } /** * A token decribing the protocol supported by an implementation of this * interface * @return this {@link ConnectorHandler}'s protocol */ public Controller.Protocol protocol() { return Controller.Protocol.TLS; } /** * Is the underlying SocketChannel connected. * @return true if connected, otherwise false */ public boolean isConnected() { return isConnected && socketChannel.isOpen(); } /** * Is the underlying SocketChannel connected. * @return true if connected, otherwise false */ public boolean isHandshakeDone() { return isHandshakeDone && !isProcessingAsyncHandshake; } /** * Get SSLConnector's {@link SSLContext} */ public SSLContext getSSLContext() { return sslContext; } /** * Set {@link SSLContext}. * Use this method to change SSLConnectorHandler configuration. * New configuration will become active only after SSLConnector * will be closed and connected again. */ public void setSSLContext(SSLContext sslContext) { this.sslContext = sslContext; } /** * Configure SSLConnectorHandler's SSL settings. * * Use this method to change SSLConnectorHandler configuration. * New configuration will become active only after SSLConnector * will be closed and connected again. */ public void configure(SSLConfig sslConfig) { this.sslContext = sslConfig.createSSLContext(); } /** * Returns SSLConnector's{@link SSLEngine} * @return{@link SSLEngine} */ public SSLEngine getSSLEngine() { return sslEngine; } /** * Set{@link SSLEngine} * @param sslEngine{@link SSLEngine} */ public void setSSLEngine(SSLEngine sslEngine) { this.sslEngine = sslEngine; } /** * Returns SSLConnectorHandler's secured input buffer, it * uses for reading data from a socket channel. * @return secured input {@link ByteBuffer} */ public ByteBuffer getSecuredInputBuffer() { return securedInputBuffer; } /** * Returns SSLConnectorHandler's secured output buffer, it * uses for writing data to a socket channel. * @return secured output {@link ByteBuffer} */ public ByteBuffer getSecuredOutputBuffer() { return securedOutputBuffer; } /** * Return the {@link Controller} * @return the {@link Controller} */ public Controller getController() { return controller; } /** * Set the {@link Controller} to use with this instance. * @param controller the {@link Controller} to use with this instance. */ public void setController(Controller controller) { this.controller = controller; } /** * Return the current {@link SocketChannel} used. * @return the current {@link SocketChannel} used. */ public SelectableChannel getUnderlyingChannel() { return socketChannel; } /** * Set the {@link SocketChannel}. * @param the {@link SocketChannel} to use. */ protected void setUnderlyingChannel(SocketChannel socketChannel){ this.socketChannel = socketChannel; } /** * Return the {@link CallbackHandler}. * @return the {@link CallbackHandler}. */ public SSLCallbackHandler getCallbackHandler() { return callbackHandler; } /** * Set the {@link CallbackHandler}. * @param callbackHandler the {@link CallbackHandler}. */ public void setCallbackHandler(SSLCallbackHandler callbackHandler) { this.callbackHandler = callbackHandler; } /** * Return the associated {@link SelectorHandler}. * @return the associated {@link SelectorHandler}. */ public SSLSelectorHandler getSelectorHandler() { return selectorHandler; } /** * Gets the size of the largest application buffer that may occur when * using this session. * SSLEngine application data buffers must be large enough to hold the * application data from any inbound network application data packet * received. Typically, outbound application data buffers can be of any size. * * (javadoc is taken from SSLSession.getApplicationBufferSize()) * @return largets application buffer size, which may occur */ public int getApplicationBufferSize() { initSSLEngineIfRequired(); return sslEngine.getSession().getApplicationBufferSize(); } /** * Read a data from channel in async mode and decrypt * @param byteBuffer buffer for decrypted data * @return number of bytes read from a channel * @throws java.io.IOException */ private int doReadAsync(ByteBuffer byteBuffer) throws IOException { // Clear or compact secured input buffer if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler compactBuffer" + " securedInputBuffer: " + securedInputBuffer); } clearOrCompactBuffer(securedInputBuffer); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler read" + " securedInputBuffer: " + securedInputBuffer); } // Read data to secured buffer int bytesRead = -1; try{ bytesRead = socketChannel.read(securedInputBuffer); } finally { if (bytesRead == -1){ SelectionKeyHandler skh = selectorHandler.getSelectionKeyHandler(); if (skh instanceof BaseSelectionKeyHandler){ ((DefaultSelectionKeyHandler)skh).notifyRemotlyClose(getSelectionKey()); } } } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler done read" + " securedInputBuffer: " + securedInputBuffer + " bytesRead: " + bytesRead); } if (bytesRead == -1) { try { sslEngine.closeInbound(); // check if there is some secured data still available if (securedInputBuffer.position() == 0 || sslEngineStatus == SSLEngineResult.Status.BUFFER_UNDERFLOW) { return -1; } } catch (SSLException e) { return -1; } } securedInputBuffer.flip(); if (bytesRead == 0 && !securedInputBuffer.hasRemaining()) { return 0; } if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler unwrapall" + " securedInputBuffer: " + securedInputBuffer); } int bytesProduced = unwrapAll(byteBuffer); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler unwrapall done" + " securedInputBuffer: " + securedInputBuffer, " produced: " + bytesProduced + " status: " + sslEngineStatus); } if (bytesProduced == 0) { if (sslEngineStatus == SSLEngineResult.Status.CLOSED) { return -1; } else if (sslEngineStatus == SSLEngineResult.Status.BUFFER_OVERFLOW) { throw new BufferOverflowException(); } } return bytesRead; } private int unwrapAll(ByteBuffer byteBuffer) throws SSLException { SSLEngineResult result = null; int bytesProduced = 0; do { result = sslEngine.unwrap(securedInputBuffer, byteBuffer); bytesProduced += result.bytesProduced(); // During handshake phase several unwrap actions could be executed on read data } while (result.getStatus() == SSLEngineResult.Status.OK && (isHandshakeDone || (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP && result.bytesProduced() == 0))); updateSSLEngineStatus(result); return bytesProduced; } /** * Write secured data to channel in async mode * * @param byteBuffer non-crypted data buffer * @return number of bytes written on a channel. * Be careful, as non-crypted data is passed, but crypted data is written * on channel. Don't use return value to determine, * number of bytes from original buffer, which were written. * @throws java.io.IOException */ private int doWriteAsync(ByteBuffer byteBuffer) throws IOException { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "asyncWrite. securedBuffer: " + securedOutputBuffer); } if (securedOutputBuffer.hasRemaining() && !flushSecuredOutputBuffer()) { return 0; } securedOutputBuffer.clear(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "asyncWrite. wrap sslEngine: " + sslEngine + " securedOutputBuffer: " + securedOutputBuffer + " byteBuffer: " + byteBuffer); } SSLEngineResult result = SSLUtils.wrap(byteBuffer, securedOutputBuffer, sslEngine); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "asyncWrite. wrap done sslEngine: " + sslEngine + " securedOutputBuffer: " + securedOutputBuffer + " byteBuffer: " + byteBuffer + " status: " + result); } updateSSLEngineStatus(result); int count = socketChannel.write(securedOutputBuffer); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "asyncWrite. written: " + count + " securedOutputBuffer: " + securedOutputBuffer); } return count; } /** * Perform an SSL handshake in async mode. * @param byteBuffer The application {@link ByteBuffer} * * @throws IOException if the handshake fail. */ private boolean doAsyncHandshake(ByteBuffer byteBuffer) throws IOException { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler.doAsyncHandshake"); } SSLEngineResult result; isProcessingAsyncHandshake = true; asyncHandshakeBuffer = byteBuffer; while (handshakeStatus != HandshakeStatus.FINISHED) { switch (handshakeStatus) { case NEED_WRAP: if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_WRAP sslEngine: " + sslEngine + " securedBuffer: " + securedOutputBuffer); } result = SSLUtils.wrap(EMPTY_BUFFER, securedOutputBuffer, sslEngine); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_WRAP done sslEngine: " + sslEngine + " securedBuffer: " + securedOutputBuffer + " result: " + result + " handshakeStatus: " + result.getHandshakeStatus()); } updateSSLEngineStatus(result); switch (result.getStatus()) { case OK: if (!flushSecuredOutputBuffer()) { if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "SSLConnectorHandler pending secured buffer flush: " + sslEngine + " securedBuffer: " + securedOutputBuffer); } return false; } break; default: throw new IOException("Handshaking error: " + result.getStatus()); } if (handshakeStatus != HandshakeStatus.NEED_UNWRAP) { break; } case NEED_UNWRAP: if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_UNWRAP sslEngine: " + sslEngine + " byteBuffer: " + byteBuffer); } int bytesRead = doReadAsync(byteBuffer); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_UNWRAP done sslEngine: " + sslEngine + " byteBuffer: " + byteBuffer + " bytesRead: " + bytesRead + " handshakeStatus: " + handshakeStatus); } if (bytesRead == -1) { try { sslEngine.closeInbound(); } catch (IOException e) { logger.log(Level.FINE, "Exception occured when closing sslEngine inbound.", e); } throw new EOFException("Connection closed"); } else if (bytesRead == 0 && sslLastOperationResult.bytesConsumed() == 0) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_UNWRAP reregister key sslEngine: " + sslEngine); } registerSelectionKeyFor(SelectionKey.OP_READ); return false; } if (handshakeStatus != HandshakeStatus.NEED_TASK) { break; } case NEED_TASK: if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_TASK sslEngine: " + sslEngine); } handshakeStatus = executeDelegatedTask(); if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler NEED_TASK sslEngine: " + sslEngine + " handshakeStatus: " + handshakeStatus); } break; default: throw new RuntimeException("Invalid Handshaking State" + handshakeStatus); } } if (isProcessingAsyncHandshake) { if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "SSLConnectorHandler finishHandshake sslEngine: " + sslEngine); } finishHandshake(); } asyncHandshakeBuffer = null; return true; } /** * Complete hanshakes operations. * @return SSLEngineResult.HandshakeStatus */ private SSLEngineResult.HandshakeStatus executeDelegatedTask() { Runnable runnable; while ((runnable = sslEngine.getDelegatedTask()) != null) { runnable.run(); } return sslEngine.getHandshakeStatus(); } /** * Update SSLConnectorHandler internal status with * last{@link SSLEngine} operation result. * * @param result last{@link SSLEngine} operation result */ private void updateSSLEngineStatus(SSLEngineResult result) { sslLastOperationResult = result; sslEngineStatus = result.getStatus(); handshakeStatus = result.getHandshakeStatus(); } /** * Clears buffer if there is no info available, or compact buffer otherwise. * @param buffer byte byffer */ private static void clearOrCompactBuffer(ByteBuffer buffer) { if (!buffer.hasRemaining()) { buffer.clear(); } else if (buffer.remaining() < buffer.capacity()) { buffer.compact(); } } /** * Gets SSLConnectorHandler {@link SelectionKey} * @return {@link SelectionKey} */ private SelectionKey getSelectionKey() { return socketChannel.keyFor(selectorHandler.getSelector()); } /** * Registers SSLConnectorHandler's {@link SelectionKey} * to listen channel operations. * @param ops interested channel operations */ private void registerSelectionKeyFor(int ops) { SelectionKey key = getSelectionKey(); selectorHandler.register(key, ops); } /** * Flushes as much as possible bytes from the secured output buffer * @return true if secured buffer was completely flushed, false otherwise */ private boolean flushSecuredOutputBuffer() throws IOException { int nWrite = 1; try{ while (nWrite > 0 && securedOutputBuffer.hasRemaining()) { nWrite = socketChannel.write(securedOutputBuffer); } } catch (IOException ex){ nWrite = -1; throw ex; } finally{ if (nWrite == -1){ SelectionKeyHandler skh = selectorHandler.getSelectionKeyHandler(); if (skh instanceof BaseSelectionKeyHandler){ ((DefaultSelectionKeyHandler)skh).notifyRemotlyClose(getSelectionKey()); } } } if (securedOutputBuffer.hasRemaining()) { SelectionKey key = socketChannel.keyFor(selectorHandler.getSelector()); selectorHandler.register(key, SelectionKey.OP_WRITE); return false; } return true; } /** * Initiate{@link SSLEngine} and related secure buffers */ private void initSSLEngineIfRequired() { if (sslEngine == null) { sslEngine = sslContext.createSSLEngine(); sslEngine.setUseClientMode(true); } int bbSize = sslEngine.getSession().getPacketBufferSize(); securedInputBuffer = ByteBuffer.allocate(bbSize * 2); securedOutputBuffer = ByteBuffer.allocate(bbSize * 2); securedOutputBuffer.limit(0); } private AsyncQueueDataProcessor obtainSSLReadPostProcessor() { if (sslReadPostProcessor == null) { synchronized(lock) { if (sslReadPostProcessor == null) { sslReadPostProcessor = new SSLReadPostProcessor(); } } } return sslReadPostProcessor; } private AsyncQueueDataProcessor obtainSSLWritePreProcessor() { if (sslWritePreProcessor == null) { synchronized(lock) { if (sslWritePreProcessor == null) { sslWritePreProcessor = new SSLWritePreProcessor(); } } } return sslWritePreProcessor; } /** * Internal SSL CallbackHandler, which is able to process properly SSL handshake * phase and translate its calls to the custom SSLCallbackHandler. */ private class SSLInternalCallbackHandler implements CallbackHandler { public void onConnect(IOEvent ioEvent) { callbackHandler.onConnect(ioEvent); } public void onRead(IOEvent ioEvent) { if (!isAsyncReadQueueMode) { try { // if processing handshake - pass the data to handshake related code if (isProcessingAsyncHandshake) { if (doAsyncHandshake(asyncHandshakeBuffer)) { callbackHandler.onHandshake(ioEvent); } return; } callbackHandler.onRead(ioEvent); } catch (IOException e) { logger.log(Level.SEVERE, "Exception occured when reading from SSL channel.", e); } } } public void onWrite(IOEvent ioEvent) { if (!isAsyncWriteQueueMode) { try { // check if all the secured data was written, if not - // flush as much as possible. if (!securedOutputBuffer.hasRemaining() || flushSecuredOutputBuffer()) { // if no encrypted data left in buffer - continue processing if (isProcessingAsyncHandshake) { if (doAsyncHandshake(asyncHandshakeBuffer)) { callbackHandler.onHandshake(ioEvent); } return; } callbackHandler.onWrite(ioEvent); } } catch (IOException e) { logger.log(Level.SEVERE, "Exception occured when writing to SSL channel.", e); } } } } /** * SSL AsyncQueueDataProcessor for a * TCPAsyncQueueReader */ private class SSLReadPostProcessor implements AsyncQueueDataProcessor { public ByteBuffer getInternalByteBuffer() { return securedInputBuffer; } public void process(ByteBuffer byteBuffer) throws SSLException { securedInputBuffer.flip(); unwrapAll(byteBuffer); clearOrCompactBuffer(securedInputBuffer); } } /** * SSL AsyncQueueDataProcessor for a * TCPAsyncQueueWriter */ private class SSLWritePreProcessor implements AsyncQueueDataProcessor { public ByteBuffer getInternalByteBuffer() { return securedOutputBuffer; } public void process(ByteBuffer byteBuffer) throws SSLException { if (!byteBuffer.hasRemaining() || securedOutputBuffer.hasRemaining()) return; securedOutputBuffer.clear(); SSLEngineResult result = sslEngine.wrap(byteBuffer, securedOutputBuffer); updateSSLEngineStatus(result); securedOutputBuffer.flip(); } } }