Skip to content

Commit

Permalink
Fixing ASN1Data encoding and decoding
Browse files Browse the repository at this point in the history
`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.
  • Loading branch information
HoneyryderChuck committed May 2, 2024
1 parent 8e5e6ef commit 34a60e8
Showing 1 changed file with 82 additions and 27 deletions.
109 changes: 82 additions & 27 deletions src/main/java/org/jruby/ext/openssl/ASN1.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 34a60e8

Please sign in to comment.