aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/asn1/Tag.java
diff options
context:
space:
mode:
authorYuuta Liang <yuutaw@students.cs.ubc.ca>2023-10-12 12:10:33 +0800
committerYuuta Liang <yuutaw@students.cs.ubc.ca>2023-10-12 12:10:33 +0800
commitd342a45d98c4795b3a3fe1aaef5236ad4a782b55 (patch)
treef4ebc0ad962b138d9371413fcc71c97a559df506 /src/main/model/asn1/Tag.java
parente60c9c76243cfe0a408af98dc60bedb973e815db (diff)
downloadjca-d342a45d98c4795b3a3fe1aaef5236ad4a782b55.tar
jca-d342a45d98c4795b3a3fe1aaef5236ad4a782b55.tar.gz
jca-d342a45d98c4795b3a3fe1aaef5236ad4a782b55.tar.bz2
jca-d342a45d98c4795b3a3fe1aaef5236ad4a782b55.zip
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 <yuutaw@students.cs.ubc.ca>
Diffstat (limited to 'src/main/model/asn1/Tag.java')
-rw-r--r--src/main/model/asn1/Tag.java109
1 files changed, 109 insertions, 0 deletions
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;
+ }
+}