From e3d4460e4896eafa39964abf6127a746b43909b8 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 | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/jruby/ext/openssl/ASN1.java b/src/main/java/org/jruby/ext/openssl/ASN1.java index 2444d9e9..c4dbd542 100644 --- a/src/main/java/org/jruby/ext/openssl/ASN1.java +++ b/src/main/java/org/jruby/ext/openssl/ASN1.java @@ -934,7 +934,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; @@ -1046,20 +1046,34 @@ else if ( obj instanceof ASN1GraphicString ) { 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; + } + + final ASN1Encodable taggedBaseObj = taggedObj.getBaseObject(); + + // TODO: how to infer that this tagged object is constructed + if (taggedBaseObj instanceof ASN1Sequence) { + final ASN1Sequence sequence = (ASN1Sequence) taggedBaseObj; 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); } + + 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 ) { @@ -1367,27 +1381,47 @@ ASN1Encodable toASN1(final ThreadContext context) { final ASN1TaggedObject toASN1TaggedObject(final ThreadContext context) { final int tag = getTag(context); + final RubyModule ASN1 = _ASN1(context.runtime); + + IRubyObject value = callMethod(context, "value"); + final IRubyObject tagClass = callMethod(context, "tag_class"); + int berTag; + String tagClassName = tagClass.toString(); + + if (tagClassName.equals("PRIVATE")) { + berTag = BERTags.PRIVATE; + } else if (tagClassName.equals("APPLICATION")) { + berTag = BERTags.APPLICATION; + } else if (tagClassName.equals("CONTEXT_SPECIFIC")) { + berTag = BERTags.CONTEXT_SPECIFIC; + } else { + berTag = BERTags.UNIVERSAL; + } - final IRubyObject value = callMethod(context, "value"); - if (value instanceof RubyArray) { + if ( value instanceof RubyArray ) { final RubyArray arr = (RubyArray) value; assert ! arr.isEmpty(); ASN1EncodableVector vec = new ASN1EncodableVector(); - for (final IRubyObject obj : arr.toJavaArray()) { + for ( final IRubyObject obj : arr.toJavaArray() ) { ASN1Encodable data = ((ASN1Data) obj).toASN1(context); if ( data == null ) break; vec.add( data ); } - return new DERTaggedObject(isExplicitTagging(), tag, new DERSequence(vec)); + + // TODO: it's not possible to generate a constructed tagged object, bc raises exception + // berTag = berTag | BERTags.CONSTRUCTED; + return new DERTaggedObject(isExplicitTagging(), berTag, tag, new DERSequence(vec)); } if (!(value instanceof ASN1Data)) { throw new UnsupportedOperationException("toASN1 " + inspect() + " value: " + value.inspect() + " (" + value.getMetaClass() + ")"); } - return new DERTaggedObject(isExplicitTagging(), tag, ((ASN1Data) value).toASN1(context)); + + return new DERTaggedObject(isExplicitTagging(), berTag, tag, ((ASN1Data) value).toASN1(context)); } + @JRubyMethod public IRubyObject to_der(final ThreadContext context) { try {