nio

Java Nio SSL Example

This is an example of a non-blocking I/O provided by java.nio using SSL handshake.
 
 
 
 
 
 
 
 
 
 

1. Definition of Secure Sockets Layer Protocol (SSL)

SSL is the secure communication protocol of choice for a large part of the Internet community. There are many applications of SSL in existence, since it is capable of securing any transmission over TCP. Secure HTTP, or HTTPS, is a familiar application of SSL in e-commerce or password transactions. Along with this popularity comes demands to use it with different I/O and threading models in order to satisfy the applications’ performance, scalability, footprint, and other requirements. There are demands to use it with blocking and non-blocking I/O channels, asynchronous I/O, input and output streams and byte buffers.The main point of the protocol is to provide privacy and reliability between two communicating applications. The following fundamental characteristics provide connection security:

  • Privacy – connection using encryption
  • Identity authentication – identification using certificates
  • Reliability – dependable maintenance of a secure connection through
    message integrity

Many developers may be wondering how to use SSL with Java NIO. With the traditional blocking sockets API, security is a simple issue: just set up an SSLContext instance with the appropriate key material, use it to create instances of SSLSocketFactory or SSLServerSocketFactoryand finally use these factories to create instances of SSLServerSocket or SSLSocket. In Java 1.6, a new abstraction was introduced to allow applications to use the SSL/TLS protocols in a transport independent way, and thus freeing applications to choose transport and computing models that best meet their needs. Not only does this new abstraction allow applications to use non-blocking I/O channels and other I/O models, it also accommodates different threading models.

2. The SSL Engine API

The new abstraction is therefore an advanced API having as core class the javax.net.ssl.SSLEngine. It encapsulates an SSL/TLS state machine and operates on inbound and outbound byte buffers supplied by the user of the SSLEngine.

2.1 Lifecycle

The SSLEngine must first go through the handshake, where the server and the client negotiate the cipher suite and the session keys. This phase typically involves the exchange of several messages. After completing the handshake, the application can start sending and receiving application data. This is the main state of the engine and will typically last until the connection is CLOSED (see image below). In some situations, one of the peers may ask for a renegotiation of the session parameters, either to generate new session keys or to change the cipher suite. This forces a re-handshake. When one of the peers is done with the connection, it should initiate a graceful shutdown, as specified in the SSL/TLS protocol. This involves exchanging a couple of closure messages between the client and the server to terminate the logical session before physically closing the socket.

SSL Lifecycle
SSL Lifecycle

2.2 SSL Handshake

The two main SSLEngine methods wrap() and unwrap() are responsible for generating and consuming network data respectively. Depending on the state of the SSLEngine, this data might be handshake or application data. Each SSLEngine has several phases during its lifetime. Before application data can be sent/received, the SSL/TLS protocol requires a handshake to establish cryptographic parameters. This handshake requires a series of back-and-forth steps by the SSLEngine. The SSL Process can provide more details about the handshake itself. During the initial handshaking, wrap() and unwrap() generate and consume handshake data, and the application is responsible for transporting the data. This sequence is repeated until the handshake is finished. Each SSLEngine operation generates a SSLEngineResult, of which the SSLEngineResult.HandshakeStatus field is used to determine what operation needs to occur next to move the handshake along. Below is an example of the handshake process:

Typical SSL Handshake
Typical SSL Handshake

3. Nio SSL Example

The following example creates a connection to https://www.amazon.com/ and displays the decrypted HTTP response.

3.1 Main class

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;

public class NioSSLExample
{
   public static void main(String[] args) throws Exception
   {
      InetSocketAddress address = new InetSocketAddress("www.amazon.com", 443);
      Selector selector = Selector.open();
      SocketChannel channel = SocketChannel.open();
      channel.connect(address);
      channel.configureBlocking(false);
      int ops = SelectionKey.OP_CONNECT | SelectionKey.OP_READ;

      SelectionKey key =  channel.register(selector, ops);
      
      // create the worker threads
      final Executor ioWorker = Executors.newSingleThreadExecutor();
      final Executor taskWorkers = Executors.newFixedThreadPool(2);

      // create the SSLEngine
      final SSLEngine engine = SSLContext.getDefault().createSSLEngine();
      engine.setUseClientMode(true);
      engine.beginHandshake();
      final int ioBufferSize = 32 * 1024;
      final NioSSLProvider ssl = new NioSSLProvider(key, engine, ioBufferSize, ioWorker, taskWorkers)
      {
         @Override
         public void onFailure(Exception ex)
         {
            System.out.println("handshake failure");
            ex.printStackTrace();
         }

         @Override
         public void onSuccess()
         {
            System.out.println("handshake success");
            SSLSession session = engine.getSession();
            try
            {
               System.out.println("local principal: " + session.getLocalPrincipal());
               System.out.println("remote principal: " + session.getPeerPrincipal());
               System.out.println("cipher: " + session.getCipherSuite());
            }
            catch (Exception exc)
            {
               exc.printStackTrace();
            }

            //HTTP request
            StringBuilder http = new StringBuilder();
            http.append("GET / HTTP/1.0\r\n");
            http.append("Connection: close\r\n");
            http.append("\r\n");
            byte[] data = http.toString().getBytes();
            ByteBuffer send = ByteBuffer.wrap(data);
            this.sendAsync(send);
         }

         @Override
         public void onInput(ByteBuffer decrypted)
         {
            // HTTP response
            byte[] dst = new byte[decrypted.remaining()];
            decrypted.get(dst);
            String response = new String(dst);
            System.out.print(response);
            System.out.flush();
         }

         @Override
         public void onClosed()
         {
            System.out.println("ssl session closed");
         }
      };

      // NIO selector
      while (true)
      {
         key.selector().select();
         Iterator keys = key.selector().selectedKeys().iterator();
         while (keys.hasNext())
         {
            keys.next();
            keys.remove();
            ssl.processInput();
         }
      }
   }
}

