The Source for Java Technology Collaboration
User: Password:



Jean-Francois Arcand

Jean-Francois Arcand's Blog

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

Posted by jfarcand on September 21, 2006 at 06:48 PM | Comments (5)

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

technorati:


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • hi there,

    "" I was investigating how I can add SSL support in GlassFish""

    I thought that Glassfish was a general purpose production ready appserver!! It surprises me that it does not have SSL support. How is it going to compete with JBoss or any of the commercial AppServers ?

    BR,
    ~A

    Posted by: anjanb2 on September 22, 2006 at 10:35 AM

  • SSL is supported since the beginning, but was using blocking Socket (like JBoss is using). What I've just did is to turn non blocking support on :-)

    -- Jeanfrancois

    Posted by: jfarcand on September 22, 2006 at 10:41 AM

  • Can you share the % difference you have observed in the performance & scalability between SSL over java.net.Socket (blocking) versus SSL over the NIO SocketChannel (non-blocking) implementations ?

    Posted by: huntch on September 24, 2006 at 05:40 PM

  • That's a good idea. As soon as I get out of my client trip I will gives some info.
    -- Jeanfrancois

    Posted by: jfarcand on September 27, 2006 at 04:57 PM


  • For discovering NIO, i'm currently developping an API that allow to exchange java-objects between a server and n-clients.

    Writing with NIO, all your tricks was a great help and this day my API works fine.

    The next step for me is to enable TLS encryption (i don't need to manage authentification because it's for me beyound the scope of this API and must be done on another layer)

    At this time the "keystore" feature annoying me because if i put theses (client keystore and server keystore) on the API Jar-file, all programs will contains both.
    Maybe i can use an unique keystore for all entities if the handshake use some randomization, but it's less secure ?

    It's all questions that hurt my spirit at this time.

    I had also the idea to wrote my little encryption protocol using
    - RSA key pair generated by the client
    - RSAPublicKey sended to the server
    - AES key generated by the server
    - AES key encrypted by the RSAPublicKey and sended to the client
    - Discard all informations about the RSA keypair (both side)
    - All exchange is done by AES encryption using the key

    But it's less "reliable" than a TLS service.

    When i

    Posted by: divxdede on September 01, 2007 at 10:38 AM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds