aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/asn1/ASN1Object.java
blob: 9b4a98cde677645908e6d52b84b163a49b1a3fde (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package model.asn1;

import model.asn1.exceptions.ParseException;
import model.asn1.parsing.BytesReader;
import ui.Utils;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Represents an encode-able ASN.1 object. It can be a SEQUENCE, an INTEGER, an OID, or any other ASN.1 type.
 * It has a parent tag and length (if explicitly encoded), a tag and length, and a value.
 * Child classes have specific parsed values available.
 */
public class ASN1Object implements Encodable {
    /**
     * The X.680 UNIVERSAL tag assignment for SEQUENCE and SEQUENCE OF. Because there is no such Java
     * model class that represents an abstract ASN1Sequence, implementations may just use this constant
     * as their default tag.
     */
    public static final Tag TAG_SEQUENCE = new Tag(TagClass.UNIVERSAL, true, 0x10);

    /**
     * The X.680 UNIVERSAL tag assignment for SET and SET OF. Because there is no such Java
     * model class that represents an abstract ASN1Set, implementations may just use this constant
     * as their default tag.
     */
    public static final Tag TAG_SET = new Tag(TagClass.UNIVERSAL, true, 0x11);

    // The ASN.1 type tag.
    private final Tag tag;

    // The value length for implementation parsing purposes (only available if the object is parsed)
    private final int length;

    // The parsed raw value (only available if the object is parsed)
    private final Byte[] value;

    // The parent ASN.1 type tag, if required for EXPLICIT tagging with a CONTEXT SPECIFIC tag number.
    private final Tag parentTag;

    /**
     * EFFECTS: Initiate the object with the given tag and an optional context-specific tag number for explicit
     * encoding. It will always have 0 length and null value.
     * By default, the tag should be the corresponding default tag specified in the constants
     * of the corresponding types. However, applications may use context-specific
     * or private tags for corresponding fields, either implicitly encoded or explicitly encoded.
     * REQUIRES: Three cases:
     *          1. No context-specific tag: parentTag must be null.
     *          2. Implicit encoding: parentTag must be null, and the tag must be CONTEXT_SPECIFIC.
     *          3. Explicit encoding: parentTag must be constructive and CONTEXT_SPECIFIC.
     */
    public ASN1Object(Tag tag, Tag parentTag) {
        this.tag = tag;
        this.parentTag = parentTag;
        this.length = 0;
        this.value = null;
    }

    /**
     * EFFECTS: Init the object (tag, parentTag, length) with the specified input DER bytes. It will have length
     * and value (length = 0 if no value in DER, but never null). It will fill the value and length but will not mark
     * the value as read (only the tag will be marked). Subtypes are responsible for deserializing the values. This
     * method is not appropriate for parsing an unknown input (use subtypes instead) since values will be left unread.
     *   Throws {@link ParseException} if input is invalid:
     *     The input data must have a valid
     *      parentTag (optional) - parentLength (optional) - tag - length - value (optional).
     *     The value must match the corresponding type (e.g., an INTEGER value cannot go to an OctetString type).
     *     The value must be supported by the corresponding type (e.g., a Printable must only contain valid chars).
     *     If parentTag presents, its class must be CONTEXT_SPECIFIC, and it must be constructive.
     *     If parentLength presents, it must not be 0.
     * MODIFIES: this, encoded (bytes are read)
     * REQUIRES: If hasParentTag is true, parentTag and parentLength must present. Otherwise, they must be null. Assumes
     * that the length won't be lower than actual. Assumes parentLength = length(tag + length + value).
     */
    public ASN1Object(BytesReader encoded, boolean hasParentTag) throws ParseException {
        if (hasParentTag) {
            this.parentTag = new Tag(encoded);
            if (parentTag.getCls() != TagClass.CONTEXT_SPECIFIC || !parentTag.isConstructive()) {
                throw new ParseException("Parent tag must be CONTEXT_SPECIFIC and constructive.");
            }
            int parentLen = new ASN1Length(encoded).getLength();
            // Validate length
            encoded.validateSize(parentLen);
            if (Integer.compareUnsigned(parentLen, 2) < 0) {
                throw new ParseException("Parent tag with incorrect length.");
            }
        } else {
            parentTag = null;
        }
        // len = the length of value; i = the length of tag - length
        this.tag = new Tag(encoded);
        int len = new ASN1Length(encoded).getLength();
        this.length = len;
        if (len == 0) {
            this.value = new Byte[0];
        } else {
            this.value = encoded.require(len, false);
        }
    }

    /**
     * EFFECTS: Automatically detect the UNIVERSAL type and parse into the corresponding ASN1Object type, or ASN1Object
     * if unrecognized or application-defined (SEQUENCE or SET). It will always mark anything to be read, including
     * unrecognized type values. This method is appropriate to decode an unknown input stream into known or unknown
     * types. All values will be read.
     *   Throws {@link ParseException} if the input is invalid.
     * MODIFIES: encoded
     */
    public static ASN1Object parse(BytesReader encoded, boolean hasParentTag) throws ParseException {
        final Tag t = encoded.getTag(hasParentTag);
        switch (t.getNumber()) {
            case 0x1: return new Bool(encoded, hasParentTag);
            case 0x2: return new Int(encoded, hasParentTag);
            case 0x3: return new BitString(encoded, hasParentTag);
            case 0x4: return new OctetString(encoded, hasParentTag);
            case 0x5: return new Null(encoded, hasParentTag);
            case 0x6: return new ObjectIdentifier(encoded, hasParentTag);
            case 0xC: return new UTF8String(encoded, hasParentTag);
            case 0x13: return new PrintableString(encoded, hasParentTag);
            case 0x16: return new IA5String(encoded, hasParentTag);
            case 0x17: return new UtcTime(encoded, hasParentTag);
            case 0x18: return new GeneralizedTime(encoded, hasParentTag);
            default: {
                ASN1Object object = new ASN1Object(encoded, hasParentTag);
                // Mark as read unconditionally because there aren't any type handlers that read them.
                encoded.require(object.length, true);
                return object;
            }
        }
    }

    /**
     * EFFECTS: Encode the object to DER bytes in the tag-length-value format, as specified in DER specs.
     * The encoding will result in:
     * (Parent Tag)(Tag)(Length)(Value)
     * Parent Tag - Only exists if the field has a context-specific parent tag number and use explicit tagging. In this
     *              case, the parent tag is the tag supplied in the constructor. If the field uses implicit tag
     *              encoding or does not have a context-specific tag number, this field does not exist. This field,
     *              as specified in the REQUIRES clause in the constructor, is always constructive.
     * Parent Length - The length of the following (tag, length, and value). A detailed length description, see follows.
     * Tag        - The tag value.
     * Length     - The length of the value, in number of bytes. If the length is <= 127, it will contain only a single
     *              byte of length value, with the highest bit cleared. If the length is > 127, the first length byte
     *              will have its highest bit set, with the remaining bits representing how many bytes are needed to
     *              store the length integer. Followed are the integer, in multiple bytes, representing the length. The
     *              multibyte integer are encoded in big-endian.
     * Value      - The value, with a total length (in bytes) corresponding to the Length field.
     * REQUIRES: encodeValueDER() != null
     */
    @Override
    public final Byte[] encodeDER() {
        final Byte[] val = encodeValueDER();
        final List<Byte> list = new ArrayList<>(val.length + 3);

        list.addAll(Arrays.asList(tag.encodeDER()));
        list.addAll(Arrays.asList(new ASN1Length(val.length).encodeDER()));
        list.addAll(Arrays.asList(val));

        if (parentTag != null) { // Explicit
            final List<Byte> newList = new ArrayList<>(list.size() + 3);
            newList.addAll(Arrays.asList(parentTag.encodeDER()));
            newList.addAll(Arrays.asList(new ASN1Length(list.size()).encodeDER()));
            newList.addAll(list);
            return newList.toArray(new Byte[0]);
        } else {
            return list.toArray(new Byte[0]);
        }
    }

    /**
     * EFFECTS: Encode the value of that object to DER bytes. The length of the returned value
     * is <= (255,255,255,...) (127 in total).
     */
    public Byte[] encodeValueDER() {
        return value;
    }

    public Tag getTag() {
        return tag;
    }

    public Tag getParentTag() {
        return parentTag;
    }

    /**
     * EFFECTS: Get the unsigned int of value length. Only available if the data is parsed.
     */
    public int getLength() {
        return length;
    }
}