From the above code:

  • In the main() method on lines 18-25, a Selector is created and a SocketChannel is registered having a selection key interested in socket-connect and socket-read operations for the connection to the amazon url:
          InetSocketAddress address = new InetSocketAddress("www.amazon.com", 443);
          Selector selector = Selector.open();
          SocketChannel channel = SocketChannel.open();
          channel.connect(address);
          channel.configureBlocking(false);
          int ops = SelectionKey.OP_CONNECT | SelectionKey.OP_READ;
          SelectionKey key =  channel.register(selector, ops);
    
  • On lines 28-29, an ioWorker thread is created for executing the SSLProvider runnable and also a ThreadPool containing 2 threads for executing the delegated runnable task for the SSL Engine.
  • On lines 32-34, the SSLEngine is initiated in client mode and with initial handshaking:
          final SSLEngine engine = SSLContext.getDefault().createSSLEngine();
          engine.setUseClientMode(true);
          engine.beginHandshake();
  • On lines 36-59, the NioSSLProvider object is instantiated. This is responsible for writing and reading from the ByteChannel and also as the entry point for the SSL Handshaking. Upon successful negotiation with the amazon server, the local and remote principals are printed and also the name of the SSL cipher suite which is used for all connections in the session.
  • The HTTP request is sent from the client after successful handshake on lines 62-67:
                StringBuilder http = new StringBuilder();
                http.append("GET / HTTP/1.0\r\n");
                http.append("Connection: close\r\n");
                http.append("\r\n");
                byte[] data = http.toString().getBytes();
                ByteBuffer send = ByteBuffer.wrap(data);
    
  • On line 72, the onInput method is called whenever the SSL Engine completed an operation with javax.net.ssl.SSLEngineResult.Status.OK. The partial decrypted response is printed each time:
             public void onInput(ByteBuffer decrypted)
             {
                // HTTP response
                byte[] dst = new byte[decrypted.remaining()];
                decrypted.get(dst);
                String response = new String(dst);
                System.out.print(response);
                System.out.flush();
             }
    
  • Finally, the nio Selector loop is started on line 90 by processing the selection keys which remain valid until the channel is closed.

3.2 NioSSLProvider class

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.Executor;

import javax.net.ssl.SSLEngine;

public abstract class NioSSLProvider extends SSLProvider
{
   private final ByteBuffer buffer = ByteBuffer.allocate(32 * 1024);
   private final SelectionKey key;

   public NioSSLProvider(SelectionKey key, SSLEngine engine, int bufferSize, Executor ioWorker, Executor taskWorkers)
   {
      super(engine, bufferSize, ioWorker, taskWorkers);
      this.key = key;
   }
   
   @Override
   public void onOutput(ByteBuffer encrypted)
   {
      try
      {
         ((WritableByteChannel) this.key.channel()).write(encrypted);
      }
      catch (IOException exc)
      {
         throw new IllegalStateException(exc);
      }
   }

   public boolean processInput()
   {
	  buffer.clear();
      int bytes;
      try
      {
         bytes = ((ReadableByteChannel) this.key.channel()).read(buffer);
      }
      catch (IOException ex)
      {
         bytes = -1;
      }
      if (bytes == -1) {
         return false;
      }
      buffer.flip();
      ByteBuffer copy = ByteBuffer.allocate(bytes);
      copy.put(buffer);
      copy.flip();
      this.notify(copy);
      return true;
   }
}

