Class SslHandler

  • All Implemented Interfaces:
    ChannelHandler, ChannelInboundHandler, ChannelOutboundHandler

    public class SslHandler
    extends ByteToMessageDecoder
    implements ChannelOutboundHandler
    Adds SSL · TLS and StartTLS support to a Channel. Please refer to the "SecureChat" example in the distribution or the web site for the detailed usage.

    Beginning the handshake

    Beside using the handshake ChannelFuture to get notified about the completion of the handshake it's also possible to detect it by implement the ChannelInboundHandler.userEventTriggered(ChannelHandlerContext, Object) method and check for a SslHandshakeCompletionEvent.

    Handshake

    The handshake will be automatically issued for you once the Channel is active and SSLEngine.getUseClientMode() returns true. So no need to bother with it by your self.

    Closing the session

    To close the SSL session, the closeOutbound() method should be called to send the close_notify message to the remote peer. One exception is when you close the Channel - SslHandler intercepts the close request and send the close_notify message before the channel closure automatically. Once the SSL session is closed, it is not reusable, and consequently you should create a new SslHandler with a new SSLEngine as explained in the following section.

    Restarting the session

    To restart the SSL session, you must remove the existing closed SslHandler from the ChannelPipeline, insert a new SslHandler with a new SSLEngine into the pipeline, and start the handshake process as described in the first section.

    Implementing StartTLS

    StartTLS is the communication pattern that secures the wire in the middle of the plaintext connection. Please note that it is different from SSL · TLS, that secures the wire from the beginning of the connection. Typically, StartTLS is composed of three steps:

    1. Client sends a StartTLS request to server.
    2. Server sends a StartTLS response to client.
    3. Client begins SSL handshake.
    If you implement a server, you need to:
    1. create a new SslHandler instance with startTls flag set to true,
    2. insert the SslHandler to the ChannelPipeline, and
    3. write a StartTLS response.
    Please note that you must insert SslHandler before sending the StartTLS response. Otherwise the client can send begin SSL handshake before SslHandler is inserted to the ChannelPipeline, causing data corruption.

    The client-side implementation is much simpler.

    1. Write a StartTLS request,
    2. wait for the StartTLS response,
    3. create a new SslHandler instance with startTls flag set to false,
    4. insert the SslHandler to the ChannelPipeline, and
    5. Initiate SSL handshake.

    Known issues

    Because of a known issue with the current implementation of the SslEngine that comes with Java it may be possible that you see blocked IO-Threads while a full GC is done.

    So if you are affected you can workaround this problem by adjust the cache settings like shown below:

         SslContext context = ...;
         context.getServerSessionContext().setSessionCacheSize(someSaneSize);
         context.getServerSessionContext().setSessionTime(someSameTimeout);
     

    What values to use here depends on the nature of your application and should be set based on monitoring and debugging of it. For more details see #832 in our issue tracker.

    • Field Detail

      • IGNORABLE_CLASS_IN_STACK

        private static final java.util.regex.Pattern IGNORABLE_CLASS_IN_STACK
      • IGNORABLE_ERROR_MESSAGE

        private static final java.util.regex.Pattern IGNORABLE_ERROR_MESSAGE
      • STATE_SENT_FIRST_MESSAGE

        private static final int STATE_SENT_FIRST_MESSAGE
        See Also:
        Constant Field Values
      • STATE_FLUSHED_BEFORE_HANDSHAKE

        private static final int STATE_FLUSHED_BEFORE_HANDSHAKE
        See Also:
        Constant Field Values
      • STATE_READ_DURING_HANDSHAKE

        private static final int STATE_READ_DURING_HANDSHAKE
        See Also:
        Constant Field Values
      • STATE_HANDSHAKE_STARTED

        private static final int STATE_HANDSHAKE_STARTED
        See Also:
        Constant Field Values
      • MAX_PLAINTEXT_LENGTH

        private static final int MAX_PLAINTEXT_LENGTH
        2^14 which is the maximum sized plaintext chunk allowed by the TLS RFC.
        See Also:
        Constant Field Values
      • engine

        private final javax.net.ssl.SSLEngine engine
      • delegatedTaskExecutor

        private final java.util.concurrent.Executor delegatedTaskExecutor
      • jdkCompatibilityMode

        private final boolean jdkCompatibilityMode
      • singleBuffer

        private final java.nio.ByteBuffer[] singleBuffer
        Used if SSLEngine.wrap(ByteBuffer[], ByteBuffer) and SSLEngine.unwrap(ByteBuffer, ByteBuffer[]) should be called with a ByteBuf that is only backed by one ByteBuffer to reduce the object creation.
      • startTls

        private final boolean startTls
      • packetLength

        private int packetLength
      • state

        private short state
      • handshakeTimeoutMillis

        private volatile long handshakeTimeoutMillis
      • closeNotifyFlushTimeoutMillis

        private volatile long closeNotifyFlushTimeoutMillis
      • closeNotifyReadTimeoutMillis

        private volatile long closeNotifyReadTimeoutMillis
      • wrapDataSize

        volatile int wrapDataSize
    • Constructor Detail

      • SslHandler

        public SslHandler​(javax.net.ssl.SSLEngine engine)
        Creates a new instance which runs all delegated tasks directly on the EventExecutor.
        Parameters:
        engine - the SSLEngine this handler will use
      • SslHandler

        public SslHandler​(javax.net.ssl.SSLEngine engine,
                          boolean startTls)
        Creates a new instance which runs all delegated tasks directly on the EventExecutor.
        Parameters:
        engine - the SSLEngine this handler will use
        startTls - true if the first write request shouldn't be encrypted by the SSLEngine
      • SslHandler

        public SslHandler​(javax.net.ssl.SSLEngine engine,
                          java.util.concurrent.Executor delegatedTaskExecutor)
        Creates a new instance.
        Parameters:
        engine - the SSLEngine this handler will use
        delegatedTaskExecutor - the Executor that will be used to execute tasks that are returned by SSLEngine.getDelegatedTask().
      • SslHandler

        public SslHandler​(javax.net.ssl.SSLEngine engine,
                          boolean startTls,
                          java.util.concurrent.Executor delegatedTaskExecutor)
        Creates a new instance.
        Parameters:
        engine - the SSLEngine this handler will use
        startTls - true if the first write request shouldn't be encrypted by the SSLEngine
        delegatedTaskExecutor - the Executor that will be used to execute tasks that are returned by SSLEngine.getDelegatedTask().
    • Method Detail

      • getHandshakeTimeoutMillis

        public long getHandshakeTimeoutMillis()
      • setHandshakeTimeout

        public void setHandshakeTimeout​(long handshakeTimeout,
                                        java.util.concurrent.TimeUnit unit)
      • setHandshakeTimeoutMillis

        public void setHandshakeTimeoutMillis​(long handshakeTimeoutMillis)
      • setWrapDataSize

        @UnstableApi
        public final void setWrapDataSize​(int wrapDataSize)
        Sets the number of bytes to pass to each SSLEngine.wrap(ByteBuffer[], int, int, ByteBuffer) call.

        This value will partition data which is passed to write write(ChannelHandlerContext, Object, ChannelPromise). The partitioning will work as follows:

        • If wrapDataSize <= 0 then we will write each data chunk as is.
        • If wrapDataSize > data size then we will attempt to aggregate multiple data chunks together.
        • If wrapDataSize > data size Else if wrapDataSize <= data size then we will divide the data into chunks of wrapDataSize when writing.

        If the SSLEngine doesn't support a gather wrap operation (e.g. SslProvider.OPENSSL) then aggregating data before wrapping can help reduce the ratio between TLS overhead vs data payload which will lead to better goodput. Writing fixed chunks of data can also help target the underlying transport's (e.g. TCP) frame size. Under lossy/congested network conditions this may help the peer get full TLS packets earlier and be able to do work sooner, as opposed to waiting for the all the pieces of the TLS packet to arrive.

        Parameters:
        wrapDataSize - the number of bytes which will be passed to each SSLEngine.wrap(ByteBuffer[], int, int, ByteBuffer) call.
      • getCloseNotifyFlushTimeoutMillis

        public final long getCloseNotifyFlushTimeoutMillis()
        Gets the timeout for flushing the close_notify that was triggered by closing the Channel. If the close_notify was not flushed in the given timeout the Channel will be closed forcibly.
      • setCloseNotifyFlushTimeout

        public final void setCloseNotifyFlushTimeout​(long closeNotifyFlushTimeout,
                                                     java.util.concurrent.TimeUnit unit)
        Sets the timeout for flushing the close_notify that was triggered by closing the Channel. If the close_notify was not flushed in the given timeout the Channel will be closed forcibly.
      • getCloseNotifyReadTimeoutMillis

        public final long getCloseNotifyReadTimeoutMillis()
        Gets the timeout (in ms) for receiving the response for the close_notify that was triggered by closing the Channel. This timeout starts after the close_notify message was successfully written to the remote peer. Use 0 to directly close the Channel and not wait for the response.
      • setCloseNotifyReadTimeout

        public final void setCloseNotifyReadTimeout​(long closeNotifyReadTimeout,
                                                    java.util.concurrent.TimeUnit unit)
        Sets the timeout for receiving the response for the close_notify that was triggered by closing the Channel. This timeout starts after the close_notify message was successfully written to the remote peer. Use 0 to directly close the Channel and not wait for the response.
      • engine

        public javax.net.ssl.SSLEngine engine()
        Returns the SSLEngine which is used by this handler.
      • applicationProtocol

        public java.lang.String applicationProtocol()
        Returns the name of the current application-level protocol.
        Returns:
        the protocol name or null if application-level protocol has not been negotiated
      • closeOutbound0

        private void closeOutbound0​(ChannelPromise promise)
      • sslCloseFuture

        public Future<Channel> sslCloseFuture()
        Return the Future that will get notified if the inbound of the SSLEngine is closed. This method will return the same Future all the time.
        See Also:
        SSLEngine
      • connect

        public void connect​(ChannelHandlerContext ctx,
                            java.net.SocketAddress remoteAddress,
                            java.net.SocketAddress localAddress,
                            ChannelPromise promise)
                     throws java.lang.Exception
        Description copied from interface: ChannelOutboundHandler
        Called once a connect operation is made.
        Specified by:
        connect in interface ChannelOutboundHandler
        Parameters:
        ctx - the ChannelHandlerContext for which the connect operation is made
        remoteAddress - the SocketAddress to which it should connect
        localAddress - the SocketAddress which is used as source on connect
        promise - the ChannelPromise to notify once the operation completes
        Throws:
        java.lang.Exception - thrown if an error occurs
      • newPendingWritesNullException

        private static java.lang.IllegalStateException newPendingWritesNullException()
      • flush

        public void flush​(ChannelHandlerContext ctx)
                   throws java.lang.Exception
        Description copied from interface: ChannelOutboundHandler
        Called once a flush operation is made. The flush operation will try to flush out all previous written messages that are pending.
        Specified by:
        flush in interface ChannelOutboundHandler
        Parameters:
        ctx - the ChannelHandlerContext for which the flush operation is made
        Throws:
        java.lang.Exception - thrown if an error occurs
      • wrapAndFlush

        private void wrapAndFlush​(ChannelHandlerContext ctx)
                           throws javax.net.ssl.SSLException
        Throws:
        javax.net.ssl.SSLException
      • wrap

        private void wrap​(ChannelHandlerContext ctx,
                          boolean inUnwrap)
                   throws javax.net.ssl.SSLException
        Throws:
        javax.net.ssl.SSLException
      • wrapMultiple

        private javax.net.ssl.SSLEngineResult wrapMultiple​(ByteBufAllocator alloc,
                                                           javax.net.ssl.SSLEngine engine,
                                                           ByteBuf in,
                                                           ByteBuf out)
                                                    throws javax.net.ssl.SSLException
        Throws:
        javax.net.ssl.SSLException
      • wrap

        private javax.net.ssl.SSLEngineResult wrap​(ByteBufAllocator alloc,
                                                   javax.net.ssl.SSLEngine engine,
                                                   ByteBuf in,
                                                   ByteBuf out)
                                            throws javax.net.ssl.SSLException
        Throws:
        javax.net.ssl.SSLException
      • ignoreException

        private boolean ignoreException​(java.lang.Throwable t)
        Checks if the given Throwable can be ignore and just "swallowed" When an ssl connection is closed a close_notify message is sent. After that the peer also sends close_notify however, it's not mandatory to receive the close_notify. The party who sent the initial close_notify can close the connection immediately then the peer will get connection reset error.
      • isEncrypted

        public static boolean isEncrypted​(ByteBuf buffer)
        Returns true if the given ByteBuf is encrypted. Be aware that this method will not increase the readerIndex of the given ByteBuf.
        Parameters:
        buffer - The ByteBuf to read from. Be aware that it must have at least 5 bytes to read, otherwise it will throw an IllegalArgumentException.
        Returns:
        encrypted true if the ByteBuf is encrypted, false otherwise.
        Throws:
        java.lang.IllegalArgumentException - Is thrown if the given ByteBuf has not at least 5 bytes to read.
      • handleUnwrapThrowable

        private void handleUnwrapThrowable​(ChannelHandlerContext ctx,
                                           java.lang.Throwable cause)
      • unwrapNonAppData

        private int unwrapNonAppData​(ChannelHandlerContext ctx)
                              throws javax.net.ssl.SSLException
        Calls SSLEngine.unwrap(ByteBuffer, ByteBuffer) with an empty buffer to handle handshakes, etc.
        Throws:
        javax.net.ssl.SSLException
      • unwrap

        private int unwrap​(ChannelHandlerContext ctx,
                           ByteBuf packet,
                           int length)
                    throws javax.net.ssl.SSLException
        Unwraps inbound SSL records.
        Throws:
        javax.net.ssl.SSLException
      • setHandshakeSuccessUnwrapMarkReentry

        private boolean setHandshakeSuccessUnwrapMarkReentry()
      • toByteBuffer

        private static java.nio.ByteBuffer toByteBuffer​(ByteBuf out,
                                                        int index,
                                                        int len)
      • inEventLoop

        private static boolean inEventLoop​(java.util.concurrent.Executor executor)
      • runDelegatedTasks

        private boolean runDelegatedTasks​(boolean inUnwrap)
        Will either run the delegated task directly calling Runnable.run() and return true or will offload the delegated task using Executor.execute(Runnable) and return false. If the task is offloaded it will take care to resume its work on the EventExecutor once there are no more tasks to process.
      • executeDelegatedTask

        private void executeDelegatedTask​(boolean inUnwrap)
      • setHandshakeSuccess

        private boolean setHandshakeSuccess()
        Notify all the handshake futures about the successfully handshake
        Returns:
        true if handshakePromise was set successfully and a SslHandshakeCompletionEvent was fired. false otherwise.
      • setHandshakeFailure

        private void setHandshakeFailure​(ChannelHandlerContext ctx,
                                         java.lang.Throwable cause)
        Notify all the handshake futures about the failure during the handshake.
      • setHandshakeFailure

        private void setHandshakeFailure​(ChannelHandlerContext ctx,
                                         java.lang.Throwable cause,
                                         boolean closeInbound,
                                         boolean notify,
                                         boolean alwaysFlushAndClose)
        Notify all the handshake futures about the failure during the handshake.
      • setHandshakeFailureTransportFailure

        private void setHandshakeFailureTransportFailure​(ChannelHandlerContext ctx,
                                                         java.lang.Throwable cause)
      • releaseAndFailAll

        private void releaseAndFailAll​(ChannelHandlerContext ctx,
                                       java.lang.Throwable cause)
      • notifyClosePromise

        private void notifyClosePromise​(java.lang.Throwable cause)
      • closeOutboundAndChannel

        private void closeOutboundAndChannel​(ChannelHandlerContext ctx,
                                             ChannelPromise promise,
                                             boolean disconnect)
                                      throws java.lang.Exception
        Throws:
        java.lang.Exception
      • startHandshakeProcessing

        private void startHandshakeProcessing​(boolean flushAtEnd)
      • renegotiate

        public Future<Channel> renegotiate()
        Performs TLS renegotiation.
      • renegotiateOnEventLoop

        private void renegotiateOnEventLoop​(Promise<Channel> newHandshakePromise)
      • handshake

        private void handshake​(boolean flushAtEnd)
        Performs TLS (re)negotiation.
        Parameters:
        flushAtEnd - Set to true if the outbound buffer should be flushed (written to the network) at the end. Set to false if the handshake will be flushed later, e.g. as part of TCP Fast Open connect.
      • applyHandshakeTimeout

        private void applyHandshakeTimeout()
      • setOpensslEngineSocketFd

        private void setOpensslEngineSocketFd​(Channel c)
      • allocateOutNetBuf

        private ByteBuf allocateOutNetBuf​(ChannelHandlerContext ctx,
                                          int pendingBytes,
                                          int numComponents)
        Allocates an outbound network buffer for SSLEngine.wrap(ByteBuffer, ByteBuffer) which can encrypt the specified amount of pending bytes.
      • isStateSet

        private boolean isStateSet​(int bit)
      • setState

        private void setState​(int bit)
      • clearState

        private void clearState​(int bit)
      • attemptCopyToCumulation

        private static boolean attemptCopyToCumulation​(ByteBuf cumulation,
                                                       ByteBuf next,
                                                       int wrapDataSize)