From fa41f9e000a4b259026bd85fc9a49a21270aa6de 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 | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/ASN1.java b/src/main/java/org/jruby/ext/openssl/ASN1.java index fbe08bb9..0bf264f0 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; @@ -1076,19 +1076,21 @@ else if ( obj instanceof DERBMPString ) { final ASN1ApplicationSpecific appSpecific = (ASN1ApplicationSpecific) obj; IRubyObject tag = runtime.newFixnum( appSpecific.getApplicationTag() ); IRubyObject tag_class = runtime.newSymbol("APPLICATION"); - final ASN1Sequence sequence = (ASN1Sequence) appSpecific.getObject(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); + final ASN1TaggedObject taggedObj = (ASN1TaggedObject) obj; + 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 ASN1TaggedObject ) { final ASN1TaggedObject taggedObj = (ASN1TaggedObject) obj; 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); + IRubyObject tag_class = runtime.newSymbol("CONTEXT_SPECIFIC");; + if (BERTags.UNIVERSAL != taggedObj.getTagClass()) { + tag_class = runtime.newSymbol("UNIVERSAL"); + } + 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 ASN1Sequence ) { @@ -1383,29 +1385,51 @@ 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; - 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)); + obj = new DEROctetString(bytes.toByteArray()); + } else { + throw new IllegalStateException("empty array detected"); } - else if ( arr.size() == 1 ) { - ASN1Encodable data = ((ASN1Data) arr.entry(0)).toASN1(context); - return new DERTaggedObject(isExplicitTagging(), tag, data); + } else { + try { + obj = new DEROctetString(((RubyString) val).getBytes()); + } catch(ClassCastException e) { + throw context.runtime.newTypeError("no implicit conversion of " + val.getMetaClass().getName() + " into String"); } - else { - throw new IllegalStateException("empty array detected"); + } + + if (tagClass.toString().equals("APPLICATION")) { + try { + return new DERApplicationSpecific(isExplicitTagging(), tag, obj); + } catch (IOException e) { + throw new IllegalStateException(e.getMessage()); } + } else { + return new DERTaggedObject(isExplicitTagging(), tag, obj); } - return new DERTaggedObject(isExplicitTagging(), tag, ((ASN1Data) val).toASN1(context)); } + @JRubyMethod public IRubyObject to_der(final ThreadContext context) { try {