From the above code:

  • A sequence of bytes is read from the channel on line 40:
    bytes = ((ReadableByteChannel) this.key.channel()).read(buffer);
    

    and a new byte buffer is allocated on line 50:

    ByteBuffer copy = ByteBuffer.allocate(bytes);
    
  • The notifymethod is called on line 53, which triggers the ssl handshake procedure and via the helper method isHandShaking on line 1 of the SSLProvider class, the wrap/unwrap sequence starts.
  • If the wrap() helper method from the SSLProvider class is called, then the buffered data are encoded into SSL/TLS network data:
    wrapResult = engine.wrap(clientWrap, serverWrap);
    

    and if the return value of the SSLEngine operation is OK then the onOutput() method on line 22 is called in order to write the encrypted response from the server into the ByteChannel:

    ((WritableByteChannel) this.key.channel()).write(encrypted);
    
  • If the unwrap() helper method from the SSLProvider class is called, then an attempt to decode the SSL network data from the server is made on line 95 of the SSLProvider class:
    unwrapResult = engine.unwrap(clientUnwrap, serverUnwrap);
    

    and if the return value of the SSLEngine operation is OK, the decrypted message from the server is printed.

3.3 SSLProvider class

For simplicity, we present the basic helper methods of this class:

private synchronized boolean isHandShaking()
   {
      switch (engine.getHandshakeStatus())
      {
         case NOT_HANDSHAKING:
            boolean occupied = false;
            {
               if (clientWrap.position() > 0)
            	   occupied |= this.wrap();
               if (clientUnwrap.position() > 0)
            	   occupied |= this.unwrap();
            }
            return occupied;

         case NEED_WRAP:
            if (!this.wrap())
               return false;
            break;

         case NEED_UNWRAP:
            if (!this.unwrap())
               return false;
            break;

         case NEED_TASK:
            final Runnable sslTask = engine.getDelegatedTask();
            Runnable wrappedTask = new Runnable()
            {
               @Override
               public void run()
               {
                  sslTask.run();
                  ioWorker.execute(SSLProvider.this);
               }
            };
            taskWorkers.execute(wrappedTask);
            return false;

         case FINISHED:
            throw new IllegalStateException("FINISHED");
      }

      return true;
   }

   private boolean wrap()
   {
      SSLEngineResult wrapResult;

      try
      {
         clientWrap.flip();
         wrapResult = engine.wrap(clientWrap, serverWrap);
         clientWrap.compact();
      }
      catch (SSLException exc)
      {
         this.onFailure(exc);
         return false;
      }

      switch (wrapResult.getStatus())
      {
         case OK:
            if (serverWrap.position() > 0)
            {
               serverWrap.flip();
               this.onOutput(serverWrap);
               serverWrap.compact();
            }
            break;

         case BUFFER_UNDERFLOW:
            // try again later
            break;

         case BUFFER_OVERFLOW:
            throw new IllegalStateException("failed to wrap");

         case CLOSED:
            this.onClosed();
            return false;
      }

      return true;
   }

   private boolean unwrap()
   {
      SSLEngineResult unwrapResult;

      try
      {
         clientUnwrap.flip();
         unwrapResult = engine.unwrap(clientUnwrap, serverUnwrap);
         clientUnwrap.compact();
      }
      catch (SSLException ex)
      {
         this.onFailure(ex);
         return false;
      }

      switch (unwrapResult.getStatus())
      {
         case OK:
            if (serverUnwrap.position() > 0)
            {
               serverUnwrap.flip();
               this.onInput(serverUnwrap);
               serverUnwrap.compact();
            }
            break;

         case CLOSED:
            this.onClosed();
            return false;

         case BUFFER_OVERFLOW:
            throw new IllegalStateException("failed to unwrap");

         case BUFFER_UNDERFLOW:
            return false;
      }

      if (unwrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED)
      {
            this.onSuccess();
            return false;
      }

      return true;
   }

4. Download Java Source Code

This was an example of SSL handshake with java.nio

Download
You can download the full source code of this example here: NioSSLExample

Nassos Hasiotis

Nassos has graduated from Computer Engineering and Informatics Department in the University of Patras. He also holds a Master degree in Communication and Network Systems from University of Athens. He has a 10-year work experience as a Java and C++ developer in the Telecommunication and IT industry participating in various projects. He currently works as a senior software developer in the IT sector where he is mainly involved with projects for the EU requiring extensive java skills.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Frank Olgemple
Frank Olgemple
7 years ago

This is what I get when I run the example:
* Exception in thread “pool-2-thread-2” java.lang.NullPointerException
at two.nio.ssl.SSLProvider$3.run(SSLProvider.java:93)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
*/

zipper
zipper
3 years ago

Great work! I think it might be more clear if you change clientWrap/clientUnwrap/serverWrap/serverUnwrap to outboundPlain/InboundEncrypted/outboundEncrypted/inboundDecrypted respectively.

Back to top button