jruby / jruby-openssl

JRuby's OpenSSL gem
http://www.jruby.org
Other
45 stars 80 forks source link

JRuby-OpenSSL does not decode DERApplicationSpecific objects #36

Open mjstrasser opened 9 years ago

mjstrasser commented 9 years ago

I have an LDAP stub server implemented using ruby-ldapserver that I use in performance testing. It works with MRI Ruby but it fails under JRuby (I need to run it on a Solaris host where a suitable native Ruby is not available). The library uses OpenSSL to decode the ASN.1 data structures sent by clients.

I was unable to perform an LDAP bind with the server. I created https://github.com/inscitiv/ruby-ldapserver/issues/3 where I documented what I know so far. (Please ignore the first comments about how IntelliJ IDEA shows byte arrays in MRI Ruby vs. JRuby. That is a red herring.)

The first error message includes unable to decode object: org.bouncycastle.asn1.DERApplicationSpecific.

@dividedmind suggested an addition to ASN1.decodeObject method, but when I tried that I didn’t get much further. I think neither of us is confident we got it right. I am now stuck with the next IO#read not returning and am not sure what to do.

I don’t know which ASN.1 data structures the LDAP bind message conveys so I am not sure what to fix. Any advice will be greatly appreciated.

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/9613040-jruby-openssl-does-not-decode-derapplicationspecific-objects?utm_campaign=plugin&utm_content=tracker%2F136995&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F136995&utm_medium=issues&utm_source=github).
kares commented 9 years ago

Hi Michael, I've struggled to get ASN.1 somehow "more-compatible" with MRI recently ... been hitting limitations of what can be done with the underlying (bouncy-castle) APIs -but the changes mentioned at inscitiv/ruby-ldapserver#3 seems good, you should try running our test suite with those and/or possibly extracting the OpenSSL behaviour's from the ldapserver into a test-case or at least pieces of code, that way I can take a quick look (without an LDAP server) if we're able to get this fixes easily. Thanks for reporting!

mjstrasser commented 9 years ago

Hi Karol, I have done a bit more work in the LDAP context and found the following.

This LDAP bind request:

ldapsearch -D "o=Telstra" -w ess -b "msisdn=61414284256,o=Telstra" "(objectClass=*)"

first sends this array of 26 bytes to perform an LDAP bind:

"0\x18\x02\x01\x01`\x13\x02\x01\x03\x04\to=Telstra\x80\x03ess"

I think this deconstructs as follows:

48, 24, (SEQUENCE in 24 octets)
  2, 1, 1, (INTEGER value 1 = incrementing message counter)
  96, 19, (Application Constructed in 19 octets, tag 0 = LDAP bind)
    2, 1, 3, (INTEGER value 3 = LDAP version)
    4, 9, 111, 61, 84, 101, 108, 115, 116, 114, 97, (OCTET STRING value "o=Telstra")
    -128, 3, 101, 115, 115 (Context-specific in 3 octets, value "ess")

Debugging Ruby-LDAPServer under MRI Ruby and OpenSSL, the Application Constructed (96 = 01100000) data structure is returned with three elements (in a Ruby array):

  1. OpenSSL::ASN1::Integer : tag=2, tag_class=UNIVERSAL, value=3
  2. OpenSSL::ASN1::OctetString : tag=4, tag_class=UNIVERSAL, value="o=Telstra"
  3. OpenSSL::ASN1::ASN1Data : tag=0, tag_class=CONTEXT_SPECIFIC, value="ess"

Under JRuby and JRuby-OpenSSL (with my added code for DERApplicationSpecific objects) the array is mostly the same, except that third element has a value of OpenSSL::ASN1::OctetString instead of a Ruby string. The next read on the IO object never returns.

Does this shed some light on what the org.jruby.ext.openssl.ASN1 class might need to do to decode this correctly?

In this code:

        if ( obj instanceof DERApplicationSpecific) {
            final DERApplicationSpecific appSpecific = (DERApplicationSpecific) obj;
            IRubyObject tag = runtime.newFixnum(appSpecific.getApplicationTag());
            IRubyObject tag_class = runtime.newSymbol("APPLICATION");
            final ASN1Sequence sequence = (ASN1Sequence) appSpecific.getObject(SEQUENCE);
            final RubyArray valArr = decodeObjects(context, ASN1, sequence.getObjects());
            return ASN1.getClass("ASN1Data").callMethod(context, "new",
                new IRubyObject[] { valArr, tag, tag_class }
            );
        }

I wonder if SEQUENCE is the correct argument to getObject() but am not sure what the alternative could be.

kares commented 9 years ago

put your patches in (as they do not break anything) ... I'm able to HACK decoding the MRI way but I'm really not sure whether it's not too specific to your case. wasted too much of my life on trying to understand OpenSSL's ASN.1 logic :) and I really do not care except for helping others ... so I recommend you pick this up where I left if you really need it. also you might be able to come up with more encoding-decoding cases.

mjstrasser commented 9 years ago

Hi Karol, I have an alternative for my need – an LDAP stub server for performance testing. I found I can use a pure Ruby solution by modifying the example LDAP server provided with the net-ldap gem.

Once I learn better how ASN.1 works I will attempt to apply that to JRuby-OpenSSL.

kares commented 9 years ago

@mjstrasser excellent, thanks for the update!