diff options
Diffstat (limited to 'src/main/model/asn1/ASN1Length.java')
-rw-r--r-- | src/main/model/asn1/ASN1Length.java | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/src/main/model/asn1/ASN1Length.java b/src/main/model/asn1/ASN1Length.java new file mode 100644 index 0000000..e85689c --- /dev/null +++ b/src/main/model/asn1/ASN1Length.java @@ -0,0 +1,101 @@ +package model.asn1; + +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; +import ui.Utils; + +import java.util.Arrays; + +/** + * 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; + } +} |