Skip to content

Commit c3db400

Browse files
committed
[fix] encoding/decoding of all ASN1 string types
1 parent 46e5f87 commit c3db400

File tree

2 files changed

+65
-71
lines changed

2 files changed

+65
-71
lines changed

src/main/java/org/jruby/ext/openssl/ASN1.java

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@
3131
import java.io.IOException;
3232
import java.io.InputStream;
3333
import java.io.PrintStream;
34-
import java.lang.reflect.InvocationTargetException;
35-
import java.lang.reflect.Method;
3634
import java.math.BigInteger;
35+
import java.nio.charset.StandardCharsets;
3736
import java.text.ParseException;
3837
import java.util.Date;
3938
import java.util.Enumeration;
@@ -548,12 +547,12 @@ private static Map<ASN1ObjectIdentifier, String> getSymLookup(final Ruby runtime
548547
{ "NUMERICSTRING", org.bouncycastle.asn1.DERNumericString.class, "NumericString" },
549548
{ "PRINTABLESTRING", org.bouncycastle.asn1.DERPrintableString.class, "PrintableString" },
550549
{ "T61STRING", org.bouncycastle.asn1.DERT61String.class, "T61String" },
551-
{ "VIDEOTEXSTRING", null, "VideotexString" },
550+
{ "VIDEOTEXSTRING", org.bouncycastle.asn1.DERVideotexString.class, "VideotexString" },
552551
{ "IA5STRING", org.bouncycastle.asn1.DERIA5String.class, "IA5String" },
553552
{ "UTCTIME", org.bouncycastle.asn1.DERUTCTime.class, "UTCTime" },
554553
{ "GENERALIZEDTIME", org.bouncycastle.asn1.DERGeneralizedTime.class, "GeneralizedTime" },
555-
{ "GRAPHICSTRING", null, "GraphicString" },
556-
{ "ISO64STRING", null, "ISO64String" },
554+
{ "GRAPHICSTRING", org.bouncycastle.asn1.DERGraphicString.class, "GraphicString" },
555+
{ "ISO64STRING", org.bouncycastle.asn1.DERVisibleString.class, "ISO64String" },
557556
{ "GENERALSTRING", org.bouncycastle.asn1.DERGeneralString.class, "GeneralString" },
558557
// OpenSSL::ASN1::UNIVERSALSTRING (28) :
559558
{ "UNIVERSALSTRING", org.bouncycastle.asn1.DERUniversalString.class, "UniversalString" },
@@ -662,25 +661,6 @@ static Class<? extends ASN1Encodable> typeClassSafe(final int typeId) {
662661
return typeClass(typeId);
663662
}
664663

665-
static ASN1Encodable typeInstance(Class<? extends ASN1Encodable> type, Object value)
666-
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
667-
Method getInstance = null;
668-
try {
669-
getInstance = type.getMethod("getInstance", Object.class);
670-
}
671-
catch (NoSuchMethodException e) {
672-
Class superType = type.getSuperclass();
673-
try {
674-
if ( superType != Object.class ) {
675-
getInstance = type.getSuperclass().getMethod("getInstance", Object.class);
676-
}
677-
}
678-
catch (NoSuchMethodException e2) { }
679-
if ( getInstance == null ) throw e;
680-
}
681-
return (ASN1Encodable) getInstance.invoke(null, value);
682-
}
683-
684664
public static void createASN1(final Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) {
685665
final RubyModule ASN1 = OpenSSL.defineModuleUnder("ASN1");
686666
ASN1.defineClassUnder("ASN1Error", OpenSSLError, OpenSSLError.getAllocator());
@@ -976,7 +956,22 @@ static IRubyObject decodeObject(final ThreadContext context,
976956
final ByteList bytes;
977957
if ( obj instanceof ASN1UTF8String ) {
978958
if ( type == null ) type = "UTF8String";
979-
bytes = new ByteList(((ASN1UTF8String) obj).getString().getBytes("UTF-8"), false);
959+
bytes = new ByteList(((ASN1UTF8String) obj).getString().getBytes(StandardCharsets.UTF_8), false);
960+
}
961+
else if ( obj instanceof ASN1UniversalString ) {
962+
if ( type == null ) type = "UniversalString";
963+
bytes = new ByteList(((ASN1UniversalString) obj).getOctets(), false);
964+
}
965+
else if ( obj instanceof ASN1BMPString ) {
966+
if ( type == null ) type = "BMPString";
967+
final String val = ((ASN1BMPString) obj).getString();
968+
final byte[] valBytes = new byte[val.length() * 2];
969+
for (int i = 0; i < val.length(); i++) {
970+
char c = val.charAt(i);
971+
valBytes[i * 2] = (byte) ((c >> 8) & 0xff);
972+
valBytes[i * 2 + 1] = (byte) (c & 0xff);
973+
}
974+
bytes = new ByteList(valBytes, false);
980975
}
981976
else {
982977
if ( type == null ) {
@@ -995,14 +990,16 @@ else if ( obj instanceof ASN1T61String ) {
995990
else if ( obj instanceof ASN1GeneralString ) {
996991
type = "GeneralString";
997992
}
998-
else if ( obj instanceof ASN1UniversalString ) {
999-
type = "UniversalString";
993+
else if ( obj instanceof ASN1VideotexString ) {
994+
type = "VideotexString";
1000995
}
1001-
else if ( obj instanceof ASN1BMPString ) {
1002-
type = "BMPString";
996+
else if ( obj instanceof ASN1VisibleString ) {
997+
type = "ISO64String";
998+
}
999+
else if ( obj instanceof ASN1GraphicString ) {
1000+
type = "GraphicString";
10031001
}
10041002
else {
1005-
// NOTE "VideotexString", "GraphicString", "ISO64String" not-handled in BC !
10061003
throw new IllegalArgumentException("could not handle ASN1 string type: " + obj + " (" + obj.getClass().getName() + ")");
10071004
}
10081005
}
@@ -1652,38 +1649,50 @@ ASN1Encodable toASN1(final ThreadContext context) {
16521649
return new DERUTF8String( val.asString().toString() );
16531650
}
16541651
if ( type == DERBMPString.class ) {
1655-
return new DERBMPString( val.asString().toString() );
1652+
return new DERBMPString(new String(toBMPChars(val.asString().getByteList())));
16561653
}
16571654
if ( type == DERUniversalString.class ) {
16581655
return new DERUniversalString( val.asString().getBytes() );
16591656
}
16601657

16611658
if ( type == DERGeneralString.class ) {
1662-
return ASN1GeneralString.getInstance( val.asString().getBytes() );
1659+
return new DERGeneralString( val.asString().toString() );
16631660
}
16641661
if ( type == DERVisibleString.class ) {
1665-
return ASN1VisibleString.getInstance( val.asString().getBytes() );
1662+
return new DERVisibleString( val.asString().toString() );
16661663
}
16671664
if ( type == DERNumericString.class ) {
1668-
return ASN1NumericString.getInstance( val.asString().getBytes() );
1665+
return new DERNumericString( val.asString().toString() );
16691666
}
1670-
1671-
if ( val instanceof RubyString ) {
1672-
try {
1673-
return typeInstance(type, ( (RubyString) val ).getBytes());
1674-
}
1675-
catch (Exception e) { // TODO exception handling
1676-
debugStackTrace(context.runtime, e);
1677-
throw Utils.newError(context.runtime, context.runtime.getRuntimeError(), e);
1678-
}
1667+
if ( type == DERPrintableString.class ) {
1668+
return new DERPrintableString( val.asString().toString() );
1669+
}
1670+
if ( type == DERT61String.class ) {
1671+
return new DERT61String( val.asString().toString() );
1672+
}
1673+
if ( type == DERVideotexString.class ) {
1674+
return new DERVideotexString( val.asString().getBytes() );
1675+
}
1676+
if ( type == DERGraphicString.class ) {
1677+
return new DERGraphicString( val.asString().getBytes() );
16791678
}
16801679

1681-
// TODO throw an exception here too?
1682-
if ( isDebug(context.runtime) ) {
1680+
if (isDebug(context.runtime)) {
16831681
debug(this + " toASN1() could not handle class " + getMetaClass() + " and value " + val.inspect() + " (" + val.getClass().getName() + ")");
16841682
}
1685-
warn(context, "WARNING: unimplemented method called: OpenSSL::ASN1Data#toASN1 (" + type + ")");
1686-
return null;
1683+
throw context.runtime.newNotImplementedError("unimplemented method called: OpenSSL::ASN1Data#toASN1 (" + type + ")");
1684+
}
1685+
1686+
private static char[] toBMPChars(final ByteList string) {
1687+
assert string.length() % 2 == 0;
1688+
1689+
final int len = string.length() / 2;
1690+
final char[] chars = new char[len];
1691+
for (int i = 0; i < len; i++) {
1692+
int si = i * 2;
1693+
chars[i] = (char)((string.get(si) << 8) | (string.get(si + 1) & 0xff));
1694+
}
1695+
return chars;
16871696
}
16881697

16891698
private static BigInteger bigIntegerValue(final IRubyObject val) {

src/test/ruby/test_asn1.rb

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,31 +1220,16 @@ def test_string_basic
12201220
}
12211221
test.(4, OpenSSL::ASN1::OctetString)
12221222
test.(12, OpenSSL::ASN1::UTF8String)
1223-
# TODO: Import Issue
1224-
# The below tests cause NPEs in the first call to `encode_test` above
1225-
# org.jruby.ext.openssl.ASN1$Primitive.toDER(ASN1.java:1610)
1226-
# org.jruby.ext.openssl.ASN1$ASN1Data.to_der(ASN1.java:1414)
1227-
# org.jruby.ext.openssl.ASN1$Primitive.to_der(ASN1.java:1522)
1228-
# org.jruby.ext.openssl.ASN1$Primitive$INVOKER$i$0$0$to_der.call(ASN1$Primitive$INVOKER$i$0$0$to_der.gen)
1229-
#test.(18, OpenSSL::ASN1::NumericString)
1230-
#test.(19, OpenSSL::ASN1::PrintableString)
1231-
#test.(20, OpenSSL::ASN1::T61String)
1232-
#test.(21, OpenSSL::ASN1::VideotexString)
1223+
test.(18, OpenSSL::ASN1::NumericString)
1224+
test.(19, OpenSSL::ASN1::PrintableString)
1225+
test.(20, OpenSSL::ASN1::T61String)
1226+
test.(21, OpenSSL::ASN1::VideotexString)
12331227
test.(22, OpenSSL::ASN1::IA5String)
1234-
# See above
1235-
#test.(25, OpenSSL::ASN1::GraphicString)
1236-
#test.(26, OpenSSL::ASN1::ISO64String)
1237-
#test.(27, OpenSSL::ASN1::GeneralString)
1238-
1239-
# TODO: Import Issue
1240-
# This fails with:
1241-
# <""> expected but was <"#1C00">
1242-
#test.(28, OpenSSL::ASN1::UniversalString)
1243-
1244-
# TODO: Import Issue
1245-
# This fails with:
1246-
# <"\x1E\x02\x00\x01">(US-ASCII) expected but was <"\x1E\x04\x00\x00\x00\x01">(ASCII-8BIT)
1247-
#test.(30, OpenSSL::ASN1::BMPString)
1228+
test.(25, OpenSSL::ASN1::GraphicString)
1229+
test.(26, OpenSSL::ASN1::ISO64String)
1230+
test.(27, OpenSSL::ASN1::GeneralString)
1231+
test.(28, OpenSSL::ASN1::UniversalString)
1232+
test.(30, OpenSSL::ASN1::BMPString)
12481233
end
12491234

12501235
def test_constructive_each

0 commit comments

Comments
 (0)