aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/asn1/ASN1Length.java
blob: e85689c4c5758d5cf88ba25be3bb144e2f7a73b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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;
    }
}