Skip to content

Commit

Permalink
[fix] encoding/decoding of all ASN1 string types
Browse files Browse the repository at this point in the history
  • Loading branch information
kares committed May 5, 2024
1 parent 46e5f87 commit c3db400
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 71 deletions.
103 changes: 56 additions & 47 deletions src/main/java/org/jruby/ext/openssl/ASN1.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Date;
import java.util.Enumeration;
Expand Down Expand Up @@ -548,12 +547,12 @@ private static Map<ASN1ObjectIdentifier, String> getSymLookup(final Ruby runtime
{ "NUMERICSTRING", org.bouncycastle.asn1.DERNumericString.class, "NumericString" },
{ "PRINTABLESTRING", org.bouncycastle.asn1.DERPrintableString.class, "PrintableString" },
{ "T61STRING", org.bouncycastle.asn1.DERT61String.class, "T61String" },
{ "VIDEOTEXSTRING", null, "VideotexString" },
{ "VIDEOTEXSTRING", org.bouncycastle.asn1.DERVideotexString.class, "VideotexString" },
{ "IA5STRING", org.bouncycastle.asn1.DERIA5String.class, "IA5String" },
{ "UTCTIME", org.bouncycastle.asn1.DERUTCTime.class, "UTCTime" },
{ "GENERALIZEDTIME", org.bouncycastle.asn1.DERGeneralizedTime.class, "GeneralizedTime" },
{ "GRAPHICSTRING", null, "GraphicString" },
{ "ISO64STRING", null, "ISO64String" },
{ "GRAPHICSTRING", org.bouncycastle.asn1.DERGraphicString.class, "GraphicString" },
{ "ISO64STRING", org.bouncycastle.asn1.DERVisibleString.class, "ISO64String" },
{ "GENERALSTRING", org.bouncycastle.asn1.DERGeneralString.class, "GeneralString" },
// OpenSSL::ASN1::UNIVERSALSTRING (28) :
{ "UNIVERSALSTRING", org.bouncycastle.asn1.DERUniversalString.class, "UniversalString" },
Expand Down Expand Up @@ -662,25 +661,6 @@ static Class<? extends ASN1Encodable> typeClassSafe(final int typeId) {
return typeClass(typeId);
}

static ASN1Encodable typeInstance(Class<? extends ASN1Encodable> type, Object value)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Method getInstance = null;
try {
getInstance = type.getMethod("getInstance", Object.class);
}
catch (NoSuchMethodException e) {
Class superType = type.getSuperclass();
try {
if ( superType != Object.class ) {
getInstance = type.getSuperclass().getMethod("getInstance", Object.class);
}
}
catch (NoSuchMethodException e2) { }
if ( getInstance == null ) throw e;
}
return (ASN1Encodable) getInstance.invoke(null, value);
}

