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 Apr 6, 2023
1 parent 823c9a9 commit fa41f9e
Showing 1 changed file with 45 additions and 21 deletions.
66 changes: 45 additions & 21 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 @@ -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 ) {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit fa41f9e

Please sign in to comment.