From d342a45d98c4795b3a3fe1aaef5236ad4a782b55 Mon Sep 17 00:00:00 2001 From: Yuuta Liang Date: Thu, 12 Oct 2023 12:10:33 +0800 Subject: Implement data structures from X.680, X.501, X.509, and PKCS#10, with X.690 encoding / decoding support The implementation took four days, and it is still a little bit rough. Updated version should arrive soon. Signed-off-by: Yuuta Liang --- src/main/model/asn1/Tag.java | 109 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/main/model/asn1/Tag.java (limited to 'src/main/model/asn1/Tag.java') diff --git a/src/main/model/asn1/Tag.java b/src/main/model/asn1/Tag.java new file mode 100644 index 0000000..15c144f --- /dev/null +++ b/src/main/model/asn1/Tag.java @@ -0,0 +1,109 @@ +package model.asn1; + +import model.asn1.exceptions.ParseException; +import model.asn1.parsing.BytesReader; + +/** + * Represents the metadata (tag) of an ASN.1 type. + */ +public class Tag implements Encodable { + private final TagClass cls; + private final boolean constructive; + private final int number; + + /** + * EFFECTS: Construct the ASN.1 tag with the given class, constructive / primitive, and number. + * REQUIRES: number > 0 (X.680$8.2) and number <= 31 (no high tag number support currently); + * the constructive or primitive value must follow the X.680 spec, and it must be primitive if it + * can be both constructive or primitive (according to X.690 DER). + */ + public Tag(TagClass cls, boolean constructive, int number) { + this.cls = cls; + this.constructive = constructive; + this.number = number; + } + + /** + * EFFECTS: Initialize the tag by parsing class / constructive / number from the encoded DER bytes. + * {@link ParseException} is thrown if the input is invalid: + * - The encoded array must have at least one byte. + * - The tag number is zero if the class is UNIVERSAL. + * REQUIRES: The highest two bits must contain the class, and then the constructive bit, and finally the low 5 bits + * must contain the tag number <= 31. + * MODIFIES: encoded (one byte read) + */ + public Tag(BytesReader encoded) throws ParseException { + final Byte val = encoded.require(1, true)[0]; + // Take the highest two bits + // -64 = 2's complement of 0b11000000 + final int highestTwo = val & -64; + if (highestTwo == TagClass.UNIVERSAL.getVal()) { + this.cls = TagClass.UNIVERSAL; + } else if (highestTwo == TagClass.APPLICATION.getVal()) { + this.cls = TagClass.APPLICATION; + } else if (highestTwo == TagClass.PRIVATE.getVal()) { + this.cls = TagClass.PRIVATE; + } else { + this.cls = TagClass.CONTEXT_SPECIFIC; + } + + // Detect the constructive bit by only keeping the corresponding bit and shifting it to the lowestbit. + this.constructive = (val & 0x20) >> 5 == 1; + + // Parse the number by clearing the high 3 bits. + this.number = val & 0x1F; + + if (this.cls == TagClass.UNIVERSAL && this.number == 0) { + throw new ParseException(String.format("The tag number must not be zero for UNIVERSAL tags" + + "(byte 0x%02X @ %d)", val, encoded.getIndex())); + } + } + + /** + * EFFECTS: Encode that tag as DER bytes, as follows: + * HI 7 6 | 5 | 4 3 2 1 0 LO + * Class | C/P | Tag Number + * Notes, In the domain of this application (PKI), a single byte is always returned + * (as nothing requires high tag number). However, the return type is held as byte[] + * to 1) compliant with the spec, 2) reserve for future scalability. + */ + @Override + public Byte[] encodeDER() { + // Fill the low 5bits with tag number + byte value = (byte) number; + // Fill the bit in-between with constructive bit + if (constructive) { + value |= 0x20; // 0b00100000: Enable the 5th bit + } else { + value &= 0xDF; // 0b11011111: Disable the 5th bit + } + // Fill the high two bits with tag class + value |= cls.getVal(); + return new Byte[] { value }; + } + + /** + * EFFECTS: Throw {@link ParseException} if this tag is not exactly the given tag. Useful for parsing. + */ + public void enforce(Tag tag) throws ParseException { + if (this.number != tag.number + || this.constructive != tag.constructive + || this.cls != tag.cls) { + throw new ParseException(String.format("Illegal tag: Expected %s[%d] but got %s[%d].", + tag.cls, tag.number, + cls, number)); + } + } + + public TagClass getCls() { + return cls; + } + + public boolean isConstructive() { + return constructive; + } + + public int getNumber() { + return number; + } +} -- cgit v1.2.3