package model.asn1; import annotations.Assoc; import model.asn1.exceptions.ParseException; import model.asn1.parsing.BytesReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 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. @Assoc(partOf = true) 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. @Assoc(lowerBond = 0, partOf = true) 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 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 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; } }