@@ -1216,8 +1216,8 @@ private static IRubyObject decodeImpl(final ThreadContext context, final RubyMod
12161216 }
12171217
12181218 @ JRubyMethod (meta = true , required = 1 )
1219- public static IRubyObject decode_all (final ThreadContext context ,
1220- final IRubyObject self , IRubyObject obj ) {
1219+ public static IRubyObject decode_all (final ThreadContext context , final IRubyObject self ,
1220+ IRubyObject obj ) {
12211221 obj = to_der_if_possible (context , obj );
12221222
12231223 BytesInputStream in = new BytesInputStream ( obj .asString ().getByteList () );
@@ -1239,10 +1239,170 @@ public static IRubyObject decode_all(final ThreadContext context,
12391239 return arr ;
12401240 }
12411241
1242- @ JRubyMethod (meta = true , required = 1 )
1243- public static IRubyObject traverse (final ThreadContext context , final IRubyObject self , IRubyObject arg ) {
1244- warn (context , "WARNING: unimplemented method called: OpenSSL::ASN1#traverse" );
1245- return context .runtime .getNil ();
1242+ @ JRubyMethod (meta = true )
1243+ public static IRubyObject traverse (final ThreadContext context , final IRubyObject self ,
1244+ IRubyObject arg , Block block ) {
1245+ arg = to_der_if_possible (context , arg );
1246+
1247+ final ByteList byteList = arg .asString ().getByteList ();
1248+ final byte [] bytes = byteList .unsafeBytes ();
1249+ final int begin = byteList .getBegin ();
1250+ final int length = byteList .getRealSize ();
1251+
1252+ final TraverseResult result = traverse (context , bytes , begin , begin + length , begin , 0 , block );
1253+ if (length != 0 && result .read != length ) {
1254+ throw newASN1Error (context .runtime ,
1255+ "Type mismatch. Total bytes read: " + result .read +
1256+ " Bytes available: " + length + " Offset: " + result .offset );
1257+ }
1258+ return context .nil ;
1259+ }
1260+
1261+ private static TraverseResult traverse (final ThreadContext context ,
1262+ final byte [] bytes , final int begin , final int end ,
1263+ final int base , final int depth ,
1264+ final Block block ) {
1265+
1266+ final Ruby runtime = context .runtime ;
1267+
1268+ if (begin >= end ) throw newASN1Error (runtime , "header too short" );
1269+
1270+ int cursor = begin ;
1271+ final int start = begin ;
1272+ final int identifier = bytes [cursor ++] & 0xFF ;
1273+ final int tagClass = identifier & 0xC0 ;
1274+ final boolean constructed = (identifier & BERTags .CONSTRUCTED ) != 0 ;
1275+
1276+ int tag = identifier & 0x1F ;
1277+ if (tag == 0x1F ) {
1278+ tag = 0 ;
1279+ if (cursor >= end ) throw newASN1Error (runtime , "EOF found inside tag value" );
1280+
1281+ int b = bytes [cursor ++] & 0xFF ;
1282+ if ((b & 0x7F ) == 0 ) {
1283+ throw newASN1Error (runtime , "corrupted stream - invalid high tag number found" );
1284+ }
1285+
1286+ while ((b & 0x80 ) != 0 ) {
1287+ tag |= (b & 0x7F );
1288+ tag <<= 7 ;
1289+ if (cursor >= end ) throw newASN1Error (runtime , "EOF found inside tag value." );
1290+ b = bytes [cursor ++] & 0xFF ;
1291+ }
1292+ tag |= (b & 0x7F );
1293+ }
1294+
1295+ if (cursor >= end ) throw newASN1Error (runtime , "header too short" );
1296+
1297+ final int lengthByte = bytes [cursor ++] & 0xFF ;
1298+ final boolean indefinite = lengthByte == 0x80 ;
1299+ long contentLength = 0 ;
1300+ if (!indefinite ) {
1301+ if ((lengthByte & 0x80 ) == 0 ) {
1302+ contentLength = lengthByte ;
1303+ }
1304+ else {
1305+ final int lengthBytes = lengthByte & 0x7F ;
1306+ if (lengthBytes == 0 || lengthBytes > 8 ) {
1307+ throw newASN1Error (runtime , "invalid length encoding" );
1308+ }
1309+ if (cursor + lengthBytes > end ) throw newASN1Error (runtime , "header too short" );
1310+ for (int i = 0 ; i < lengthBytes ; i ++) {
1311+ contentLength = (contentLength << 8 ) | (bytes [cursor ++] & 0xFFL );
1312+ }
1313+ }
1314+ if (contentLength > (long ) end - cursor ) {
1315+ throw newASN1Error (runtime , "value is too short" );
1316+ }
1317+ }
1318+
1319+ final int headerLength = cursor - start ;
1320+ if (block .isGiven ()) {
1321+ block .yield (context ,
1322+ runtime .newArray (
1323+ RubyBignum .bignorm (runtime , BigInteger .valueOf (depth )),
1324+ RubyBignum .bignorm (runtime , BigInteger .valueOf (start - base )),
1325+ RubyBignum .bignorm (runtime , BigInteger .valueOf (headerLength )),
1326+ RubyBignum .bignorm (runtime , BigInteger .valueOf (contentLength )),
1327+ runtime .newBoolean (constructed ),
1328+ tagClassSymbol (runtime , tagClass ),
1329+ RubyBignum .bignorm (runtime , BigInteger .valueOf (tag ))
1330+ )
1331+ );
1332+ }
1333+
1334+ int read = headerLength ;
1335+ int offset = (start - base ) + headerLength ;
1336+
1337+ if (constructed ) {
1338+ if (indefinite ) {
1339+ int available = end - cursor ;
1340+ while (available > 0 ) {
1341+ final TraverseResult inner = traverse (context , bytes , cursor , end , base , depth + 1 , block );
1342+ read += inner .read ;
1343+ cursor += inner .read ;
1344+ available -= inner .read ;
1345+ offset = inner .offset ;
1346+
1347+ if (inner .isEoc () && inner .tagClass == BERTags .UNIVERSAL ) break ;
1348+ if (available == 0 ) {
1349+ throw newASN1Error (context .runtime , "EOC missing in indefinite length encoding" );
1350+ }
1351+ }
1352+ }
1353+ else {
1354+ final int contentEnd = cursor + (int ) contentLength ;
1355+ while (cursor < contentEnd ) {
1356+ final TraverseResult inner = traverse (context , bytes , cursor , contentEnd , base , depth + 1 , block );
1357+ read += inner .read ;
1358+ cursor += inner .read ;
1359+ offset = inner .offset ;
1360+ }
1361+ }
1362+ }
1363+ else {
1364+ if (indefinite ) throw newASN1Error (context .runtime , "indefinite length for primitive value" );
1365+ read += contentLength ;
1366+ offset += (int ) contentLength ;
1367+ }
1368+
1369+ if (!indefinite && read != headerLength + contentLength ) {
1370+ throw newASN1Error (context .runtime ,
1371+ "Type mismatch. Bytes read: " + read + " Bytes available: " + (headerLength + contentLength ));
1372+ }
1373+
1374+ return new TraverseResult (read , offset , tag , tagClass );
1375+ }
1376+
1377+ private static IRubyObject tagClassSymbol (final Ruby runtime , final int tagClass ) {
1378+ switch (tagClass ) {
1379+ case BERTags .PRIVATE :
1380+ return runtime .newSymbol ("PRIVATE" );
1381+ case BERTags .APPLICATION :
1382+ return runtime .newSymbol ("APPLICATION" );
1383+ case BERTags .CONTEXT_SPECIFIC :
1384+ return runtime .newSymbol ("CONTEXT_SPECIFIC" );
1385+ default :
1386+ return runtime .newSymbol ("UNIVERSAL" );
1387+ }
1388+ }
1389+
1390+ private static final class TraverseResult {
1391+ private final int read ;
1392+ private final int offset ;
1393+ private final int tag ;
1394+ private final int tagClass ;
1395+
1396+ private TraverseResult (final int read , final int offset , final int tag , final int tagClass ) {
1397+ this .read = read ;
1398+ this .offset = offset ;
1399+ this .tag = tag ;
1400+ this .tagClass = tagClass ;
1401+ }
1402+
1403+ boolean isEoc () {
1404+ return tag == 0 ;
1405+ }
12461406 }
12471407
12481408 public static RaiseException newASN1Error (Ruby runtime , String message ) {
0 commit comments