From 34a60e8670dd231eaa083d733e563f21d2b13e5b Mon Sep 17 00:00:00 2001 From: HoneyryderChuck Date: Thu, 6 Apr 2023 07:04:39 +0100 Subject: [PATCH] Fixing ASN1Data encoding and decoding `OpenSSL::ASN1::ASN1Data` encoding and decoding hasn't worked well for some time: * it was decoding the value always as a Sequence (when values can be strings as well); * after decoding, the `.tag_class` was always `:CONTEXT_SPECIFIC` (it can be `:UNIVERSAL` as well); * it wasn't correctly encoding `:APPLICATION`-tagged structures; The tests added verify that the behaviour of these operations now matchws what CRuby's `openssl` gem does. --- src/main/java/org/jruby/ext/openssl/ASN1.java | 109 +++++++++++++----- 1 file changed, 82 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/ASN1.java b/src/main/java/org/jruby/ext/openssl/ASN1.java index b2efe10c..46fec900 100644 --- a/src/main/java/org/jruby/ext/openssl/ASN1.java +++ b/src/main/java/org/jruby/ext/openssl/ASN1.java @@ -956,7 +956,7 @@ private static RubyString name(final ThreadContext context, IRubyObject value, } // ObjectId static IRubyObject decodeObject(final ThreadContext context, - final RubyModule ASN1, final org.bouncycastle.asn1.ASN1Encodable obj) + final RubyModule ASN1, final ASN1Encodable obj) throws IOException, IllegalArgumentException { final Ruby runtime = context.runtime; @@ -1072,22 +1072,47 @@ else if ( obj instanceof DERBMPString ) { return ASN1.getClass("ObjectId").newInstance(context, runtime.newString(objId), Block.NULL_BLOCK); } - if (obj instanceof ASN1TaggedObject) { + + if ( obj instanceof ASN1TaggedObject ) { final ASN1TaggedObject taggedObj = (ASN1TaggedObject) obj; - if (taggedObj.getTagClass() == BERTags.APPLICATION) { - IRubyObject tag = runtime.newFixnum( taggedObj.getTagNo() ); - IRubyObject tag_class = runtime.newSymbol("APPLICATION"); - final ASN1Sequence sequence = (ASN1Sequence) taggedObj.getBaseUniversal(false, SEQUENCE); - @SuppressWarnings("unchecked") + // IRubyObject val = decodeObject(context, ASN1, taggedObj.getBaseObject()); + IRubyObject tag = runtime.newFixnum( taggedObj.getTagNo() ); + IRubyObject tag_class; + switch(taggedObj.getTagClass()) { + case BERTags.PRIVATE: + tag_class = runtime.newSymbol("PRIVATE"); + break; + case BERTags.APPLICATION: + tag_class = runtime.newSymbol("APPLICATION"); + break; + case BERTags.CONTEXT_SPECIFIC: + tag_class = runtime.newSymbol("CONTEXT_SPECIFIC"); + break; + default: + tag_class = runtime.newSymbol("UNIVERSAL"); + break; + } + + try { + final ASN1Sequence sequence = (ASN1Sequence) taggedObj.getExplicitBaseObject(); final RubyArray valArr = decodeObjects(context, ASN1, sequence.getObjects()); return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { valArr, tag, tag_class }, Block.NULL_BLOCK); - } else { - IRubyObject val = decodeObject(context, ASN1, taggedObj.getBaseObject()); - IRubyObject tag = runtime.newFixnum( taggedObj.getTagNo() ); - IRubyObject tag_class = runtime.newSymbol("CONTEXT_SPECIFIC"); - final RubyArray valArr = runtime.newArray(val); - return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { valArr, tag, tag_class }, Block.NULL_BLOCK); + } catch(ClassCastException e) { + final ASN1Encodable taggedBaseObj = taggedObj.getBaseObject(); + return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { decodeObject(context, ASN1, taggedBaseObj).callMethod(context, "value"), tag, tag_class }, Block.NULL_BLOCK); } + + // if ( obj instanceof DLTaggedObject ) { // isConstructed + // System.out.println("ASN1Data constructive!"); + // final ASN1Sequence sequence = (ASN1Sequence) taggedObj.getBaseUniversal(false, SEQUENCE); + // @SuppressWarnings("unchecked") + // final RubyArray valArr = decodeObjects(context, ASN1, sequence.getObjects()); + // return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { valArr, tag, tag_class }, Block.NULL_BLOCK); + // } else { + // final ASN1Encodable taggedBaseObj = taggedObj.getBaseObject(); + // System.out.println("ASN1Data primitive!"); + // return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { decodeObject(context, ASN1, taggedBaseObj).callMethod(context, "value"), tag, tag_class }, Block.NULL_BLOCK); + // } } if ( obj instanceof ASN1Sequence ) { @@ -1384,29 +1409,59 @@ ASN1Encodable toASN1(final ThreadContext context) { final ASN1TaggedObject toASN1TaggedObject(final ThreadContext context) { final int tag = getTag(context); + final RubyModule ASN1 = _ASN1(context.runtime); + + IRubyObject val = callMethod(context, "value"); + final IRubyObject tagClass = callMethod(context, "tag_class"); + ASN1Encodable obj; + int berTag; - final IRubyObject val = callMethod(context, "value"); if ( val instanceof RubyArray ) { final RubyArray arr = (RubyArray) val; - if ( arr.size() > 1 ) { - ASN1EncodableVector vec = new ASN1EncodableVector(); - for ( final IRubyObject obj : arr.toJavaArray() ) { - ASN1Encodable data = ((ASN1Data) obj).toASN1(context); - if ( data == null ) break; vec.add( data ); + + if ( arr.size() >= 1 ) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream( ); + + for ( final IRubyObject o : arr.toJavaArray() ) { + try { + bytes.write(((RubyString) o).getBytes()); + } catch (IOException e) { + throw new IllegalStateException(e.getMessage()); + } catch(ClassCastException e) { + throw context.runtime.newTypeError(e.getMessage()); + } } - return new DERTaggedObject(isExplicitTagging(), tag, new DERSequence(vec)); - } - else if ( arr.size() == 1 ) { - ASN1Encodable data = ((ASN1Data) arr.entry(0)).toASN1(context); - return new DERTaggedObject(isExplicitTagging(), tag, data); - } - else { + obj = new DEROctetString(bytes.toByteArray()); + } else { throw new IllegalStateException("empty array detected"); } + } else { + try { + obj = new DEROctetString(((RubyString) val).getBytes()); + } catch(ClassCastException e) { + throw context.runtime.newTypeError("no implicit conversion of " + val.getMetaClass().getName() + " into String"); + } } - return new DERTaggedObject(isExplicitTagging(), tag, ((ASN1Data) val).toASN1(context)); + + switch(tagClass.toString()) { + case "PRIVATE": + berTag = BERTags.PRIVATE; + break; + case "APPLICATION": + berTag = BERTags.APPLICATION; + break; + case "CONTEXT_SPECIFIC": + berTag = BERTags.CONTEXT_SPECIFIC; + break; + default: + berTag = BERTags.UNIVERSAL; + break; + } + + return new DERTaggedObject(isExplicitTagging(), berTag, tag, obj); } + @JRubyMethod public IRubyObject to_der(final ThreadContext context) { try {