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 8, 2024
1 parent 4a33a85 commit e3d4460
Showing 1 changed file with 51 additions and 17 deletions.
68 changes: 51 additions & 17 deletions src/main/java/org/jruby/ext/openssl/ASN1.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

0 comments on commit e3d4460

Please sign in to comment.