aboutsummaryrefslogtreecommitdiff
path: root/src/test/model/asn1
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/model/asn1')
-rw-r--r--src/test/model/asn1/ASN1LengthTest.java94
-rw-r--r--src/test/model/asn1/ASN1ObjectTest.java177
-rw-r--r--src/test/model/asn1/BitStringTest.java75
-rw-r--r--src/test/model/asn1/BoolTest.java45
-rw-r--r--src/test/model/asn1/GeneralizedTimeTest.java119
-rw-r--r--src/test/model/asn1/IA5StringTest.java79
-rw-r--r--src/test/model/asn1/IntTest.java96
-rw-r--r--src/test/model/asn1/NullTest.java38
-rw-r--r--src/test/model/asn1/ObjectIdentifierTest.java89
-rw-r--r--src/test/model/asn1/OctetStringTest.java44
-rw-r--r--src/test/model/asn1/PrintableStringTest.java71
-rw-r--r--src/test/model/asn1/TagClassTest.java15
-rw-r--r--src/test/model/asn1/TagTest.java107
-rw-r--r--src/test/model/asn1/UTF8StringTest.java64
-rw-r--r--src/test/model/asn1/UtcTimeTest.java121
-rw-r--r--src/test/model/asn1/parsing/BytesReaderTest.java95
16 files changed, 1329 insertions, 0 deletions
diff --git a/src/test/model/asn1/ASN1LengthTest.java b/src/test/model/asn1/ASN1LengthTest.java
new file mode 100644
index 0000000..44aed8e
--- /dev/null
+++ b/src/test/model/asn1/ASN1LengthTest.java
@@ -0,0 +1,94 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.asn1.parsing.BytesReaderTest;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ASN1LengthTest {
+ @Test
+ void testConstructor() {
+ assertEquals(0, new ASN1Length(0).getLength());
+ assertEquals(1, new ASN1Length(1).getLength());
+ }
+
+ @Test
+ void testParse() throws Exception { // TODO: Exception
+ BytesReader reader = new BytesReader(new Byte[]{ 0x0 });
+ assertEquals(0, new ASN1Length(reader).getLength());
+ assertEquals(1, reader.getIndex());
+
+ reader = new BytesReader(new Byte[]{ 0x20 });
+ assertEquals(0x20, new ASN1Length(reader).getLength());
+ assertEquals(1, reader.getIndex());
+
+ reader = new BytesReader(new Byte[]{ -127, 0x30 });
+ assertEquals(0x30, new ASN1Length(reader).getLength());
+ assertEquals(2, reader.getIndex());
+
+ reader = new BytesReader(new Byte[]{ -126, -22, 0x60 });
+ assertEquals(60000, new ASN1Length(reader).getLength());
+ assertEquals(3, reader.getIndex());
+
+ reader = new BytesReader(new Byte[]{ -127, 127 });
+ assertEquals(127, new ASN1Length(reader).getLength());
+ assertEquals(2, reader.getIndex());
+
+ reader = new BytesReader(new Byte[]{ -124, 1, 1, 1, 1 });
+ assertEquals(16843009, new ASN1Length(reader).getLength());
+ assertEquals(5, reader.getIndex());
+
+ reader = new BytesReader(new Byte[]{ -127, -97 });
+ assertEquals(159, new ASN1Length(reader).getLength());
+ assertEquals(2, reader.getIndex());
+ }
+
+ @Test
+ void testParseFail() throws ParseException {
+ // First byte 0b10000000
+ assertThrows(ParseException.class, () ->
+ new ASN1Length(new BytesReader(new Byte[]{ -128 }))
+ );
+
+ // First byte 0b11111111
+ assertThrows(ParseException.class, () ->
+ new ASN1Length(new BytesReader(new Byte[]{ -1 }))
+ );
+
+ // Multibyte, requested 2 bytes but have none.
+ assertThrows(ParseException.class, () ->
+ new ASN1Length(new BytesReader(new Byte[]{ -126 }))
+ );
+
+ // Multibyte, requested 2 bytes but have one.
+ assertThrows(ParseException.class, () ->
+ new ASN1Length(new BytesReader(new Byte[]{ -126, 0x1 }))
+ );
+
+ // But this one should work (0b01111111)
+ new ASN1Length(new BytesReader(new Byte[]{ -127, 127 }));
+
+ // Multibyte, too long.
+ assertThrows(ParseException.class, () ->
+ new ASN1Length(new BytesReader(new Byte[]{ -124, -1, -1, -1, -1 }))
+ );
+ // But this one should work, except for it is too large
+ new ASN1Length(new BytesReader(new Byte[]{ -125, -1, -1, -1 }));
+ }
+
+ @Test
+ void testEncode() {
+ // Short form
+ assertArrayEquals(new Byte[]{ 0x0 }, new ASN1Length(0).encodeDER());
+ assertArrayEquals(new Byte[]{ 0x1 }, new ASN1Length(1).encodeDER());
+ assertArrayEquals(new Byte[]{ 127 }, new ASN1Length(127).encodeDER());
+
+ // Long form
+ // 0b10000001, 0b10000000
+ assertArrayEquals(new Byte[]{ -127, -128 }, new ASN1Length(128).encodeDER());
+ // 0b10000010, 0b11111111, 0b11111111
+ assertArrayEquals(new Byte[]{ -126, -1, -1 }, new ASN1Length(65535).encodeDER());
+ }
+}
diff --git a/src/test/model/asn1/ASN1ObjectTest.java b/src/test/model/asn1/ASN1ObjectTest.java
new file mode 100644
index 0000000..ea765e6
--- /dev/null
+++ b/src/test/model/asn1/ASN1ObjectTest.java
@@ -0,0 +1,177 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ASN1ObjectTest {
+ @Test
+ void testParseType() throws ParseException {
+ assertEquals(Bool.class,
+ ASN1Object.parse(new BytesReader(new Bool(Bool.TAG, null, true).encodeDER()),
+ false).getClass());
+ assertEquals(Int.class,
+ ASN1Object.parse(new BytesReader(new Int(Int.TAG, null, 1).encodeDER()),
+ false).getClass());
+ assertEquals(BitString.class,
+ ASN1Object.parse(new BytesReader(new BitString(BitString.TAG,
+ null, 0, new Byte[]{ 1 }).encodeDER()),
+ false).getClass());
+ assertEquals(OctetString.class,
+ ASN1Object.parse(new BytesReader(new OctetString(OctetString.TAG, null, new Byte[]{ 1 }).encodeDER()),
+ false).getClass());
+ assertEquals(Null.class,
+ ASN1Object.parse(new BytesReader(new Null(Null.TAG, null).encodeDER()),
+ false).getClass());
+ assertEquals(ObjectIdentifier.class,
+ ASN1Object.parse(new BytesReader(new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ new Integer[]{ 1, 2, 3 }).encodeDER()),
+ false).getClass());
+ assertEquals(UTF8String.class,
+ ASN1Object.parse(new BytesReader(new UTF8String(UTF8String.TAG, null,
+ "qwq").encodeDER()),
+ false).getClass());
+ assertEquals(PrintableString.class,
+ ASN1Object.parse(new BytesReader(new PrintableString(PrintableString.TAG, null,
+ "qwq").encodeDER()),
+ false).getClass());
+ assertEquals(IA5String.class,
+ ASN1Object.parse(new BytesReader(new IA5String(IA5String.TAG, null,
+ "qwq").encodeDER()),
+ false).getClass());
+ assertEquals(UtcTime.class,
+ ASN1Object.parse(new BytesReader(new UtcTime(UtcTime.TAG, null,
+ ZonedDateTime.now(ZoneId.of("UTC"))).encodeDER()),
+ false).getClass());
+ assertEquals(GeneralizedTime.class,
+ ASN1Object.parse(new BytesReader(new GeneralizedTime(GeneralizedTime.TAG, null,
+ ZonedDateTime.now(ZoneId.of("UTC"))).encodeDER()),
+ false).getClass());
+ assertEquals(ASN1Object.class,
+ ASN1Object.parse(new BytesReader(new Byte[]{ 0x30, 1, 0x0 }), false)
+ .getClass());
+ }
+
+ @Test
+ void testConstructor() {
+ assertEquals(0, new ASN1Object(Null.TAG, null).getLength());
+ assertNull(new ASN1Object(Null.TAG, null).encodeValueDER());
+ assertEquals(0x5,
+ new ASN1Object(new Tag(TagClass.UNIVERSAL, false, 0x5),
+ null).getTag().getNumber());
+ assertEquals(0x6,
+ new ASN1Object(new Tag(TagClass.UNIVERSAL, false, 0x5),
+ new Tag(TagClass.UNIVERSAL, false, 0x6)).getParentTag().getNumber());
+ }
+ @Test
+ void testParseSuccess() throws ParseException {
+ // No parent tag
+ assertEquals(0x5,
+ new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x0 }), false)
+ .getTag().getNumber());
+ assertEquals(TagClass.UNIVERSAL,
+ new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x0 }), false)
+ .getTag().getCls());
+ assertFalse(new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x0 }), false)
+ .getTag().isConstructive());
+ assertNull(new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x0 }), false)
+ .getParentTag());
+
+ assertEquals(0, new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x0 }), false)
+ .encodeValueDER().length);
+ assertEquals(0, new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x0 }), false)
+ .getLength());
+
+ // With parent tag
+ // -95 is the 2's complement represent of 0b10100001
+ assertEquals(0x5,
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true)
+ .getTag().getNumber());
+ assertEquals(TagClass.UNIVERSAL,
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true)
+ .getTag().getCls());
+ assertFalse(new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true)
+ .getTag().isConstructive());
+ assertEquals(0x1,
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true)
+ .getParentTag().getNumber());
+ assertEquals(TagClass.CONTEXT_SPECIFIC,
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true)
+ .getParentTag().getCls());
+ assertTrue(new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true)
+ .getParentTag().isConstructive());
+
+ // Test index
+ BytesReader reader = new BytesReader(new Byte[]{ 0xE, 5, 1, 2, 3, 4, 5 });
+ ASN1Object obj = new ASN1Object(reader, false);
+ // Contents should not be read.
+ assertEquals(2, reader.getIndex());
+ // But is copied
+ assertArrayEquals(new Byte[]{ 1, 2, 3, 4, 5 }, obj.encodeValueDER());
+ // If we parse an unknown type
+ reader = new BytesReader(new Byte[]{ 0xE, 5, 1, 2, 3, 4, 5 });
+ obj = ASN1Object.parse(reader, false);
+ // Contents should be read now
+ assertEquals(7, reader.getIndex());
+ }
+
+ @Test
+ void testParseFail() {
+ // Value early EOF
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ 0x5, 0x1 }), false));
+ // Tag early EOF
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 2 }), true));
+ // Length not found
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ 0x5 }), false));
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 2, 0x5 }), true));
+ // Parent tag is not CONTEXT_SPECIFIC
+ // UNIVERSAL
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ 33, 2, 0x5, 0x0 }), true));
+ // APPLICATION
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ 97, 2, 0x5, 0x0 }), true));
+ // PRIVATE
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -31, 2, 0x5, 0x0 }), true));
+ // Parent tag is not constructive
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -127, 2, 0x5, 0x0 }), true));
+ // Parent tag length incorrect
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 0, 0x5, 0x0 }), true));
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 1, 0x5, 0x0 }), true));
+ assertThrows(ParseException.class, () ->
+ new ASN1Object(new BytesReader(new Byte[]{ -95, 3, 0x5, 0x0 }), true));
+ }
+
+ @Test
+ void testEncode() {
+ // No parent tag
+ assertArrayEquals(new Byte[] {
+ 0x5, 0x0
+ }, new Null(Null.TAG, null).encodeDER());
+ // Custom tag
+ assertArrayEquals(new Byte[] {
+ 0x72, 0x0
+ }, new Null(new Tag(TagClass.APPLICATION, true, 0x12), null)
+ .encodeDER());
+ // With parent tag
+ assertArrayEquals(new Byte[] {
+ -95, 2,
+ 0x72, 0x0
+ }, new Null(new Tag(TagClass.APPLICATION, true, 0x12),
+ new Tag(TagClass.CONTEXT_SPECIFIC, true, 0x1))
+ .encodeDER());
+ }
+}
diff --git a/src/test/model/asn1/BitStringTest.java b/src/test/model/asn1/BitStringTest.java
new file mode 100644
index 0000000..c893b36
--- /dev/null
+++ b/src/test/model/asn1/BitStringTest.java
@@ -0,0 +1,75 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BitStringTest {
+ @Test
+ void testConstructor() {
+ assertArrayEquals(new Byte[]{ 0x2, 0x3 },
+ new BitString(BitString.TAG, null, 0, new Byte[]{ 0x2, 0x3 })
+ .getVal());
+ assertEquals(3,
+ new BitString(BitString.TAG, null, 3, new Byte[]{ 0x2, 0x8 })
+ .getUnused());
+ }
+
+ @Test
+ void testConvert() {
+ // 00000010 00001000
+ // 00000000 01000001 = 65
+ assertArrayEquals(new Byte[]{ 65 },
+ new BitString(BitString.TAG, null, 3, new Byte[]{ 0x2, 0x8 })
+ .getConvertedVal());
+
+ assertArrayEquals(new Byte[]{ 0x2, 0x8 },
+ new BitString(BitString.TAG, null, 0, new Byte[]{ 0x2, 0x8 })
+ .getConvertedVal());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertArrayEquals(new Byte[]{ 0x6e, 0x5d, -64 },
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -64 }), false)
+ .getVal());
+ assertEquals(6,
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -64 }), false)
+ .getUnused());
+ assertArrayEquals(new Byte[]{ 0x01, -71, 0x77 },
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -64 }), false)
+ .getConvertedVal());
+ }
+
+ @Test
+ void testParseFail() {
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04 }), false));
+ // 0b11100000
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -32 }), false));
+ // 0b11000001
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -63 }), false));
+ // Unused bits = 8
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x08, 0x6e, 0x5d, -64 }), false));
+ // Unused bits = 6 -> 7
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x07, 0x6e, 0x5d, -64 }), false));
+ // Illegal unused bits: 8 and -1
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x08, 0x6e, 0x5d, -64 }), false));
+ assertThrows(ParseException.class, () ->
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, -1, 0x6e, 0x5d, -64 }), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -64 },
+ new BitString(new BytesReader(new Byte[]{ 0x03, 0x04, 0x06, 0x6e, 0x5d, -64 }), false)
+ .encodeDER());
+ }
+}
diff --git a/src/test/model/asn1/BoolTest.java b/src/test/model/asn1/BoolTest.java
new file mode 100644
index 0000000..fed3152
--- /dev/null
+++ b/src/test/model/asn1/BoolTest.java
@@ -0,0 +1,45 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BoolTest {
+ @Test
+ void testConstructor() {
+ assertTrue(new Bool(Bool.TAG, null, true).getValue());
+ assertFalse(new Bool(Bool.TAG, null, false).getValue());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertFalse(new Bool(new BytesReader(new Byte[]{ 0x1, 1, 0 }), false)
+ .getValue());
+ assertTrue(new Bool(new BytesReader(new Byte[]{ 0x1, 1, -1 }), false)
+ .getValue());
+ }
+
+ @Test
+ void testParseFail() throws ParseException {
+ assertThrows(ParseException.class, () ->
+ new Bool(new BytesReader(new Byte[]{ 0x1, 0 }), false));
+ assertThrows(ParseException.class, () ->
+ new Bool(new BytesReader(new Byte[]{ 0x1, 1 }), false));
+ assertThrows(ParseException.class, () ->
+ new Bool(new BytesReader(new Byte[]{ 0x1, 1, 1 }), false));
+ assertThrows(ParseException.class, () ->
+ new Bool(new BytesReader(new Byte[]{ 0x1, 1, -2 }), false));
+ assertThrows(ParseException.class, () ->
+ new Bool(new BytesReader(new Byte[]{ 0x1, 2, -1, 2 }), false));
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(new Byte[]{ 0 },
+ new Bool(Bool.TAG, null, false).encodeValueDER());
+ assertArrayEquals(new Byte[]{ -1 },
+ new Bool(Bool.TAG, null, true).encodeValueDER());
+ }
+}
diff --git a/src/test/model/asn1/GeneralizedTimeTest.java b/src/test/model/asn1/GeneralizedTimeTest.java
new file mode 100644
index 0000000..4660de7
--- /dev/null
+++ b/src/test/model/asn1/GeneralizedTimeTest.java
@@ -0,0 +1,119 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class GeneralizedTimeTest {
+ @Test
+ void testConstructor() throws ParseException {
+ final ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));
+ assertEquals(now, new GeneralizedTime(GeneralizedTime.TAG, null, now).getTimestamp());
+ final ASN1Time parsed = new GeneralizedTime(new BytesReader(new Byte[] {
+ 0x18, 15,
+ '1', '9', '1', '9', '0', '8', '1', '0', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false);
+ assertEquals("19190810114514Z",
+ parsed.toString());
+ assertEquals(ZonedDateTime.of(1919, 8, 10, 11, 45, 14,
+ 0, ZoneId.of("UTC")),
+ parsed.getTimestamp());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ ASN1Time parsed = new GeneralizedTime(new BytesReader(new Byte[] {
+ 0x18, 15,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false);
+ assertEquals(ZonedDateTime.of(2023, 9, 27, 11, 45, 14,
+ 0, ZoneId.of("UTC")),
+ parsed.getTimestamp());
+
+ // No seconds
+ parsed = new GeneralizedTime(new BytesReader(new Byte[] {
+ 0x18, 13,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', 'Z'
+ }), false);
+ assertEquals(ZonedDateTime.of(2023, 9, 27, 11, 45, 0,
+ 0, ZoneId.of("UTC")),
+ parsed.getTimestamp());
+
+ // Length 0
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 0
+ }), false));
+ // Early EOF
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 15
+ }), false));
+ // No tailing Z
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 14,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4'
+ }), false));
+ // Custom timezone
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 18,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4', 'Z', '-', '0', '8'
+ }), false));
+ // Invalid month / day / hour / minute / second
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 15,
+ '2', '0', '2', '3', '1', '3', '2', '7', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 15,
+ '2', '0', '2', '3', '1', '0', '3', '2', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 15,
+ '2', '0', '2', '3', '1', '0', '3', '0', '2', '5', '4', '5', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 15,
+ '2', '0', '2', '3', '1', '0', '3', '0', '2', '4', '6', '1', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new GeneralizedTime(new BytesReader(new Byte[]{
+ 0x18, 15,
+ '2', '0', '2', '3', '1', '0', '3', '0', '2', '4', '6', '0', '6', '1', 'Z'
+ }), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertEquals("20230927114514Z", new GeneralizedTime(new BytesReader(new Byte[] {
+ -95, 17,
+ 0x18, 15,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4', 'Z'
+ }), true).toString());
+ // No seconds
+ assertEquals("202309271145Z", new GeneralizedTime(new BytesReader(new Byte[] {
+ -95, 15,
+ 0x18, 13,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', 'Z'
+ }), true).toString());
+
+ // To byte array
+ assertArrayEquals(new Byte[] {
+ 0x18, 13,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', 'Z'
+ }, new GeneralizedTime(GeneralizedTime.TAG, null, ZonedDateTime.of(2023, 9,
+ 27, 11, 45, 0, 0, ZoneId.of("UTC")))
+ .encodeDER());
+ }
+}
diff --git a/src/test/model/asn1/IA5StringTest.java b/src/test/model/asn1/IA5StringTest.java
new file mode 100644
index 0000000..dfaa1aa
--- /dev/null
+++ b/src/test/model/asn1/IA5StringTest.java
@@ -0,0 +1,79 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class IA5StringTest {
+ private String stringToTest = "";
+
+ @BeforeEach
+ void setup() {
+ stringToTest = IntStream.range(0, 127)
+ .mapToObj(Character::toString)
+ .collect(Collectors.joining());
+ }
+
+ @Test
+ void testAcceptedString() throws ParseException {
+ assertEquals(stringToTest,
+ new IA5String(IA5String.TAG, null, stringToTest)
+ .getString());
+ }
+
+ @Test
+ void testIllegalStrings() {
+ assertThrows(ParseException.class,
+ () -> new IA5String(IA5String.TAG, null,
+ stringToTest + new String(new byte[]{ -128 }, StandardCharsets.UTF_8)));
+ assertThrows(ParseException.class,
+ () -> new IA5String(IA5String.TAG, null,
+ stringToTest + new String(new byte[]{ -1 }, StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(
+ new Byte[] { 0x00, 0x01, 0x02 },
+ new IA5String(IA5String.TAG, null, new String(new byte[]{ 0, 1, 2}, StandardCharsets.UTF_8))
+ .encodeValueDER());
+ assertArrayEquals(
+ new Byte[] {
+ 0x16, 0x02, // Tag - Length
+ 0x68, 0x69 // Value
+ }, new IA5String(IA5String.TAG, null, "hi").encodeDER());
+ assertArrayEquals(
+ new Byte[] {
+ -85, 0x05, // Parent Tag - Length
+ 0x16, 0x03, // Inner Tag - Length
+ 0x68, 0x69, 0x69 // Value
+ }, new IA5String(IA5String.TAG,
+ new Tag(TagClass.CONTEXT_SPECIFIC, true, 11),
+ "hii").encodeDER());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertEquals("123",
+ new IA5String(new BytesReader(new Byte[]{ 0x16, 3, '1', '2', '3' }), false)
+ .getString());
+ assertEquals("",
+ new IA5String(new BytesReader(new Byte[]{ 0x16, 0 }), false)
+ .getString());
+ }
+
+ @Test
+ void testParseFail() {
+ assertThrows(ParseException.class, () ->
+ new IA5String(new BytesReader(new Byte[]{ 0x16, 3, '1', '2' }), false));
+ assertThrows(ParseException.class, () ->
+ new IA5String(new BytesReader(new Byte[]{ 0x16, 2, '1', -128 }), false));
+ }
+}
diff --git a/src/test/model/asn1/IntTest.java b/src/test/model/asn1/IntTest.java
new file mode 100644
index 0000000..6e92eb0
--- /dev/null
+++ b/src/test/model/asn1/IntTest.java
@@ -0,0 +1,96 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class IntTest {
+ @Test
+ void testConstructor() {
+ assertEquals(0x2, new Int(Int.TAG, null, 255).getTag().getNumber());
+ assertEquals(255, new Int(Int.TAG, null, 255).getLong());
+ }
+
+ @Test
+ void testEncode() {
+ // Single-byte
+ assertArrayEquals(new Byte[] { 0x0 }, new Int(Int.TAG, null, 0).encodeValueDER());
+ assertArrayEquals(new Byte[] { 0x1 }, new Int(Int.TAG, null, 1).encodeValueDER());
+ assertArrayEquals(new Byte[] { -1 }, new Int(Int.TAG, null, 255).encodeValueDER());
+
+ // Multiple bytes
+ assertArrayEquals(new Byte[] { 0x01, 0x00 }, new Int(Int.TAG, null, 256).encodeValueDER());
+ assertArrayEquals(new Byte[] { -1, -1 }, new Int(Int.TAG, null, 65535).encodeValueDER());
+ assertArrayEquals(new Byte[] { 0x01, 0x00, 0x00 }, new Int(Int.TAG, null, 65536).encodeValueDER());
+ assertArrayEquals(new Byte[] { -1, -1, -1 }, new Int(Int.TAG, null, 16777215).encodeValueDER());
+ assertArrayEquals(new Byte[] { 0x01, 0x00, 0x00, 0x00 }, new Int(Int.TAG, null, 16777216).encodeValueDER());
+ assertArrayEquals(new Byte[] { -1, -1, -1, -1 }, new Int(Int.TAG, null, 4294967295L).encodeValueDER());
+ assertArrayEquals(new Byte[] { -1, -1, -1, -1 }, new Int(Int.TAG, null, 4294967295L).encodeValueDER());
+
+ // 2 ^ 63 + 1
+ assertArrayEquals(new Byte[] { -128, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 },
+ new Int(Int.TAG, null, Long.parseUnsignedLong("9223372036854775809")).encodeValueDER());
+
+ // Test no leading zeros
+ // Not 0x00, 0xFF
+ assertArrayEquals(new Byte[] { -1 }, new Int(Int.TAG, null, 255).encodeValueDER());
+
+ // Test no leading ones
+ // Not 0xFF, 0x80
+ assertArrayEquals(new Byte[] { -128 }, new Int(Int.TAG, null, -128).encodeValueDER());
+
+ // Encode DER
+ assertArrayEquals(new Byte[] { 0x02, 2, 0x01, 0x00 }, new Int(Int.TAG, null, 256).encodeDER());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ // Single-byte
+ assertEquals(0, new Int(new BytesReader(new Byte[] { 0x2, 1, 0x0 }), false).getLong());
+ assertEquals(1, new Int(new BytesReader(new Byte[] { 0x2, 1, 0x1 }), false).getLong());
+ assertEquals(-1, new Int(new BytesReader(new Byte[] { 0x2, 1, -1 }), false).getLong());
+
+ // Multiple bytes
+ assertEquals(256,
+ new Int(new BytesReader(new Byte[] { 0x2, 2, 0x01, 0x00 }), false).getLong());
+ assertEquals(-1,
+ new Int(new BytesReader(new Byte[] { 0x2, 2, -1, -1 }), false).getLong());
+ assertEquals(65536,
+ new Int(new BytesReader(new Byte[] { 0x2, 3, 0x01, 0x00, 0x00 }), false).getLong());
+ assertEquals(-1,
+ new Int(new BytesReader(new Byte[] { 0x2, 3, -1, -1, -1 }), false).getLong());
+ assertEquals(16777216,
+ new Int(new BytesReader(new Byte[] { 0x2, 4, 0x01, 0x00, 0x00, 0x00 }), false).getLong());
+ assertEquals(-1,
+ new Int(new BytesReader(new Byte[] { 0x2, 4, -1, -1, -1, -1 }), false).getLong());
+ assertEquals(4294967296L,
+ new Int(new BytesReader(new Byte[] { 0x2, 5, 0x01, 0x00, 0x00, 0x00, 0x00 }),false).getLong());
+
+ // 2 ^ 63 + 1
+ assertEquals(Long.parseUnsignedLong("9223372036854775809"),
+ new Int(new BytesReader(new Byte[] { 0x2, 9, 0x00, -128, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }),
+ false).getValue().longValue());
+
+ // Test no leading zeros
+ // Not 0x00, 0xFF
+ assertEquals(255, new Int(new BytesReader(new Byte[] { 0x02, 0x2, 0x0, -1 }), false).getLong());
+
+ // Test no leading ones
+ // Not 0xFF, 0x80
+ assertArrayEquals(new Byte[] { -128 }, new Int(Int.TAG, null, -128).encodeValueDER());
+ }
+
+ @Test
+ void testParseFail() {
+ // Not enough bytes
+ assertThrows(ParseException.class, () ->
+ new Int(new BytesReader(new Byte[]{ 0x2, 0x7, -1, -1, -1, -1, -1, -1 }),
+ false));
+ // Zero len
+ assertThrows(ParseException.class, () ->
+ new Int(new BytesReader(new Byte[]{ 0x2, 0x0, -1, -1, -1, -1, -1, -1 }),
+ false));
+ }
+}
diff --git a/src/test/model/asn1/NullTest.java b/src/test/model/asn1/NullTest.java
new file mode 100644
index 0000000..4ec2c64
--- /dev/null
+++ b/src/test/model/asn1/NullTest.java
@@ -0,0 +1,38 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class NullTest {
+ @Test
+ void testConstructor() {
+ assertEquals(Null.TAG, new Null(Null.TAG, null).getTag());
+ assertEquals(0x01,
+ new Null(Null.TAG, new Tag(TagClass.CONTEXT_SPECIFIC, true, 0x01)).getParentTag().getNumber());
+ }
+
+ @Test
+ void testEncode() {
+ assertEquals(0, new Null(Null.TAG, null).encodeValueDER().length);
+ assertArrayEquals(new Byte[] {
+ 0x5, 0x0 // Tag - Length
+ }, new Null(Null.TAG, null).encodeDER());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ new Null(new BytesReader(new Byte[]{ 0x5, 0x0 }), false);
+ new Null(new BytesReader(new Byte[]{ -95, 2, 0x5, 0x0 }), true);
+ }
+
+ @Test
+ void testParseFail() {
+ assertThrows(ParseException.class, () ->
+ new Null(new BytesReader(new Byte[]{ 0x5, 0x2 }), false));
+ assertThrows(ParseException.class, () ->
+ new Null(new BytesReader(new Byte[]{ 0x5, 0x2, 1, 1 }), false));
+ }
+}
diff --git a/src/test/model/asn1/ObjectIdentifierTest.java b/src/test/model/asn1/ObjectIdentifierTest.java
new file mode 100644
index 0000000..f6f1049
--- /dev/null
+++ b/src/test/model/asn1/ObjectIdentifierTest.java
@@ -0,0 +1,89 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ObjectIdentifierTest {
+ @Test
+ void testConstructor() {
+ assertArrayEquals(new Integer[]{ 1, 3, 6, 1, 4},
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, new Integer[]{ 1, 3, 6, 1, 4 }).getInts());
+ assertArrayEquals(new Integer[]{ 1, 2, 3, 4, 5},
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, new Integer[]{ 1, 2, 3, 4, 5 }).getInts());
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("1.3.6.1.4",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, new Integer[]{ 1, 3, 6, 1, 4 }).toString());
+ assertEquals("1.2.3.4.5.6",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, new Integer[]{ 1, 2, 3, 4, 5, 6 }).toString());
+ assertEquals("CN",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_CN).toString());
+ assertEquals("SN",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_SN).toString());
+ assertEquals("C",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_C).toString());
+ assertEquals("L",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_L).toString());
+ assertEquals("O",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_O).toString());
+ assertEquals("OU",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_OU).toString());
+ assertEquals("DC",
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_DC).toString());
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(new Byte[]{ 0x55, 0x04, 0x0A },
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_O).encodeValueDER());
+ assertArrayEquals(new Byte[]{ 0x67, -127, 0x0C, 0x01, 0x02, 0x01 },
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ new Integer[]{ 2, 23, 140, 1, 2, 1 }).encodeValueDER());
+ assertArrayEquals(new Byte[]{ 0x2B, 0x06, 0x01, 0x04, 0x01, -126, -33, 0x13, 0x01, 0x01, 0x01 },
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ new Integer[]{ 1, 3, 6, 1, 4, 1, 44947, 1, 1, 1 }).encodeValueDER());
+ assertArrayEquals(new Byte[]{ 0x2A, -122, 0x48, -50, 0x3D, 0x02, 0x01 },
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ new Integer[]{ 1, 2, 840, 10045, 2, 1 }).encodeValueDER());
+ assertArrayEquals(new Byte[]{ 0x2A, -122, 0x48, -122, -9, 0x0D, 0x01, 0x01, 0x0B },
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION).encodeValueDER());
+
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertArrayEquals(ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION,
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 0x9, 0x2A, -122, 0x48, -122, -9, 0x0D,
+ 0x01, 0x01, 0x0B }),false).getInts());
+ assertArrayEquals(new Integer[]{ 1, 2, 840, 10045, 2, 1 },
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 7, 0x2A, -122, 0x48, -50, 0x3D, 0x02, 0x01 }),
+ false).getInts());
+ assertArrayEquals(new Integer[]{ 0, 2, 840, 10045, 2, 1 },
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 7, 2, -122, 0x48, -50, 0x3D, 0x02, 0x01 }),
+ false).getInts());
+ assertArrayEquals(new Integer[]{ 2, 2, 840, 10045, 2, 1 },
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 7, 82, -122, 0x48, -50, 0x3D, 0x02, 0x01 }),
+ false).getInts());
+ assertArrayEquals(new Integer[]{ 2, 42, 840, 10045, 2, 1 },
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 7, 122, -122, 0x48, -50, 0x3D, 0x02, 0x01 }),
+ false).getInts());
+ assertArrayEquals(new Integer[]{ 1, 2, 840, 113549, 1, 9, 14 },
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x06, 0x09, 0x2A, -122, 0x48, -122, -9, 0x0D, 0x01,
+ 0x09, 0x0E }), false).getInts());
+ }
+
+ @Test
+ void testParseFail() {
+ assertThrows(ParseException.class, () ->
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 0x0 }), false));
+ assertThrows(ParseException.class, () ->
+ new ObjectIdentifier(new BytesReader(new Byte[]{ 0x6, 0x9, 0x2A, -122, 0x48, -122, -9, 0x0D,
+ 0x01, 0x01, -117 }), false));
+ }
+}
diff --git a/src/test/model/asn1/OctetStringTest.java b/src/test/model/asn1/OctetStringTest.java
new file mode 100644
index 0000000..9e6e8a9
--- /dev/null
+++ b/src/test/model/asn1/OctetStringTest.java
@@ -0,0 +1,44 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class OctetStringTest {
+ @Test
+ void testConstructor() {
+ assertArrayEquals(new Byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 },
+ new OctetString(OctetString.TAG, null, new Byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 })
+ .getBytes());
+ assertArrayEquals(new Byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 },
+ new OctetString(OctetString.TAG, null, new Byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 })
+ .encodeValueDER());
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(new Byte[]{ 0x04, 0x06, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 },
+ new OctetString(OctetString.TAG, null, new Byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 })
+ .encodeDER());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertArrayEquals(new Byte[]{ 0x0A, 0x0B, 0x0C },
+ new OctetString(new BytesReader(new Byte[]{ 0x4, 3, 0x0A, 0x0B, 0x0C }), false)
+ .getBytes());
+ }
+
+ @Test
+ void testParseFail() {
+ // EOF
+ assertThrows(ParseException.class, () ->
+ new OctetString(new BytesReader(new Byte[]{ 0x4, 2, 0x0 }), false));
+ }
+}
diff --git a/src/test/model/asn1/PrintableStringTest.java b/src/test/model/asn1/PrintableStringTest.java
new file mode 100644
index 0000000..f46f400
--- /dev/null
+++ b/src/test/model/asn1/PrintableStringTest.java
@@ -0,0 +1,71 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class PrintableStringTest {
+ private final String ILLEGAL_CHARS_TO_TEST =
+ "<>\\[]{}@!*&^%$#_|`~";
+
+ @Test
+ void testAcceptedString() throws ParseException {
+ final PrintableString string =
+ new PrintableString(PrintableString.TAG, null, "0123456789abCdExYz '()+,-./:=?");
+ assertEquals("0123456789abCdExYz '()+,-./:=?", string.getString());
+ }
+
+ @Test
+ void testIllegalStrings() {
+ ILLEGAL_CHARS_TO_TEST.chars()
+ .forEach(c ->
+ assertThrows(ParseException.class,
+ () -> new PrintableString(PrintableString.TAG, null, Character.toString(c)),
+ String.format("Expected failing validation by char '%c'.", c))
+ );
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(
+ new Byte[] { 0x68, 0x68, 0x68 },
+ new PrintableString(PrintableString.TAG, null, "hhh").encodeValueDER());
+ assertArrayEquals(
+ new Byte[] {
+ 0x13, 0x02, // Tag - Length
+ 0x68, 0x69 // Value
+ }, new PrintableString(PrintableString.TAG, null, "hi").encodeDER());
+ assertArrayEquals(
+ new Byte[] {
+ -86, 0x05, // Parent Tag - Length
+ 0x13, 0x03, // Inner Tag - Length
+ 0x68, 0x69, 0x69 // Value
+ }, new PrintableString(PrintableString.TAG,
+ new Tag(TagClass.CONTEXT_SPECIFIC, true, 10),
+ "hii").encodeDER());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertEquals("123",
+ new PrintableString(new BytesReader(new Byte[]{ 0x13, 3, '1', '2', '3' }), false)
+ .getString());
+ assertEquals("",
+ new PrintableString(new BytesReader(new Byte[]{ 0x13, 0, '1', '2', '3' }), false)
+ .getString());
+ }
+
+ @Test
+ void testParseFail() {
+ // EOF
+ assertThrows(ParseException.class, () ->
+ new PrintableString(new BytesReader(new Byte[]{ 0x13, 1 }), false));
+ // Illegal chars
+ assertThrows(ParseException.class, () ->
+ new PrintableString(new BytesReader(new Byte[]{ 0x13, 2, '1', '*' }), false));
+ assertThrows(ParseException.class, () ->
+ new PrintableString(new BytesReader(new Byte[]{ 0x13, 2, '1', '@' }), false));
+ }
+}
diff --git a/src/test/model/asn1/TagClassTest.java b/src/test/model/asn1/TagClassTest.java
new file mode 100644
index 0000000..d510618
--- /dev/null
+++ b/src/test/model/asn1/TagClassTest.java
@@ -0,0 +1,15 @@
+package model.asn1;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TagClassTest {
+ @Test
+ void testConstructor() {
+ assertEquals(TagClass.Values.UNIVERSAL, TagClass.UNIVERSAL.getVal());
+ assertEquals(TagClass.Values.PRIVATE, TagClass.PRIVATE.getVal());
+ assertEquals(TagClass.Values.CONTENT_SPECIFIC, TagClass.CONTEXT_SPECIFIC.getVal());
+ assertEquals(TagClass.Values.APPLICATION, TagClass.APPLICATION.getVal());
+ }
+}
diff --git a/src/test/model/asn1/TagTest.java b/src/test/model/asn1/TagTest.java
new file mode 100644
index 0000000..02df91f
--- /dev/null
+++ b/src/test/model/asn1/TagTest.java
@@ -0,0 +1,107 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TagTest {
+ @Test
+ void testConstructor() {
+ assertEquals(ASN1Object.TAG_SEQUENCE.getNumber(),
+ new Tag(TagClass.UNIVERSAL, false, ASN1Object.TAG_SEQUENCE.getNumber()).getNumber());
+ assertEquals(ASN1Object.TAG_SEQUENCE.getCls(),
+ new Tag(TagClass.UNIVERSAL, false, ASN1Object.TAG_SEQUENCE.getNumber()).getCls());
+ assertEquals(ASN1Object.TAG_SEQUENCE.isConstructive(),
+ new Tag(TagClass.UNIVERSAL,
+ ASN1Object.TAG_SEQUENCE.isConstructive(),
+ ASN1Object.TAG_SEQUENCE.getNumber())
+ .isConstructive());
+ }
+
+ @Test
+ void testParseSuccess() throws ParseException {
+ // Test basic parsing
+ assertEquals(0x1,
+ new Tag(new BytesReader(new Byte[]{ 0x1 })).getNumber());
+ assertEquals(TagClass.UNIVERSAL,
+ new Tag(new BytesReader(new Byte[]{ 0x1 })).getCls());
+ assertFalse(new Tag(new BytesReader(new Byte[]{ 0x1 })).isConstructive());
+ // Test basic parsing with different class
+ assertEquals(5,
+ new Tag(new BytesReader(new Byte[]{ 101 })).getNumber());
+ assertEquals(TagClass.APPLICATION,
+ new Tag(new BytesReader(new Byte[]{ 101 })).getCls());
+ assertTrue(new Tag(new BytesReader(new Byte[]{ 101 })).isConstructive());
+ // Test different classes
+ assertEquals(TagClass.UNIVERSAL,
+ new Tag(new BytesReader(new Byte[]{ 1 })).getCls()); // 0b00000001
+ assertEquals(TagClass.PRIVATE,
+ new Tag(new BytesReader(new Byte[]{ -63 })).getCls()); // 0b11000001
+ assertEquals(TagClass.CONTEXT_SPECIFIC,
+ new Tag(new BytesReader(new Byte[]{ -127 })).getCls()); // 0b10000001
+ assertEquals(TagClass.APPLICATION,
+ new Tag(new BytesReader(new Byte[]{ 65 })).getCls()); // 0b01000001
+ // Test different numbers
+ assertEquals(0x10,
+ new Tag(new BytesReader(new Byte[]{ 0x10 })).getNumber());
+ assertEquals(31,
+ new Tag(new BytesReader(new Byte[]{ 31 })).getNumber());
+ // Test constructive bit
+ assertFalse(new Tag(new BytesReader(new Byte[]{ 0x1 })).isConstructive());
+ assertTrue(new Tag(new BytesReader(new Byte[]{ 33 })).isConstructive());
+ // Test modification
+ BytesReader reader = new BytesReader(new Byte[]{ 33 });
+ assertEquals(0, reader.getIndex());
+ new Tag(reader);
+ assertEquals(1, reader.getIndex());
+ }
+
+ @Test
+ void testParseFail() {
+ // No enough bytes
+ assertThrows(ParseException.class, () -> {
+ BytesReader reader = new BytesReader(new Byte[]{ 33 });
+ reader.require(1, true);
+ new Tag(reader);
+ });
+ // Number zero
+ assertThrows(ParseException.class, () -> new Tag(new BytesReader(new Byte[]{ 0 })));
+ }
+
+ @Test
+ void testEncode() {
+ // Basic encoding
+ assertArrayEquals(new Byte[]{ 1 }, new Tag(TagClass.UNIVERSAL, false, 1).encodeDER());
+ assertArrayEquals(new Byte[]{ 31 }, new Tag(TagClass.UNIVERSAL, false, 31).encodeDER());
+ // With different class
+ assertArrayEquals(new Byte[]{ -127 }, new Tag(TagClass.CONTEXT_SPECIFIC, false, 1).encodeDER());
+ assertArrayEquals(new Byte[]{ -61 }, new Tag(TagClass.PRIVATE, false, 3).encodeDER());
+ assertArrayEquals(new Byte[]{ 71 }, new Tag(TagClass.APPLICATION, false, 7).encodeDER());
+ // With different constructive bit
+ assertArrayEquals(new Byte[]{ 63 }, new Tag(TagClass.UNIVERSAL, true, 31).encodeDER());
+ }
+
+ @Test
+ void testEnforce() {
+ assertThrows(ParseException.class, () ->
+ new Tag(TagClass.UNIVERSAL, true, 10)
+ .enforce(new Tag(TagClass.UNIVERSAL, true, 9)));
+ assertThrows(ParseException.class, () ->
+ new Tag(TagClass.UNIVERSAL, true, 10)
+ .enforce(new Tag(TagClass.UNIVERSAL, true, 11)));
+ assertThrows(ParseException.class, () ->
+ new Tag(TagClass.UNIVERSAL, true, 10)
+ .enforce(new Tag(TagClass.UNIVERSAL, false, 10)));
+ assertThrows(ParseException.class, () ->
+ new Tag(TagClass.UNIVERSAL, true, 10)
+ .enforce(new Tag(TagClass.APPLICATION, true, 10)));
+ assertThrows(ParseException.class, () ->
+ new Tag(TagClass.UNIVERSAL, true, 10)
+ .enforce(new Tag(TagClass.PRIVATE, true, 10)));
+ assertThrows(ParseException.class, () ->
+ new Tag(TagClass.UNIVERSAL, true, 10)
+ .enforce(new Tag(TagClass.CONTEXT_SPECIFIC, true, 10)));
+ }
+}
diff --git a/src/test/model/asn1/UTF8StringTest.java b/src/test/model/asn1/UTF8StringTest.java
new file mode 100644
index 0000000..a2518ae
--- /dev/null
+++ b/src/test/model/asn1/UTF8StringTest.java
@@ -0,0 +1,64 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UTF8StringTest {
+ private static final String[] UTF8_CHARS = new String[] {
+ new String(new byte[]{ -16, -97, -113, -77, -17, -72, -113, -30,
+ -128, -115, -30, -102, -89, -17, -72, -113 }, StandardCharsets.UTF_8),
+ new String(new byte[]{ -16, -97, -112, -79 }, StandardCharsets.UTF_8)
+ };
+
+ @Test
+ void testAcceptedString() throws ParseException {
+ final ASN1String string =
+ new UTF8String(PrintableString.TAG, null, "0123456789abCdExYz '()+,-./:=?*@" +
+ String.join("", UTF8_CHARS));
+ assertEquals("0123456789abCdExYz '()+,-./:=?*@" + String.join("", UTF8_CHARS),
+ string.getString());
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(
+ new Byte[]{
+ -16, -97, -113, -77, -17, -72, -113, -30,
+ -128, -115, -30, -102, -89, -17, -72, -113,
+ -16, -97, -112, -79
+ }, new UTF8String(UTF8String.TAG, null, UTF8_CHARS[0] + UTF8_CHARS[1])
+ .encodeValueDER());
+ assertArrayEquals(
+ new Byte[] {
+ 0x0C, 6, // Tag - Length
+ 0x68, 0x69, // Value
+ -16, -97, -112, -79
+ }, new UTF8String(UTF8String.TAG, null, "hi" + UTF8_CHARS[1]).encodeDER());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertEquals(UTF8_CHARS[0],
+ new UTF8String(new BytesReader(new Byte[]{ 0x0C, 16,
+ -16, -97, -113, -77, -17, -72, -113, -30,
+ -128, -115, -30, -102, -89, -17, -72, -113
+ }), false).getString());
+ assertEquals("",
+ new UTF8String(new BytesReader(new Byte[]{ 0x0C, 0, '1', '2', '3' }), false)
+ .getString());
+ }
+
+ @Test
+ void testParseFail() {
+ // EOF
+ assertThrows(ParseException.class, () ->
+ new UTF8String(new BytesReader(new Byte[]{ 0x0C, 1 }), false));
+ }
+}
diff --git a/src/test/model/asn1/UtcTimeTest.java b/src/test/model/asn1/UtcTimeTest.java
new file mode 100644
index 0000000..5ba7e13
--- /dev/null
+++ b/src/test/model/asn1/UtcTimeTest.java
@@ -0,0 +1,121 @@
+package model.asn1;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UtcTimeTest {
+ @Test
+ void testConstructor() throws ParseException {
+ final ZonedDateTime now = ZonedDateTime.now();
+ assertEquals(now, new UtcTime(UtcTime.TAG, null, now).getTimestamp());
+
+ final ASN1Time parsed = new UtcTime(new BytesReader(new Byte[] {
+ 0x17, 13,
+ '1', '9', '0', '8', '1', '0', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false);
+ assertEquals("190810114514Z",
+ parsed.toString());
+ assertEquals(ZonedDateTime.of(2019, 8, 10, 11, 45, 14,
+ 0, ZoneId.of("UTC")),
+ parsed.getTimestamp());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ ASN1Time parsed = new UtcTime(new BytesReader(new Byte[] {
+ 0x17, 13,
+ '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false);
+
+ assertEquals(ZonedDateTime.of(2023, 9, 27, 11, 45, 14,
+ 0, ZoneId.of("UTC")),
+ parsed.getTimestamp());
+
+ // No seconds
+ parsed = new UtcTime(new BytesReader(new Byte[] {
+ 0x17, 11,
+ '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', 'Z'
+ }), false);
+ assertEquals(ZonedDateTime.of(2023, 9, 27, 11, 45, 0,
+ 0, ZoneId.of("UTC")),
+ parsed.getTimestamp());
+
+ // Length 0
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 0
+ }), false));
+ // Early EOF
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 13
+ }), false));
+ // No tailing Z
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 12,
+ '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4'
+ }), false));
+ // Custom timezone
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 18,
+ '2', '0', '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4', 'Z', '-', '0', '8'
+ }), false));
+ // Invalid month / day / hour / minute / second
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 13,
+ '2', '3', '1', '3', '2', '7', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 13,
+ '2', '3', '1', '0', '3', '2', '1', '1', '4', '5', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 13,
+ '2', '3', '1', '0', '3', '0', '2', '5', '4', '5', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 13,
+ '2', '3', '1', '0', '3', '0', '2', '4', '6', '1', '1', '4', 'Z'
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new UtcTime(new BytesReader(new Byte[]{
+ 0x17, 13,
+ '2', '3', '1', '0', '3', '0', '2', '4', '6', '0', '6', '1', 'Z'
+ }), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertEquals("230927114514Z", new UtcTime(new BytesReader(new Byte[] {
+ -95, 15,
+ 0x17, 13,
+ '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', '1', '4', 'Z'
+ }), true).toString());
+ // No seconds
+ assertEquals("2309271145Z", new UtcTime(new BytesReader(new Byte[] {
+ -95, 13,
+ 0x17, 11,
+ '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', 'Z'
+ }), true).toString());
+
+ // To byte array
+ assertArrayEquals(new Byte[] {
+ 0x17, 11,
+ '2', '3', '0', '9', '2', '7', '1', '1', '4', '5', 'Z'
+ }, new UtcTime(UtcTime.TAG, null, ZonedDateTime.of(2023, 9,
+ 27, 11, 45, 0, 0, ZoneId.of("UTC")))
+ .encodeDER());
+ }
+}
diff --git a/src/test/model/asn1/parsing/BytesReaderTest.java b/src/test/model/asn1/parsing/BytesReaderTest.java
new file mode 100644
index 0000000..3b63a79
--- /dev/null
+++ b/src/test/model/asn1/parsing/BytesReaderTest.java
@@ -0,0 +1,95 @@
+package model.asn1.parsing;
+
+import model.asn1.Tag;
+import model.asn1.TagClass;
+import model.asn1.exceptions.ParseException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BytesReaderTest {
+ private BytesReader target;
+
+ @BeforeEach
+ void setup() {
+ target = new BytesReader(new Byte[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ }
+
+ @Test
+ void testConstructor() {
+ assertEquals(0, target.getIndex());
+ assertEquals(10, target.getRawInput().length);
+ assertArrayEquals(new Byte[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, target.getRawInput());
+ }
+
+ @Test
+ void testBytesRemaining() {
+ assertEquals(10, target.bytesRemaining());
+ target.read(5, true);
+ assertEquals(5, target.bytesRemaining());
+ }
+
+ @Test
+ void testRead() {
+ assertEquals(10, target.bytesRemaining());
+ assertEquals(0, target.getIndex());
+ assertArrayEquals(new Byte[]{ 1, 2 }, target.read(2, true));
+ assertEquals(8, target.bytesRemaining());
+ assertEquals(2, target.getIndex());
+ assertArrayEquals(new Byte[]{ 3 }, target.read(1, false));
+ assertEquals(8, target.bytesRemaining());
+ assertEquals(2, target.getIndex());
+ assertArrayEquals(new Byte[]{ 3, 4, 5, 6, 7 }, target.read(5, true));
+ assertEquals(3, target.bytesRemaining());
+ assertEquals(7, target.getIndex());
+ assertArrayEquals(new Byte[]{ 8, 9, 10 }, target.read(3, false));
+ assertEquals(3, target.bytesRemaining());
+ assertEquals(7, target.getIndex());
+ assertArrayEquals(new Byte[]{ 8, 9, 10 }, target.read(3, true));
+ assertEquals(0, target.bytesRemaining());
+ assertEquals(10, target.getIndex());
+ }
+
+ @Test
+ void testRequire() throws Exception { // TODO: Exception testing
+ assertEquals(10, target.bytesRemaining());
+ assertEquals(0, target.getIndex());
+ assertArrayEquals(new Byte[]{ 1, 2 }, target.require(2, true));
+ assertArrayEquals(new Byte[]{ 3, 4, 5, 6, 7, 8, 9 }, target.require(7, true));
+ assertArrayEquals(new Byte[]{ 10 }, target.require(1, false));
+ assertThrows(ParseException.class, () -> target.require(2, true));
+ assertArrayEquals(new Byte[]{ 10 }, target.require(1, true));
+ assertThrows(ParseException.class, () -> target.require(1, true));
+ }
+
+ @Test
+ void testValidateSize() throws Exception { // TODO: Exception testing
+ assertEquals(10, target.bytesRemaining());
+ assertEquals(0, target.getIndex());
+ assertArrayEquals(new Byte[]{ 1, 2 }, target.require(2, true));
+ assertArrayEquals(new Byte[]{ 3, 4, 5, 6, 7, 8, 9 }, target.require(7, true));
+ assertArrayEquals(new Byte[]{ 10 }, target.require(1, false));
+ target.validateSize(1);
+ assertThrows(ParseException.class, () -> target.validateSize(2));
+ assertArrayEquals(new Byte[]{ 10 }, target.require(1, true));
+ assertThrows(ParseException.class, () -> target.validateSize(1));
+ }
+
+ @Test
+ void testDetectTag() throws Exception {
+ final BytesReader reader = new BytesReader(new Byte[]{ -95, 0 });
+ assertTrue(reader.detectTag(new Tag(TagClass.CONTEXT_SPECIFIC, true, 1)));
+ assertFalse(reader.detectTag(new Tag(TagClass.CONTEXT_SPECIFIC, true, 0)));
+ assertFalse(reader.detectTag(new Tag(TagClass.UNIVERSAL, true, 0)));
+ assertFalse(reader.detectTag(new Tag(TagClass.CONTEXT_SPECIFIC, false, 0)));
+ }
+
+ @Test
+ void testGetTag() throws Exception {
+ BytesReader reader = new BytesReader(new Byte[]{ -95, 0 });
+ assertEquals(1, reader.getTag(false).getNumber());
+ reader = new BytesReader(new Byte[]{ -96, 0, -95, 0 });
+ assertEquals(1, reader.getTag(true).getNumber());
+ }
+}