package aQute.libg.asn1; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; public class BER implements Types { final static DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss\\Z"); final DataInputStream xin; long position; public BER(InputStream in) { this.xin = new DataInputStream(in); } public void dump(PrintStream out) throws Exception { int type = readByte(); long length = readLength(); if (type == -1 || length == -1) throw new EOFException("Empty file"); dump(out, type, length, ""); } void dump(PrintStream out, int type, long length, String indent) throws Exception { int clss = type >> 6; int nmbr = type & 0x1F; boolean cnst = (type & 0x20) != 0; String tag = "[" + nmbr + "]"; if (clss == 0) tag = TAGS[nmbr]; if (cnst) { System.err.printf("%5d %s %s %s%n", length, indent, CLASSES[clss], tag); while (length > 1) { long atStart = getPosition(); int t2 = read(); long l2 = readLength(); dump(out, t2, l2, indent + " "); length -= getPosition() - atStart; } } else { assert length < Integer.MAX_VALUE; assert length >= 0; byte[] data = new byte[(int) length]; readFully(data); String summary; switch (nmbr) { case BOOLEAN : assert length == 1; summary = data[0] != 0 ? "true" : "false"; break; case INTEGER : long n = toLong(data); summary = n + ""; break; case UTF8_STRING : case IA5STRING : case VISIBLE_STRING : case UNIVERSAL_STRING : case PRINTABLE_STRING : case UTCTIME : summary = new String(data, UTF_8); break; case OBJECT_IDENTIFIER : summary = readOID(data); break; case GENERALIZED_TIME : case GRAPHIC_STRING : case GENERAL_STRING : case CHARACTER_STRING : case REAL : case EOC : case BIT_STRING : case OCTET_STRING : case NULL : case OBJECT_DESCRIPTOR : case EXTERNAL : case ENUMERATED : case EMBEDDED_PDV : case RELATIVE_OID : case NUMERIC_STRING : case T61_STRING : case VIDEOTEX_STRING : case BMP_STRING : default : StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10 && i < data.length; i++) { sb.append(Integer.toHexString(data[i])); } if (data.length > 10) { sb.append("..."); } summary = sb.toString(); break; } out.printf("%5d %s %s %s %s\n", length, indent, CLASSES[clss], tag, summary); } } long toLong(byte[] data) { if (data[0] < 0) { for (int i = 0; i < data.length; i++) data[i] = (byte) (0xFF ^ data[i]); return -(toLong(data) + 1); } long n = 0; for (int i = 0; i < data.length; i++) { n = n * 256 + data[i]; } return n; } /** * 8.1.3.3 For the definite form, the length octets shall consist of one or * more octets, and shall represent the number of octets in the contents * octets using either the short form (see 8.1.3.4) or the long form (see * 8.1.3.5) as a sender's option. NOTE – The short form can only be used if * the number of octets in the contents octets is less than or equal to 127. * 8.1.3.4 In the short form, the length octets shall consist of a single * octet in which bit 8 is zero and bits 7 to 1 encode the number of octets * in the contents octets (which may be zero), as an unsigned binary integer * with bit 7 as the most significant bit. EXAMPLE L = 38 can be encoded as * 001001102 8.1.3.5 In the long form, the length octets shall consist of an * initial octet and one or more subsequent octets. The initial octet shall * be encoded as follows: a) bit 8 shall be one; b) bits 7 to 1 shall encode * the number of subsequent octets in the length octets, as an unsigned * binary integer with bit 7 as the most significant bit; c) the value * 111111112 shall not be used. ISO/IEC 8825-1:2003 (E) NOTE 1 – This * restriction is introduced for possible future extension. Bits 8 to 1 of * the first subsequent octet, followed by bits 8 to 1 of the second * subsequent octet, followed in turn by bits 8 to 1 of each further octet * up to and including the last subsequent octet, shall be the encoding of * an unsigned binary integer equal to the number of octets in the contents * octets, with bit 8 of the first subsequent octet as the most significant * bit. EXAMPLE L = 201 can be encoded as: 100000012 110010012 NOTE 2 – In * the long form, it is a sender's option whether to use more length octets * than the minimum necessary. 8.1.3.6 For the indefinite form, the length * octets indicate that the contents octets are terminated by * end-of-contents octets (see 8.1.5), and shall consist of a single octet. * 8.1.3.6.1 The single octet shall have bit 8 set to one, and bits 7 to 1 * set to zero. 8.1.3.6.2 If this form of length is used, then * end-of-contents octets (see 8.1.5) shall be present in the encoding * following the contents octets. 8.1.4 Contents octets The contents octets * shall consist of zero, one or more octets, and shall encode the data * value as specified in subsequent clauses. NOTE – The contents octets * depend on the type of the data value; subsequent clauses follow the same * sequence as the definition of types in ASN.1. 8.1.5 End-of-contents * octets The end-of-contents octets shall be present if the length is * encoded as specified in 8.1.3.6, otherwise they shall not be present. The * end-of-contents octets shall consist of two zero octets. NOTE – The * end-of-contents octets can be considered as the encoding of a value whose * tag is universal class, whose form is primitive, whose number of the tag * is zero, and whose contents are absent, thus: End-of-contents Length * Contents 0016 0016 Absent */ private long readLength() throws IOException { long n = readByte(); if (n > 0) { // short form return n; } // long form int count = (int) (n & 0x7F); if (count == 0) { // indefinite form return 0; } n = 0; while (count-- > 0) { n = n * 256 + read(); } return n; } private int readByte() throws IOException { position++; return xin.readByte(); } private void readFully(byte[] data) throws IOException { position += data.length; xin.readFully(data); } private long getPosition() { return position; } private int read() throws IOException { position++; return xin.read(); } String readOID(byte[] data) { StringBuilder sb = new StringBuilder(); sb.append((0xFF & data[0]) / 40); sb.append("."); sb.append((0xFF & data[0]) % 40); int i = 0; while (++i < data.length) { int n = 0; while (data[i] < 0) { n = n * 128 + (0x7F & data[i]); i++; } n = n * 128 + data[i]; sb.append("."); sb.append(n); } return sb.toString(); } int getPayloadLength(PDU pdu) throws Exception { switch (pdu.getTag() & 0x1F) { case EOC : return 1; case BOOLEAN : return 1; case INTEGER : return size(pdu.getInt()); case UTF8_STRING : String s = pdu.getString(); byte[] encoded = s.getBytes(UTF_8); return encoded.length; case IA5STRING : case VISIBLE_STRING : case UNIVERSAL_STRING : case PRINTABLE_STRING : case GENERALIZED_TIME : case GRAPHIC_STRING : case GENERAL_STRING : case CHARACTER_STRING : case UTCTIME : case NUMERIC_STRING : { String str = pdu.getString(); encoded = str.getBytes("ASCII"); return encoded.length; } case OBJECT_IDENTIFIER : case REAL : case BIT_STRING : return pdu.getBytes().length; case OCTET_STRING : case NULL : case OBJECT_DESCRIPTOR : case EXTERNAL : case ENUMERATED : case EMBEDDED_PDV : case RELATIVE_OID : case T61_STRING : case VIDEOTEX_STRING : case BMP_STRING : return pdu.getBytes().length; default : throw new IllegalArgumentException("Invalid type: " + pdu); } } int size(long value) { if (value < 128) return 1; if (value <= 0xFF) return 2; if (value <= 0xFFFF) return 3; if (value <= 0xFFFFFF) return 4; if (value <= 0xFFFFFFFF) return 5; if (value <= 0xFFFFFFFFFFL) return 6; if (value <= 0xFFFFFFFFFFFFL) return 7; if (value <= 0xFFFFFFFFFFFFFFL) return 8; if (value <= 0xFFFFFFFFFFFFFFFFL) return 9; throw new IllegalArgumentException("length too long"); } public void write(OutputStream out, PDU pdu) throws Exception { byte id = 0; switch (pdu.getClss()) { case UNIVERSAL : id |= 0; break; case APPLICATION : id |= 0x40; break; case CONTEXT : id |= 0x80; break; case PRIVATE : id |= 0xC0; break; } if (pdu.isConstructed()) id |= 0x20; int tag = pdu.getTag(); if (tag >= 0 && tag < 31) { id |= tag; } else { throw new UnsupportedOperationException("Cant do tags > 30"); } out.write(id); int length = getPayloadLength(pdu); int size = size(length); if (size == 1) { out.write(length); } else { out.write(size); while (--size >= 0) { byte data = (byte) ((length >> (size * 8)) & 0xFF); out.write(data); } } writePayload(out, pdu); } void writePayload(OutputStream out, PDU pdu) throws Exception { switch (pdu.getTag()) { case EOC : out.write(0); break; case BOOLEAN : if (pdu.getBoolean()) out.write(-1); else out.write(0); break; case ENUMERATED : case INTEGER : { long value = pdu.getInt(); int size = size(value); for (int i = size; i >= 0; i--) { byte b = (byte) ((value >> (i * 8)) & 0xFF); out.write(b); } } case BIT_STRING : { byte bytes[] = pdu.getBytes(); int unused = bytes[0]; assert unused <= 7; int[] mask = { 0xFF, 0x7F, 0x3F, 0x1F, 0xF, 0x7, 0x3, 0x1 }; bytes[bytes.length - 1] &= (byte) mask[unused]; out.write(bytes); break; } case RELATIVE_OID : case OBJECT_IDENTIFIER : { int[] oid = pdu.getOID(); assert oid.length > 2; assert oid[0] < 4; assert oid[1] < 40; byte top = (byte) (oid[0] * 40 + oid[1]); out.write(top); for (int i = 2; i < oid.length; i++) { putOid(out, oid[i]); } break; } case OCTET_STRING : { byte bytes[] = pdu.getBytes(); out.write(bytes); break; } case NULL : break; case BMP_STRING : case GRAPHIC_STRING : case VISIBLE_STRING : case GENERAL_STRING : case UNIVERSAL_STRING : case CHARACTER_STRING : case NUMERIC_STRING : case PRINTABLE_STRING : case VIDEOTEX_STRING : case T61_STRING : case REAL : case EMBEDDED_PDV : case EXTERNAL : throw new UnsupportedEncodingException("dont know real, embedded PDV or external"); case UTF8_STRING : { String s = pdu.getString(); byte[] data = s.getBytes(UTF_8); out.write(data); break; } case OBJECT_DESCRIPTOR : case IA5STRING : String s = pdu.getString(); byte[] data = s.getBytes("ASCII"); out.write(data); break; case SEQUENCE : case SET : { PDU pdus[] = pdu.getChildren(); for (PDU p : pdus) { write(out, p); } } case UTCTIME : case GENERALIZED_TIME : Date date = pdu.getDate(); synchronized (df) { String ss = df.format(date); byte d[] = ss.getBytes("ASCII"); out.write(d); } break; } } private void putOid(OutputStream out, int i) throws IOException { if (i > 127) { putOid(out, i >> 7); out.write(0x80 + (i & 0x7F)); } else out.write(i & 0x7F); } }