aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/asn1/ASN1Object.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/model/asn1/ASN1Object.java')
-rw-r--r--src/main/model/asn1/ASN1Object.java201
1 files changed, 201 insertions, 0 deletions
diff --git a/src/main/model/asn1/ASN1Object.java b/src/main/model/asn1/ASN1Object.java
new file mode 100644
index 0000000..1af26ce
--- /dev/null
+++ b/src/main/model/asn1/ASN1Object.java
@@ -0,0 +1,201 @@
+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) {
+ throw new ParseException("Parent tag must be CONTEXT_SPECIFIC, but found "
+ + parentTag.getCls() + "[" + parentTag.getNumber() + "].");
+ }
+ if (!parentTag.isConstructive()) {
+ throw new ParseException("Parent tag must be 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(encodeValueDER()));
+
+ 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;
+ }
+}