OpenSSL in JRuby


This weekend I started work on OpenSSL in JRuby. It’s a pretty big undertaking, and it’s actually worse than I suspected from the beginning, so I’m going to tell a bit about my endeavours and have far I’ve gotten right now. First of all, if someone is interested in the work, I have set up a branch for this, since it will take time and be very big. The branch is called svn://svn.codehaus.org/jruby/branches/openssl.

The approach
I have investigated several variants of implementing OpenSSL in JRuby. One that seemed easy was to find a JNI-library that wraps OpenSSL in Java. But it seems there are no full scale versions implemented anywhere. I also checked around other script-on-JVM-languages to see how they had solved it, but I didn’t find anything close to OpenSSL functionality. Which left me with the approach I’ve decided to try out: implement as OpenSSL compatible Java code as possible with JCE and JSSE. I’m convinced this is doable, but right now it feels quite hard.

The progress
So, how far have I gotten these last days? Pretty far, but not far enough. I’m basing the work on MRI’s test suite for OpenSSL. From those I have test_digest.rb and test_cipher.rb passing all tests. This doesn’t sound like much, but especially test_cipher was a pain to get running.

The plan from hereon is to get the utils.rb-file to load, which means implementing OpenSSL::PKey::RSA and OpenSSL::PKey::DSA, getting the basics of OpenSSL::X509 in place and also find a way to fix OpenSSL::ASN1. Oh well, I’ve got loads of time for this. Or not. =)

The problem
The real problem when implementing this, is the fact that Ruby’s OpenSSL support is… Well, how shall I put it? Thin, you might say. It’s basically a a wrapper around the C-library, which means that the disconnect when implementing this functionality with JCE is quite large. Just translating OpenSSL cipher names to the JCE equivalent is a challenge. But the big problem with the ciphers was initiating the key and IV (initialization vector). I have tried all the PBE solutions available, including the versions in BouncyCastle ending with “-OPENSSL”. No luck.

The problem is that Ruby uses the function called EVP_BytesToKey, which, according to the documentation implements PKCS#5 1.5 with a few tricks up its sleeve. Not very nice. In the end I had to implement my own version of this to generate keys. And since I had to look like mad for this information, I will here give you the implementation to this function in Java. Just use the return value to initialize your own SecretKey-implementation and instantiate an IvParameterSpec and you should be set to go: (note, I release this into the public domain. And note, this is just quick, ported code to show the concept.)

    public byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data, int count) {       byte[][] both = new byte[2][];       byte[] key = new byte[key_len];       int key_ix = 0;       byte[]  iv = new byte[iv_len];       int iv_ix = 0;       both[0] = key;       both[1] = iv;       byte[] md_buf = null;       int nkey = key_len;       int niv = iv_len;       int i = 0;       if(data == null) {           return both;       }       int addmd = 0;       for(;;) {           md.reset();           if(addmd++ > 0) {               md.update(md_buf);           }           md.update(data);           if(null != salt) {               md.update(salt,0,8);           }           md_buf = md.digest();           for(i=1;i<count;i++) {               md.reset();               md.update(md_buf);               md_buf = md.digest();           }           i=0;           if(nkey > 0) {               for(;;) {                   if(nkey == 0) break;                   if(i == md_buf.length) break;                   key[key_ix++] = md_buf[i];                   nkey--;                   i++;               }           }           if(niv > 0 && i != md_buf.length) {               for(;;) {                   if(niv == 0) break;                   if(i == md_buf.length) break;                   iv[iv_ix++] = md_buf[i];                   niv--;                   i++;               }           }           if(nkey == 0 && niv == 0) {               break;           }       }       for(i=0;i<md_buf.length;i++) {           md_buf[i] = 0;       }       return both;   }