Class SslHandler

  • All Implemented Interfaces:
    io.netty5.channel.ChannelHandler

    public class SslHandler
    extends io.netty5.handler.codec.ByteToMessageDecoder
    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 Future to get notified about the completion of the handshake it's also possible to detect it by implement the ChannelHandler.channelInboundEvent(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.

    • Constructor Detail

      • SslHandler

        public SslHandler​(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​(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​(SSLEngine engine,
                          boolean startTls,
                          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,
                                        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). 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,
                                                     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,
                                                    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.
      • applicationProtocol

        public 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
      • handshakeFuture

        public io.netty5.util.concurrent.Future<io.netty5.channel.Channel> handshakeFuture()
        Returns a Future that will get notified once the current TLS handshake completes.
        Returns:
        the Future for the initial TLS handshake if renegotiate() was not invoked. The Future for the most recent TLS renegotiation otherwise.
      • closeOutbound

        public io.netty5.util.concurrent.Future<Void> closeOutbound()
        Sends an SSL close_notify message to the specified channel and destroys the underlying SSLEngine. This will not close the underlying Channel. If you want to also close the Channel use Channel.close() or ChannelOutboundInvoker.close()
      • sslCloseFuture

        public io.netty5.util.concurrent.Future<io.netty5.channel.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
      • handlerRemoved0

        public void handlerRemoved0​(io.netty5.channel.ChannelHandlerContext ctx)
                             throws Exception
        Overrides:
        handlerRemoved0 in class io.netty5.handler.codec.ByteToMessageDecoder
        Throws:
        Exception
      • disconnect

        public io.netty5.util.concurrent.Future<Void> disconnect​(io.netty5.channel.ChannelHandlerContext ctx)
      • close

        public io.netty5.util.concurrent.Future<Void> close​(io.netty5.channel.ChannelHandlerContext ctx)
      • read

        public void read​(io.netty5.channel.ChannelHandlerContext ctx)
      • write

        public io.netty5.util.concurrent.Future<Void> write​(io.netty5.channel.ChannelHandlerContext ctx,
                                                            Object msg)
      • flush

        public void flush​(io.netty5.channel.ChannelHandlerContext ctx)
      • channelInactive

        public void channelInactive​(io.netty5.channel.ChannelHandlerContext ctx)
                             throws Exception
        Specified by:
        channelInactive in interface io.netty5.channel.ChannelHandler
        Overrides:
        channelInactive in class io.netty5.handler.codec.ByteToMessageDecoder
        Throws:
        Exception
      • channelExceptionCaught

        public void channelExceptionCaught​(io.netty5.channel.ChannelHandlerContext ctx,
                                           Throwable cause)
                                    throws Exception
        Throws:
        Exception
      • isEncrypted

        public static boolean isEncrypted​(io.netty5.buffer.api.Buffer buffer)
        Returns true if the given Buffer is encrypted. Be aware that this method will not increase the readerIndex of the given Buffer.
        Parameters:
        buffer - The Buffer 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 Buffer is encrypted, false otherwise.
        Throws:
        IllegalArgumentException - Is thrown if the given Buffer has not at least 5 bytes to read.
      • decode

        protected void decode​(io.netty5.channel.ChannelHandlerContext ctx,
                              io.netty5.buffer.api.Buffer in)
                       throws SSLException
        Specified by:
        decode in class io.netty5.handler.codec.ByteToMessageDecoder
        Throws:
        SSLException
      • channelReadComplete

        public void channelReadComplete​(io.netty5.channel.ChannelHandlerContext ctx)
                                 throws Exception
        Specified by:
        channelReadComplete in interface io.netty5.channel.ChannelHandler
        Overrides:
        channelReadComplete in class io.netty5.handler.codec.ByteToMessageDecoder
        Throws:
        Exception
      • handlerAdded0

        public void handlerAdded0​(io.netty5.channel.ChannelHandlerContext ctx)
                           throws Exception
        Overrides:
        handlerAdded0 in class io.netty5.handler.codec.ByteToMessageDecoder
        Throws:
        Exception
      • renegotiate

        public io.netty5.util.concurrent.Future<io.netty5.channel.Channel> renegotiate()
        Performs TLS renegotiation.
      • renegotiate

        public io.netty5.util.concurrent.Future<io.netty5.channel.Channel> renegotiate​(io.netty5.util.concurrent.Promise<io.netty5.channel.Channel> promise)
        Performs TLS renegotiation.
      • channelActive

        public void channelActive​(io.netty5.channel.ChannelHandlerContext ctx)
                           throws Exception
        Issues an initial TLS handshake once connected when used in client-mode
        Throws:
        Exception
      • pendingOutboundBytes

        public long pendingOutboundBytes​(io.netty5.channel.ChannelHandlerContext ctx)