package model.asn1; import model.asn1.exceptions.ParseException; import model.asn1.parsing.BytesReader; import ui.Utils; /** * Represents the Length part in DER encoding. It appears after Tag and before Value. It represents the length of the * encoded Value in bytes. * For encoding, if the length is <= 127, it is encoded as a single byte, with the highest bit cleared. If it is > 127, * the initial byte will have its highest bit set, with the remaining 7 bits representing how many of bytes in advance * are needed to represent the multibyte length value. Then, following are the multibyte length value, encoded in a * big-endian unsigned integer. */ public class ASN1Length implements Encodable { /** * The length. It is represented in Java signed 32bit integer, but it should be unsigned. * Operations should use Integer#unsigned* methods. */ private final int length; /** * EFFECTS: Initialize the object with the given length. * REQUIRES: length >= 0 */ public ASN1Length(int length) { this.length = length; } /** * EFFECTS: Parse the length from the given DER input. * Throws {@link ParseException} if the input is invalid: * - Indefinite length * - Not enough bytes * - Initial byte 0b11111111 (See X.690$8.1.3.5) * - Value too long (compliant to RFC but unsupported by this program): multibyte and # of bytes > 3 * MODIFIES: reader (bytes are read, at least one byte, at most 5 bytes) */ public ASN1Length(BytesReader reader) throws ParseException { final Byte first = reader.require(1, true)[0]; if (first < 0) { // Multibyte // 0b11111111 if (first == -1) { throw new ParseException("The initial byte must not be 0xFF"); } // Clear the sign bit and get the remaining. int count = first & 127; if (count == 0) { throw new ParseException("Indefinite length is forbidden by DER"); } final Byte[] values = reader.require(count, true); // Pad one byte to the top - so it is always unsigned. Byte[] b1 = new Byte[values.length + 1]; System.arraycopy(values, 0, b1, 1, values.length); b1[0] = 0x0; this.length = Utils.bytesToInt(b1); } else { this.length = first; } } /** * EFFECTS: Compute the length to add in the Tag - Length - Value format. For a detailed description on length, see * class specification. */ @Override public Byte[] encodeDER() { // Determine the length of the length. // If the length is <= 127 bytes, use a single byte. // If the length is > 127 bytes, set the highest bit as 1, and the // rest of the bits specify how many more bytes the length is, followed // by a sequence of bytes of the length. // Setting the length 80 (0b10000000) means indefinite length, which is forbidden // by DER. // DER prefers the shortest form. if (length <= 127) { // Possible in a single byte. return new Byte[]{(byte) length}; } else { // Big-endian encoding of the length. DER uses big-endian. final Byte[] lengthBytes = Utils.valToByte(length); final Byte[] result = new Byte[lengthBytes.length + 1]; // Turn-on the highest bit. result[0] = (byte) (lengthBytes.length | -128); // Append the length. System.arraycopy(lengthBytes, 0, result, 1, lengthBytes.length); return result; } } /** * EFFECT: Returns the unsigned integer length. */ public int getLength() { return length; } }