public static void createASN1(final Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) {
final RubyModule ASN1 = OpenSSL.defineModuleUnder("ASN1");
ASN1.defineClassUnder("ASN1Error", OpenSSLError, OpenSSLError.getAllocator());
Expand Down Expand Up @@ -976,7 +956,22 @@ static IRubyObject decodeObject(final ThreadContext context,
final ByteList bytes;
if ( obj instanceof ASN1UTF8String ) {
if ( type == null ) type = "UTF8String";
bytes = new ByteList(((ASN1UTF8String) obj).getString().getBytes("UTF-8"), false);
bytes = new ByteList(((ASN1UTF8String) obj).getString().getBytes(StandardCharsets.UTF_8), false);
}
else if ( obj instanceof ASN1UniversalString ) {
if ( type == null ) type = "UniversalString";
bytes = new ByteList(((ASN1UniversalString) obj).getOctets(), false);
}
else if ( obj instanceof ASN1BMPString ) {
if ( type == null ) type = "BMPString";
final String val = ((ASN1BMPString) obj).getString();
final byte[] valBytes = new byte[val.length() * 2];
for (int i = 0; i < val.length(); i++) {
char c = val.charAt(i);
valBytes[i * 2] = (byte) ((c >> 8) & 0xff);
valBytes[i * 2 + 1] = (byte) (c & 0xff);
}
bytes = new ByteList(valBytes, false);
}
else {
if ( type == null ) {
Expand All @@ -995,14 +990,16 @@ else if ( obj instanceof ASN1T61String ) {
else if ( obj instanceof ASN1GeneralString ) {
type = "GeneralString";
}
else if ( obj instanceof ASN1UniversalString ) {
type = "UniversalString";
else if ( obj instanceof ASN1VideotexString ) {
type = "VideotexString";
}
else if ( obj instanceof ASN1BMPString ) {
type = "BMPString";
else if ( obj instanceof ASN1VisibleString ) {
type = "ISO64String";
}
else if ( obj instanceof ASN1GraphicString ) {
type = "GraphicString";
}
else {
// NOTE "VideotexString", "GraphicString", "ISO64String" not-handled in BC !
throw new IllegalArgumentException("could not handle ASN1 string type: " + obj + " (" + obj.getClass().getName() + ")");
}
}
Expand Down Expand Up @@ -1652,38 +1649,50 @@ ASN1Encodable toASN1(final ThreadContext context) {
return new DERUTF8String( val.asString().toString() );
}
if ( type == DERBMPString.class ) {
return new DERBMPString( val.asString().toString() );
return new DERBMPString(new String(toBMPChars(val.asString().getByteList())));
}
if ( type == DERUniversalString.class ) {
return new DERUniversalString( val.asString().getBytes() );
}

if ( type == DERGeneralString.class ) {
return ASN1GeneralString.getInstance( val.asString().getBytes() );
return new DERGeneralString( val.asString().toString() );
}
if ( type == DERVisibleString.class ) {
return ASN1VisibleString.getInstance( val.asString().getBytes() );
return new DERVisibleString( val.asString().toString() );
}
if ( type == DERNumericString.class ) {
return ASN1NumericString.getInstance( val.asString().getBytes() );
return new DERNumericString( val.asString().toString() );
}

if ( val instanceof RubyString ) {
try {
return typeInstance(type, ( (RubyString) val ).getBytes());
}
catch (Exception e) { // TODO exception handling
debugStackTrace(context.runtime, e);
throw Utils.newError(context.runtime, context.runtime.getRuntimeError(), e);
}
if ( type == DERPrintableString.class ) {
return new DERPrintableString( val.asString().toString() );
}
if ( type == DERT61String.class ) {
return new DERT61String( val.asString().toString() );
}
if ( type == DERVideotexString.class ) {
return new DERVideotexString( val.asString().getBytes() );
}
if ( type == DERGraphicString.class ) {
return new DERGraphicString( val.asString().getBytes() );
}

// TODO throw an exception here too?
if ( isDebug(context.runtime) ) {
if (isDebug(context.runtime)) {
debug(this + " toASN1() could not handle class " + getMetaClass() + " and value " + val.inspect() + " (" + val.getClass().getName() + ")");
}
warn(context, "WARNING: unimplemented method called: OpenSSL::ASN1Data#toASN1 (" + type + ")");
return null;
throw context.runtime.newNotImplementedError("unimplemented method called: OpenSSL::ASN1Data#toASN1 (" + type + ")");
}

private static char[] toBMPChars(final ByteList string) {
assert string.length() % 2 == 0;

final int len = string.length() / 2;
final char[] chars = new char[len];
for (int i = 0; i < len; i++) {
int si = i * 2;
chars[i] = (char)((string.get(si) << 8) | (string.get(si + 1) & 0xff));
}
return chars;
}

private static BigInteger bigIntegerValue(final IRubyObject val) {
Expand Down
33 changes: 9 additions & 24 deletions src/test/ruby/test_asn1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1220,31 +1220,16 @@ def test_string_basic
}
test.(4, OpenSSL::ASN1::OctetString)
test.(12, OpenSSL::ASN1::UTF8String)
# TODO: Import Issue
# The below tests cause NPEs in the first call to `encode_test` above
# org.jruby.ext.openssl.ASN1$Primitive.toDER(ASN1.java:1610)
# org.jruby.ext.openssl.ASN1$ASN1Data.to_der(ASN1.java:1414)
# org.jruby.ext.openssl.ASN1$Primitive.to_der(ASN1.java:1522)
# org.jruby.ext.openssl.ASN1$Primitive$INVOKER$i$0$0$to_der.call(ASN1$Primitive$INVOKER$i$0$0$to_der.gen)
#test.(18, OpenSSL::ASN1::NumericString)
#test.(19, OpenSSL::ASN1::PrintableString)
#test.(20, OpenSSL::ASN1::T61String)
#test.(21, OpenSSL::ASN1::VideotexString)
test.(18, OpenSSL::ASN1::NumericString)
test.(19, OpenSSL::ASN1::PrintableString)
test.(20, OpenSSL::ASN1::T61String)
test.(21, OpenSSL::ASN1::VideotexString)
test.(22, OpenSSL::ASN1::IA5String)
# See above
#test.(25, OpenSSL::ASN1::GraphicString)
#test.(26, OpenSSL::ASN1::ISO64String)
#test.(27, OpenSSL::ASN1::GeneralString)

# TODO: Import Issue
# This fails with:
# <""> expected but was <"#1C00">
#test.(28, OpenSSL::ASN1::UniversalString)

# TODO: Import Issue
# This fails with:
# <"\x1E\x02\x00\x01">(US-ASCII) expected but was <"\x1E\x04\x00\x00\x00\x01">(ASCII-8BIT)
#test.(30, OpenSSL::ASN1::BMPString)
test.(25, OpenSSL::ASN1::GraphicString)
test.(26, OpenSSL::ASN1::ISO64String)
test.(27, OpenSSL::ASN1::GeneralString)
test.(28, OpenSSL::ASN1::UniversalString)
test.(30, OpenSSL::ASN1::BMPString)
end

def test_constructive_each
Expand Down

0 comments on commit c3db400

Please sign in to comment.