aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/asn1/ASN1Length.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/model/asn1/ASN1Length.java')
-rw-r--r--src/main/model/asn1/ASN1Length.java101
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;
+ }
+}