Home > Uncategorized > Tricks and Tips with NIO part V: SSL and NIO, friend or foe?

Tricks and Tips with NIO part V: SSL and NIO, friend or foe?

Over the last couple of months, I was investigating how I can add SSL support in GlassFish without having to redesign its HTTP Connector called Grizzly. I have to admit that everytime I’ve looked at the example included with the JDK, I was feeling like trying to bike on a frozen lake.

DSCN0392.JPG

This is doable, but very hard (I’m the one near the person using skis (one scary person :-)!)

Maybe that’s only me, but I’ve found it very difficult to implement it properly, mostly because it didn’t fit well in the Grizzly framework. Of course I can implements it as a special case in Grizzly, but I was trying to integrate SSL support and be able to use all the tricks I’ve previously discussed (OP_WRITE, SelectionKey, Temporay Selectors). I was aware that other NIO based framework sucessfully implemented it, but wanted to come with something before looking at their implementation. The excellent MINA framework have a very interesting way of support this, but I wasn’t able to figure out how to re-initiate an handshake when the endpoint (most probably a Servlet) wants to look at the client certificate (CLIENT_CERT). If someone figured how to do it, please let me know! I would have liked to get their implementation into Grizzly.

Anyway, I’ve finally decided to implement it from scratch and be able to re-use the tricks I’ve already described. The good new is you can see the implementation here.
Now the details. The entry point when using SSL is the SSLEngine. The SSLEngine is associated with the lifetime of the SocketChannel, so you need to take care of re-using the same SSLEngine between registration of the SelectionKey. Rrrr, for HTTP, it means you will most probably use SelectionKey.attach() for doing it. I don’t like that SelectionKey.attach(..) (see here why). My problem with this is when you want to implement your SelectionKey registering code (ex: the HTTP keep-alive support), you need to create a data structure that will contains the SSLEngine and a long (or worse, a ByteBuffer), and then attach it to the SelectionKey. Your data structure will most likely looks like:

public class SSLAttachment{
    protected SSLEngine sslEngine;
    protedted long keepAliveTime;
    protected ByteBuffer byteByffer;
    ....
}

and you will pool them to avoid creating one everytime you need to register the SelectionKey. Naaaaa I don’t like that, mostly because Grizzly, by default, doesn’t attach data structures to the SelectionKey. Not to say that we did a lot of benchmarks and having to pool the data structure (or worse, create a new one everytime you need to register) is not optimal. OK I understand some protocols realy needs to do that (see all the comments here about this). Fortunalty, I digged the SSLEngine API and was able to use the SSLSession attached to an SSLEngine (SSLEngine.getSession()). Hence no needs for a data structure, just need to do something like:

((SSLEngine)selectionKey.attachment()).getSession().putValue
                            (EXPIRE_TIME, System.currentTimeMillis());

So, when using NIO + SSL, I recommend you use the SSLEngine.getSession() to store the data structure. This way you don’t have to synchronize on a pool and/or create your own data structure.

Another things I wanted to support with Grizzly SSL is the temporary Selectors tricks I’ve described here. The way Grizzly handles an HTTP request by default is by trying to read all the header bytes without having to register the SelectionKey back to the main Selector. When the main Selector cannot reads more bytes, I’m always registering the SocketChannel to a temporary Selector and try to do more reads. This approach gives very good result, and I did implement the same approach for SSL. The way I did it is by:

  • Read available bytes
  • Invoke SSLEngine.unwrap(..)
  • If all the required bytes needed for the handshake are read, flush all the response bytes (using a temporary Selector trick to make sure all the bytes are flushed to the client)
  • Once the handshake is done and successful, reads more bytes
  • If the client is still alive (might have closed the connection), then call SSLEngine.unwrap().
  • Try to parse the headers. If bytes are required, read more by using a pool of temporary Selectors
  • Once the headers has been read, execute the request, attach the SSLEngine to the SelectionKey and return to the main Selector

But wait…One thing I don’t get with the SSLEngine is when you complete the handshake operation:

        Runnable runnable;
        while ((runnable = sslEngine.getDelegatedTask()) != null) {
            runnable.run();
        }

In which situation will you execute the delegated task on a thread? It might be protocol specific, but with HTTP, that doesn’t make any sense to execute it using a Thread (maybe I should try and see what I’m getting). Another observation is when the Servlet wants to see the certificate during its execution, you need a way to re-initiate the handshake. Something like:

    protected Object[] doPeerCertificateChain(boolean force)
        throws IOException {
        Logger logger = SSLSelectorThread.logger();
        sslEngine.setNeedClientAuth(true);
        javax.security.cert.X509Certificate[] jsseCerts = null;
        try {
            jsseCerts = sslEngine.getSession().getPeerCertificateChain();
        } catch(Exception ex) {
            ;
        }
        
        if (jsseCerts == null)
            jsseCerts = new javax.security.cert.X509Certificate[0];
        
        /**
         * We need to initiate a new handshake.
         */
        if(jsseCerts.length <= 0 && force) {
            sslEngine.getSession().invalidate();
            sslEngine.beginHandshake();
            handshake= true;
            if (!doHandshake()){
                throw new IOException("Handshake failed");
            }
        }

        Certificate[] certs=null;
        try {
            certs = sslEngine.getSession().getPeerCertificates();
        } catch( Throwable t ) {
            if ( logger.isLoggable(Level.FINE))
                logger.log(Level.FINE,"Error getting client certs",t);
            return null;
        }

Here the doHandshake() implementation is the same used when doing the initial handshake. This is where it gets complicated in term of design, because the endpoint (here Servlet) needs to have a way to retrieve the SSLEngine and execute the handshake. Since the doHanshake() code is far from simple, then you don’t want it duplicated in several classes. For that, I’ve created the SSLUtil class. If you planning to use SSL with NIO, take a look at it (and find bugs :-).

The good news is we have benchmarked this implementation and the performance is much better than when using blocking socket. Now be aware that when you work with SSLEngine, you cannot uses interface X509KeyManager because SSLEngine instead require the use of X509ExtendedKeyManager. This fact is hidden in the documentation and the exception you are getting is quite confusing if you fail to implement it properly:


Caused by: javax.net.ssl.SSLHandshakeException: no cipher suites in common
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
at com.sun.net.ssl.internal.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1352)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:176)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:164)
at com.sun.net.ssl.internal.ssl.ServerHandshaker.chooseCipherSuite(ServerHandshaker.java:639)
at com.sun.net.ssl.internal.ssl.ServerHandshaker.clientHello(ServerHandshaker.java:450)

I was puzzled because the blocking case was working perfectly. Anyway just make sure you aren’t using X509KeyManager. I’ve filled a bug against the JDK as I suspect I will not be the only one that face this problem.

OK, this is it! As usual, feedback are more than welcome, and many thanks for all the great feedback I got from the previous blogs

_uacct = “UA-3111670-1″;
urchinTracker();

technorati:

About these ads
Categories: Uncategorized
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 51 other followers

%d bloggers like this: