aboutsummaryrefslogtreecommitdiff
path: root/src/test
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/test
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/test')
-rw-r--r--src/test/model/MyModelTest.java5
-rw-r--r--src/test/model/TestConstants.java544
-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
-rw-r--r--src/test/model/csr/AttributeTest.java53
-rw-r--r--src/test/model/csr/AttributesTest.java69
-rw-r--r--src/test/model/csr/CertificationRequestInfoTest.java174
-rw-r--r--src/test/model/csr/CertificationRequestTest.java114
-rw-r--r--src/test/model/csr/ValuesTest.java133
-rw-r--r--src/test/model/pki/AlgorithmIdentifierTest.java84
-rw-r--r--src/test/model/pki/SubjectPublicKeyInfoTest.java115
-rw-r--r--src/test/model/pki/cert/CertificateTest.java81
-rw-r--r--src/test/model/pki/cert/ExtensionTest.java118
-rw-r--r--src/test/model/pki/cert/ExtensionsTest.java112
-rw-r--r--src/test/model/pki/cert/TbsCertificateTest.java184
-rw-r--r--src/test/model/pki/cert/ValidityTest.java118
-rw-r--r--src/test/model/pki/crl/CertificateListContentTest.java37
-rw-r--r--src/test/model/pki/crl/CertificateListTest.java52
-rw-r--r--src/test/model/pki/crl/RevokedCertificateTest.java25
-rw-r--r--src/test/model/x501/AttributeTypeAndValueTest.java90
-rw-r--r--src/test/model/x501/NameTest.java167
-rw-r--r--src/test/model/x501/RelativeDistinguishedNameTest.java95
36 files changed, 3694 insertions, 5 deletions
diff --git a/src/test/model/MyModelTest.java b/src/test/model/MyModelTest.java
deleted file mode 100644
index 91b287a..0000000
--- a/src/test/model/MyModelTest.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package model;
-
-class MyModelTest {
- // delete or rename this class!
-} \ No newline at end of file
diff --git a/src/test/model/TestConstants.java b/src/test/model/TestConstants.java
new file mode 100644
index 0000000..3356549
--- /dev/null
+++ b/src/test/model/TestConstants.java
@@ -0,0 +1,544 @@
+package model;
+
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.csr.Attribute;
+import model.csr.Attributes;
+import model.csr.Values;
+import model.pki.AlgorithmIdentifier;
+import model.pki.SubjectPublicKeyInfo;
+import model.pki.cert.Extension;
+import model.pki.cert.Extensions;
+import model.pki.cert.TbsCertificate;
+import model.pki.cert.Validity;
+import model.pki.crl.CertificateListContent;
+import model.pki.crl.Reason;
+import model.pki.crl.RevokedCertificate;
+import model.x501.AttributeTypeAndValue;
+import model.x501.Name;
+import model.x501.RelativeDistinguishedName;
+import ui.Utils;
+
+import java.nio.charset.StandardCharsets;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static model.asn1.ASN1Object.TAG_SEQUENCE;
+import static model.asn1.ASN1Object.TAG_SET;
+import static model.asn1.ObjectIdentifier.OID_EXTENSION_REQUEST;
+import static model.asn1.ObjectIdentifier.TAG;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public final class TestConstants {
+ // An opaque value of extended key usage block.
+ public static final ASN1Object EXT_KEY_USAGE;
+
+ // Example SubjectAlternativeName request attribute.
+ public static final ASN1Object SAN;
+
+ public static final Values CSR_ATTR_VALUES_2;
+
+ public static final Attribute CSR_ATTR_2;
+
+ public static final Byte[] CSR_ATTR_VALUES_2_DER = new Byte[]{
+ 0x30, 81, // Attribute
+ // 1.2.840.1113549.1.9.14
+ 0x06, 0x09, 0x2A, -122, 0x48, -122, -9, 0x0D, 0x01, 0x09, 0x0E,
+
+ 0x31, 68, // Values
+
+ 0x30, 0x1F, // SEQUENCE (AttributeValue)
+ 0x30, 0x1D, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x25, // 2.5.29.37 extKeyUsage
+ 0x04, 0x16, // OCTET STRING
+ 0x30, 0x14, // SEQUENCE
+ // 1.3.6.1.5.5.7.3.1 serverAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ // 1.3.6.1.5.5.7.3.2 clientAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+
+ 0x30, 0x21, // SEQUENCE (AttributeValue)
+ 0x30, 0x1F, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x11, // 2.5.29.17 subjectAltName
+ 0x04, 0x18, // OCTET STRING
+ 0x30, 0x16, // SEQUENCE
+ -126, 0x14, // [2]
+ 0x6C, 0x70, 0x2D, 0x62, 0x32, 0x35, 0x35, 0x2E, 0x61, // lp-b255.yuuta.moe
+ 0x64, 0x2E, 0x79, 0x75, 0x75, 0x74, 0x61, 0x2E, 0x6D, 0x6F, 0x65
+ };
+
+ public static final Attribute CSR_ATTR_1;
+
+ public static final Byte[] CSR_ATTR_1_DER = new Byte[] {
+ 0x30, 0x1C,
+ 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, -126, 0x37, 0x0D, 0x02, 0x03, 0x31, 0x0E, 0x16,
+ 0x0C, 0x31, 0x30, 0x2E, 0x30, 0x2E, 0x31, 0x39, 0x30, 0x34, 0x35, 0x2E, 0x32
+ };
+
+ public static final Attributes CSR_ATTRS_2;
+
+ public static final Byte[] L_MILANO_DER = new Byte[]{
+ 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C,
+ 0x06, 0x4D, 0x69, 0x6C, 0x61, 0x6E, 0x6F
+ };
+ public static final RelativeDistinguishedName L_MILANO;
+ public static final Byte[] CN_TEST_ED25519_DER = new Byte[] {
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C,
+ 0x0C, 0x54, 0x65, 0x73, 0x74, 0x20, 0x65, 0x64,
+ 0x32, 0x35, 0x35, 0x31, 0x39,
+ };
+ public static final RelativeDistinguishedName L_MILANO_CN_TEST_ED25519;
+
+ // CN = yuuta
+ public static final RelativeDistinguishedName CN_YUUTA;
+
+ // OU = users
+ public static final RelativeDistinguishedName OU_USERS;
+
+ // C = CA
+ public static final RelativeDistinguishedName C_CA;
+
+ // CN = yuuta, OU = users, C = CA
+ public static final Name NAME_1;
+
+ // SN = Qwq
+ public static final RelativeDistinguishedName SN_QWQ;
+
+ // O = IT
+ public static final RelativeDistinguishedName O_IT;
+
+ // C = CN
+ public static final RelativeDistinguishedName C_CN;
+
+ // SN = Qwq, O = IT, C = CN
+ public static final Name NAME_2;
+
+ /**
+ * Certificate:
+ * Data:
+ * Version: 3 (0x2)
+ * Serial Number:
+ * 70:fa:0f:fa:a6:d7:f4:b4:93:05:5d:a9:d3:e4:42:a8:52:60:b3:f8
+ * Signature Algorithm: ecdsa-with-SHA256
+ * Issuer: CN = Yuuta Root CA, C = CA
+ * Validity
+ * Not Before: Jun 23 02:50:46 2023 GMT
+ * Not After : Jun 23 02:50:46 2048 GMT
+ * Subject: CN = Yuuta Root CA, C = CA
+ * Subject Public Key Info:
+ * Public Key Algorithm: id-ecPublicKey
+ * Public-Key: (256 bit)
+ * pub:
+ * ASN1 OID: prime256v1
+ * NIST CURVE: P-256
+ * X509v3 extensions:
+ * X509v3 Subject Key Identifier:
+ * 78:92:E0:6C:70:F5:A3:BE:02:EE:44:BA:A7:8C:DA:D6:B5:43:A7:93
+ * X509v3 Authority Key Identifier:
+ * 78:92:E0:6C:70:F5:A3:BE:02:EE:44:BA:A7:8C:DA:D6:B5:43:A7:93
+ * X509v3 Basic Constraints: critical
+ * CA:TRUE
+ * X509v3 Key Usage: critical
+ * Digital Signature, Certificate Sign, CRL Sign
+ * Signature Algorithm: ecdsa-with-SHA256
+ * Signature Value:
+ */
+ public static final String CERT_L1_ECC_PEM =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIBrzCCAVWgAwIBAgIUcPoP+qbX9LSTBV2p0+RCqFJgs/gwCgYIKoZIzj0EAwIw\n" +
+ "JTEWMBQGA1UEAwwNWXV1dGEgUm9vdCBDQTELMAkGA1UEBhMCQ0EwHhcNMjMwNjIz\n" +
+ "MDI1MDQ2WhcNNDgwNjIzMDI1MDQ2WjAlMRYwFAYDVQQDDA1ZdXV0YSBSb290IENB\n" +
+ "MQswCQYDVQQGEwJDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABB3ocbzdSHAm\n" +
+ "cWzdBFs/Xd4UMYs/MYAYKjPlGYYT1udIL5UVOlmN7QnkUxrzYbI1YW5mX1/PCuJl\n" +
+ "ZT0iKzBxLCSjYzBhMB0GA1UdDgQWBBR4kuBscPWjvgLuRLqnjNrWtUOnkzAfBgNV\n" +
+ "HSMEGDAWgBR4kuBscPWjvgLuRLqnjNrWtUOnkzAPBgNVHRMBAf8EBTADAQH/MA4G\n" +
+ "A1UdDwEB/wQEAwIBhjAKBggqhkjOPQQDAgNIADBFAiAJr6S6xkgxMitFmnS/0cIu\n" +
+ "E7W9Ykii5d2Fe9+Lu4nL9wIhAIwHnAvZ4YzwfY6P5K4SaBwzzKPiq2zdpaXBm0lX\n" +
+ "qcsz\n" +
+ "-----END CERTIFICATE-----";
+ /**
+ * Certificate:
+ * Data:
+ * Version: 3 (0x2)
+ * Serial Number:
+ * 3e:10:93:9d:e4:57:8d:39:87:fd:ff:42:7b:da:65:5b:1f:21:cb:07
+ * Signature Algorithm: ecdsa-with-SHA512
+ * Issuer: CN = Yuuta Root CA, C = CA
+ * Validity
+ * Not Before: Jun 24 00:15:22 2023 GMT
+ * Not After : Jun 21 00:15:22 2033 GMT
+ * Subject: DC = MOE, DC = YUUTA, DC = AD, CN = Yuuta Home Issuing CA
+ * Subject Public Key Info:
+ * Public Key Algorithm: rsaEncryption
+ * Public-Key: (4096 bit)
+ * Modulus:
+ * Exponent: 65537 (0x10001)
+ * X509v3 extensions:
+ * X509v3 Subject Key Identifier:
+ * B1:C2:A7:81:63:66:4B:72:0A:DD:FD:7D:20:29:BD:6B:49:09:61:C0
+ * X509v3 Authority Key Identifier:
+ * 78:92:E0:6C:70:F5:A3:BE:02:EE:44:BA:A7:8C:DA:D6:B5:43:A7:93
+ * X509v3 Basic Constraints: critical
+ * CA:TRUE, pathlen:0
+ * X509v3 Key Usage: critical
+ * Digital Signature, Certificate Sign, CRL Sign
+ * X509v3 CRL Distribution Points:
+ * Full Name:
+ * URI:http://home.yuuta.moe/pki/rootca.crl
+ * Authority Information Access:
+ * CA Issuers - URI:http://home.yuuta.moe/pki/rootca.crt
+ * Signature Algorithm: ecdsa-with-SHA512
+ * Signature Value:
+ */
+ public static final String CERT_L2_RSA_PEM =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIEMjCCA9mgAwIBAgIUPhCTneRXjTmH/f9Ce9plWx8hywcwCgYIKoZIzj0EAwQw\n" +
+ "JTEWMBQGA1UEAwwNWXV1dGEgUm9vdCBDQTELMAkGA1UEBhMCQ0EwHhcNMjMwNjI0\n" +
+ "MDAxNTIyWhcNMzMwNjIxMDAxNTIyWjBgMRMwEQYKCZImiZPyLGQBGRYDTU9FMRUw\n" +
+ "EwYKCZImiZPyLGQBGRYFWVVVVEExEjAQBgoJkiaJk/IsZAEZFgJBRDEeMBwGA1UE\n" +
+ "AwwVWXV1dGEgSG9tZSBJc3N1aW5nIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A\n" +
+ "MIICCgKCAgEA2WTLz4X8B2XN/AiDJNsQQNMiBDjye/TfpbC3dLsUZr4BneH9EX+I\n" +
+ "lKkaoJaSIXU7iXoN43FeLX5iuoq+aj4f3x+qevWySD7xLLt8gpozN5MKBdRXtaem\n" +
+ "4js3Nm3YLUbjv115sddHN/3QTQXgBSUGdjyi8woL54IKdKYzB1g2Jn2Et383usMA\n" +
+ "yHd3gCbwszvE5jpOgBIHxZMgMnmVAQhbQNzoEDMIkwaXmSt4jwX03oigf0KAaD+a\n" +
+ "XIwQRl15iIDZnG6rRw6+eiIR8c+x1ot1/u5qncwNhRUtLbbX3QfBQ6D/XBSfrqmA\n" +
+ "zhddM/i2Qt5Iw44CcLSGujFeb9ybU7NLx02EjfQsSAUGQR4VuXyD+//FsLYkh7g3\n" +
+ "WmdBTWzIhVnYEU9ohTeXaZZNTp9T67czqnntFbaCdOxnwOrcmFt1v0skrHd5mHKe\n" +
+ "1W3OU6XOjM6vQwcwhPUUGxAXYBcqwQ84fzD26CZz5g8I8HpnpmJ+SNtFIg+SnPOs\n" +
+ "sslnsoeMZpDPESwORYgayXIWkglop1fYeD4/ictH4me70vOIHF9fWqI8ydHoNxuw\n" +
+ "uZjZDa0mQgsHTmr40NhDLP/q6MEnS2w/MwHuSd3YbhbjPWFbu0Zo7XreiRkXjRLa\n" +
+ "R22XkuH+FkEGB3ZxQVIkkWf1znaKQS+ZdPuTzpZph5BPL50gE58k+i0CAwEAAaOB\n" +
+ "4DCB3TAdBgNVHQ4EFgQUscKngWNmS3IK3f19ICm9a0kJYcAwHwYDVR0jBBgwFoAU\n" +
+ "eJLgbHD1o74C7kS6p4za1rVDp5MwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8B\n" +
+ "Af8EBAMCAYYwNQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2hvbWUueXV1dGEubW9l\n" +
+ "L3BraS9yb290Y2EuY3JsMEAGCCsGAQUFBwEBBDQwMjAwBggrBgEFBQcwAoYkaHR0\n" +
+ "cDovL2hvbWUueXV1dGEubW9lL3BraS9yb290Y2EuY3J0MAoGCCqGSM49BAMEA0cA\n" +
+ "MEQCIHShp7SwbQ2rQC7l8u4u9rSU6Zl4DRfyor4jiHGAjm0gAiAbOHk6q+3Vm3uq\n" +
+ "Jj92o1yDl09pFNIryojHMDRBpHl6yQ==\n" +
+ "-----END CERTIFICATE-----";
+
+ /**
+ * Certificate:
+ * Data:
+ * Version: 1 (0x0)
+ * Serial Number: 3580 (0xdfc)
+ * Signature Algorithm: sha1WithRSAEncryption
+ * Issuer: C = JP, ST = Tokyo, L = Chuo-ku, O = Frank4DD, OU = WebCert Support, CN = Frank4DD Web CA, emailAddress = support@frank4dd.com
+ * Validity
+ * Not Before: Aug 22 05:27:41 2012 GMT
+ * Not After : Aug 21 05:27:41 2017 GMT
+ * Subject: C = JP, ST = Tokyo, O = Frank4DD, CN = www.example.com
+ * Subject Public Key Info:
+ * Public Key Algorithm: rsaEncryption
+ * Public-Key: (2048 bit)
+ * Modulus:
+ * Exponent: 65537 (0x10001)
+ * Signature Algorithm: sha1WithRSAEncryption
+ * Signature Value:
+ */
+ public static final String CERT_V1_PEM =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIC2jCCAkMCAg38MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\n" +
+ "A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\n" +
+ "MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\n" +
+ "YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\n" +
+ "ODIyMDUyNzQxWhcNMTcwODIxMDUyNzQxWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\n" +
+ "CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\n" +
+ "ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0z9FeMynsC8+u\n" +
+ "dvX+LciZxnh5uRj4C9S6tNeeAlIGCfQYk0zUcNFCoCkTknNQd/YEiawDLNbxBqut\n" +
+ "bMDZ1aarys1a0lYmUeVLCIqvzBkPJTSQsCopQQ9V8WuT252zzNzs68dVGNdCJd5J\n" +
+ "NRQykpwexmnjPPv0mvj7i8XgG379TyW6P+WWV5okeUkXJ9eJS2ouDYdR2SM9BoVW\n" +
+ "+FgxDu6BmXhozW5EfsnajFp7HL8kQClI0QOc79yuKl3492rH6bzFsFn2lfwWy9ic\n" +
+ "7cP8EpCTeFp1tFaD+vxBhPZkeTQ1HKx6hQ5zeHIB5ySJJZ7af2W8r4eTGYzbdRW2\n" +
+ "4DDHCPhZAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQMv+BFvGdMVzkQaQ3/+2noVz\n" +
+ "/uAKbzpEL8xTcxYyP3lkOeh4FoxiSWqy5pGFALdPONoDuYFpLhjJSZaEwuvjI/Tr\n" +
+ "rGhLV1pRG9frwDFshqD2Vaj4ENBCBh6UpeBop5+285zQ4SI7q4U9oSebUDJiuOx6\n" +
+ "+tZ9KynmrbJpTSi0+BM=\n" +
+ "-----END CERTIFICATE-----";
+
+ public static final Byte[] CERT_L1_ECC;
+ public static final Byte[] CERT_L2_RSA;
+ public static final Byte[] CERT_V1;
+
+ public static final ZonedDateTime NOW = ZonedDateTime.now(ZoneId.of("UTC"));
+ public static final TbsCertificate CERT_GENERATED;
+
+ public static final RevokedCertificate REVOKED_CESSATION;
+ public static final Byte[] REVOKED_CESSATION_DER;
+ public static final RevokedCertificate REVOKED_KEY_COMPROMISE;
+ public static final Byte[] REVOKED_KEY_COMPROMISE_DER;
+ public static final CertificateListContent CRL_CONTENT_1;
+ public static final Byte[] CRL_CONTENT_1_DER;
+ public static final CertificateListContent CRL_CONTENT_2;
+ public static final Byte[] CRL_CONTENT_2_DER;
+
+ static {
+ try {
+ EXT_KEY_USAGE = ASN1Object.parse(new BytesReader(new Byte[]{
+ 0x30, 0x1F, // SEQUENCE (AttributeValue)
+ 0x30, 0x1D, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x25, // 2.5.29.37 extKeyUsage
+ 0x04, 0x16, // OCTET STRING
+ 0x30, 0x14, // SEQUENCE
+ // 1.3.6.1.5.5.7.3.1 serverAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ // 1.3.6.1.5.5.7.3.2 clientAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
+ }), false);
+
+ SAN = ASN1Object.parse(new BytesReader(new Byte[]{
+ 0x30, 0x21, // SEQUENCE (AttributeValue)
+ 0x30, 0x1F, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x11, // 2.5.29.17 subjectAltName
+ 0x04, 0x18, // OCTET STRING
+ 0x30, 0x16, // SEQUENCE
+ -126, 0x14, // [2]
+ 0x6C, 0x70, 0x2D, 0x62, 0x32, 0x35, 0x35, 0x2E, 0x61, // lp-b255.yuuta.moe
+ 0x64, 0x2E, 0x79, 0x75, 0x75, 0x74, 0x61, 0x2E, 0x6D, 0x6F, 0x65
+ }), false);
+
+ CSR_ATTR_VALUES_2 = new Values(TAG_SET, null, new ASN1Object[]{
+ EXT_KEY_USAGE,
+ SAN
+ });
+
+ CSR_ATTR_2 = new Attribute(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_EXTENSION_REQUEST),
+ CSR_ATTR_VALUES_2);
+
+ CSR_ATTR_1 = new Attribute(
+ TAG_SEQUENCE, null,
+ new ObjectIdentifier(TAG, null, new Integer[]{ 1, 3, 6, 1, 4, 1, 311, 13, 2, 3 }),
+ new Values(TAG_SET, null, new ASN1Object[]{
+ new IA5String(IA5String.TAG, null, "10.0.19045.2")
+ })
+ );
+
+ CSR_ATTRS_2 = new Attributes(TAG_SET, null, new Attribute[]{
+ CSR_ATTR_2, CSR_ATTR_1
+ });
+
+ // L = Milano
+ L_MILANO = new RelativeDistinguishedName(TAG_SET, null,
+ new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_L),
+ new PrintableString(PrintableString.TAG, null, "Milano"))
+ });
+ // CN = Test ed25519+L = Milano
+ L_MILANO_CN_TEST_ED25519 = new RelativeDistinguishedName(TAG_SET, null,
+ new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_CN),
+ new UTF8String(UTF8String.TAG, null, "Test ed25519")),
+ new AttributeTypeAndValue(TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_L),
+ new UTF8String(UTF8String.TAG, null, "Milano"))
+ });
+
+ CN_YUUTA = new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_CN),
+ new PrintableString(PrintableString.TAG, null, "yuuta"))
+ });
+ OU_USERS = new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_OU),
+ new PrintableString(PrintableString.TAG, null, "users"))
+ });
+ C_CA = new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_C),
+ new PrintableString(PrintableString.TAG, null, "CA"))
+ });
+ NAME_1 = new Name(ASN1Object.TAG_SEQUENCE, null, new RelativeDistinguishedName[]{
+ CN_YUUTA, OU_USERS, C_CA
+ });
+
+ SN_QWQ = new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_SN),
+ new PrintableString(PrintableString.TAG, null, "Qwq"))
+ });
+ O_IT = new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_O),
+ new PrintableString(PrintableString.TAG, null, "IT"))
+ });
+ C_CN = new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_C),
+ new PrintableString(PrintableString.TAG, null, "CN"))
+ });
+ NAME_2 = new Name(ASN1Object.TAG_SEQUENCE, null, new RelativeDistinguishedName[]{
+ SN_QWQ, O_IT, C_CN
+ });
+
+ CERT_L1_ECC = Utils.parsePEM(Utils.byteToByte(CERT_L1_ECC_PEM.getBytes(StandardCharsets.UTF_8)),
+ "CERTIFICATE");
+ CERT_L2_RSA = Utils.parsePEM(Utils.byteToByte(CERT_L2_RSA_PEM.getBytes(StandardCharsets.UTF_8)),
+ "CERTIFICATE");
+ CERT_V1 = Utils.parsePEM(Utils.byteToByte(CERT_V1_PEM.getBytes(StandardCharsets.UTF_8)),
+ "CERTIFICATE");
+
+ CERT_GENERATED = new TbsCertificate(ASN1Object.TAG_SEQUENCE, null,
+ new Int(Int.TAG, new Tag(TagClass.CONTEXT_SPECIFIC, false, 0), TbsCertificate.VERSION_V3),
+ new Int(Int.TAG, null, 100),
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new Name(ASN1Object.TAG_SEQUENCE, null, new RelativeDistinguishedName[]{
+ new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_CN),
+ new PrintableString(PrintableString.TAG, null, "Test CA"))
+ }),
+ new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_C),
+ new PrintableString(PrintableString.TAG, null, "CA"))
+ })
+ }),
+ new Validity(ASN1Object.TAG_SEQUENCE, null,
+ new UtcTime(UtcTime.TAG, null, NOW),
+ new GeneralizedTime(GeneralizedTime.TAG, null, NOW.plusYears(1))),
+ new Name(ASN1Object.TAG_SEQUENCE, null, new RelativeDistinguishedName[]{
+ new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_CN),
+ new PrintableString(PrintableString.TAG, null, "Yuuta Liang"))
+ }),
+ new RelativeDistinguishedName(ASN1Object.TAG_SET, null, new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_C),
+ new PrintableString(PrintableString.TAG, null, "CA"))
+ })
+ }),
+ new SubjectPublicKeyInfo(ASN1Object.TAG_SEQUENCE, null,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_EC_PUBLIC_KEY),
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_PRIME256_V1)),
+ new BitString(BitString.TAG, null, 0, new Byte[]{ 1, 2, 3 })),
+ new Extensions(ASN1Object.TAG_SEQUENCE,
+ new Tag(TagClass.CONTEXT_SPECIFIC, false, 3),
+ new Extension[]{
+ new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_BASIC_CONSTRAINTS),
+ new Bool(Bool.TAG, null, true),
+ new OctetString(OctetString.TAG, null,
+ new Byte[]{ 0x30, 0x06, 0x01, 0x01, -1, 0x02, 0x01, 0x00 })),
+ new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_KEY_USAGE),
+ new Bool(Bool.TAG, null, true),
+ new OctetString(OctetString.TAG, null,
+ new Byte[]{ 0x03, 0x02, 0x01, -122 }))
+ }));
+
+ REVOKED_CESSATION =
+ new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null,
+ new Int(Int.TAG, null, 123),
+ new UtcTime(UtcTime.TAG, null, ZonedDateTime.now(ZoneId.of("UTC"))),
+ Reason.CESSATION_OF_OPERATION);
+ REVOKED_KEY_COMPROMISE =
+ new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null,
+ new Int(Int.TAG, null, 2),
+ new UtcTime(UtcTime.TAG, null, ZonedDateTime.now(ZoneId.of("UTC"))),
+ Reason.KEY_COMPROMISE);
+ REVOKED_CESSATION_DER = combine((byte) 0x30,
+ REVOKED_CESSATION.getSerialNumber().encodeDER(),
+ REVOKED_CESSATION.getRevocationDate().encodeDER(),
+ combine((byte) 0x30,
+ combine((byte) 0x30,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_CRL_REASON).encodeDER(),
+ new OctetString(OctetString.TAG, null,
+ new Byte[]{ 0x0A, 0x01, (byte) Reason.CESSATION_OF_OPERATION.getVal() })
+ .encodeDER())));
+ REVOKED_KEY_COMPROMISE_DER = combine((byte) 0x30,
+ REVOKED_KEY_COMPROMISE.getSerialNumber().encodeDER(),
+ REVOKED_KEY_COMPROMISE.getRevocationDate().encodeDER(),
+ combine((byte) 0x30,
+ combine((byte) 0x30,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_CRL_REASON).encodeDER(),
+ new OctetString(OctetString.TAG, null,
+ new Byte[]{ 0x0A, 0x01, (byte) Reason.KEY_COMPROMISE.getVal() })
+ .encodeDER())));
+ CRL_CONTENT_1 =
+ new CertificateListContent(ASN1Object.TAG_SEQUENCE, null,
+ new Name(ASN1Object.TAG_SEQUENCE, null,
+ new RelativeDistinguishedName[]{
+ new RelativeDistinguishedName(ASN1Object.TAG_SET, null,
+ new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_CN),
+ new PrintableString(PrintableString.TAG, null,
+ "Test CA"))
+ })
+ }),
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new GeneralizedTime(GeneralizedTime.TAG, null,
+ ZonedDateTime.now(ZoneId.of("UTC"))),
+ null,
+ new RevokedCertificate[]{
+ TestConstants.REVOKED_CESSATION,
+ TestConstants.REVOKED_KEY_COMPROMISE
+ });
+ CRL_CONTENT_1_DER = combine((byte) 0x30, CRL_CONTENT_1.getVersion().encodeDER(),
+ CRL_CONTENT_1.getIssuer().encodeDER(),
+ CRL_CONTENT_1.getSignature().encodeDER(),
+ CRL_CONTENT_1.getThisUpdate().encodeDER(),
+ combine((byte) 0x30, REVOKED_CESSATION_DER, REVOKED_KEY_COMPROMISE_DER));
+ CRL_CONTENT_2 = new CertificateListContent(CRL_CONTENT_1.getTag(), CRL_CONTENT_1.getParentTag(),
+ CRL_CONTENT_1.getIssuer(),
+ CRL_CONTENT_1.getSignature(),
+ CRL_CONTENT_1.getThisUpdate(),
+ CRL_CONTENT_1.getThisUpdate(),
+ CRL_CONTENT_1.getRevokedCertificates());
+ CRL_CONTENT_2_DER = combine((byte) 0x30, CRL_CONTENT_2.getVersion().encodeDER(),
+ CRL_CONTENT_2.getIssuer().encodeDER(),
+ CRL_CONTENT_2.getSignature().encodeDER(),
+ CRL_CONTENT_2.getThisUpdate().encodeDER(),
+ CRL_CONTENT_2.getNextUpdate().encodeDER(),
+ combine((byte) 0x30, REVOKED_CESSATION_DER, REVOKED_KEY_COMPROMISE_DER));
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Byte[] mutate(Byte[] in, int i, int from, int to) {
+ Byte[] b = new Byte[in.length];
+ System.arraycopy(in, 0, b, 0, in.length);
+ assertEquals(from, (int) in[i]);
+ b[i] = (byte) to;
+ return b;
+ }
+
+ public static Byte[] combine(Byte tag, Byte[]... vals) {
+ return Stream.of(Collections.singletonList(tag),
+ Arrays.asList(new ASN1Length(Arrays.stream(vals).mapToInt(b -> b.length).sum()).encodeDER()),
+ Arrays.stream(vals).flatMap(Arrays::stream).collect(Collectors.toList()))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ }
+}
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());
+ }
+}
diff --git a/src/test/model/csr/AttributeTest.java b/src/test/model/csr/AttributeTest.java
new file mode 100644
index 0000000..f4daa4c
--- /dev/null
+++ b/src/test/model/csr/AttributeTest.java
@@ -0,0 +1,53 @@
+package model.csr;
+
+import model.TestConstants;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static model.asn1.ObjectIdentifier.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AttributeTest {
+ @Test
+ void testConstructor() throws ParseException {
+ assertArrayEquals(OID_EXTENSION_REQUEST, TestConstants.CSR_ATTR_2.getType().getInts());
+ assertEquals(0x21, TestConstants.CSR_ATTR_2.getValues().getArray()[1].getLength());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ final Attribute parsed = new Attribute(new BytesReader(TestConstants.CSR_ATTR_VALUES_2_DER), false);
+
+ assertArrayEquals(OID_EXTENSION_REQUEST, parsed.getType().getInts());
+ assertEquals(2, parsed.getValues().getArray().length);
+ }
+
+ @Test
+ void testParseFail() {
+ // No type
+ assertThrows(ParseException.class, () -> new Attribute(new BytesReader(new Byte[]{
+ 0x30, 0x0
+ }), false));
+ // No value
+ assertThrows(ParseException.class, () -> new Attribute(new BytesReader(new Byte[]{
+ 0x30, 0x5, 0x6, 0x3, 0x55, 0x4, 0x6
+ }), false));
+ // Incorrect type tag (should be OID)
+ assertThrows(ParseException.class, () -> new Attribute(new BytesReader(new Byte[]{
+ 0x30, 0x9, 0x7, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x49, 0x54
+ }), false));
+ // Incorrect value tag (should be SET)
+ assertThrows(ParseException.class, () -> new Attribute(new BytesReader(new Byte[]{
+ 0x30, 13,
+ 0x06, 0x09, 0x1A, -122, 0x48, -122, -9, 0x0D, 0x01, 0x09, 0x0E,
+ 0x30, 0
+ }), false));
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(TestConstants.CSR_ATTR_1_DER, TestConstants.CSR_ATTR_1.encodeDER());
+ assertArrayEquals(TestConstants.CSR_ATTR_VALUES_2_DER, TestConstants.CSR_ATTR_2.encodeDER());
+ }
+}
diff --git a/src/test/model/csr/AttributesTest.java b/src/test/model/csr/AttributesTest.java
new file mode 100644
index 0000000..86a0112
--- /dev/null
+++ b/src/test/model/csr/AttributesTest.java
@@ -0,0 +1,69 @@
+package model.csr;
+
+import model.TestConstants;
+import model.asn1.ObjectIdentifier;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AttributesTest {
+ @Test
+ void testConstructor() {
+ assertEquals("10.0.19045.2",
+ TestConstants.CSR_ATTRS_2.getArray()[1].getValues().getArray()[0].toString());
+ assertArrayEquals(ObjectIdentifier.OID_EXTENSION_REQUEST,
+ TestConstants.CSR_ATTRS_2.getArray()[0].getType().getInts());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ final Attributes parsed = new Attributes(new BytesReader(new Byte[]{
+ -96, 30,
+ 0x30, 0x1C,
+ 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, -126, 0x37, 0x0D, 0x02, 0x03,
+ 0x31, 0x0E,
+ 0x16, 0x0C, 0x31, 0x30, 0x2E, 0x30, 0x2E, 0x31, 0x39, 0x30, 0x34, 0x35, 0x2E, 0x32
+ }), false);
+ assertEquals(1, parsed.getArray().length);
+ assertEquals("10.0.19045.2", parsed.getArray()[0].getValues().getArray()[0].toString());
+ }
+
+ @Test
+ void testParseFail() {
+ // Incorrect length
+ assertThrows(ParseException.class, () -> new Attributes(new BytesReader(new Byte[]{
+ -96, 31, // Incorrect
+ 0x30, 0x1C,
+ 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, -126, 0x37, 0x0D, 0x02, 0x03,
+ 0x31, 0x0E,
+ 0x16, 0x0C, 0x31, 0x30, 0x2E, 0x30, 0x2E, 0x31, 0x39, 0x30, 0x34, 0x35, 0x2E, 0x32
+ }), false));
+ // Incorrect child item tag
+ assertThrows(ParseException.class, () -> new Attributes(new BytesReader(new Byte[]{
+ -96, 30,
+ 0x31, 0x1C, // Incorrect
+ 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, -126, 0x37, 0x0D, 0x02, 0x03,
+ 0x31, 0x0E,
+ 0x16, 0x0C, 0x31, 0x30, 0x2E, 0x30, 0x2E, 0x31, 0x39, 0x30, 0x34, 0x35, 0x2E, 0x32
+ }), false));
+ }
+
+ @Test
+ void testEncode() {
+ Byte[] a2 = TestConstants.CSR_ATTR_2.encodeDER();
+ Byte[] a1 = TestConstants.CSR_ATTR_1.encodeDER();
+ assertArrayEquals(
+ Stream.of(Arrays.asList(new Byte[]{ 0x31, (byte)(a2.length + a1.length) }),
+ Arrays.asList(a2),
+ Arrays.asList(a1))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new),
+ TestConstants.CSR_ATTRS_2.encodeDER());
+ }
+}
diff --git a/src/test/model/csr/CertificationRequestInfoTest.java b/src/test/model/csr/CertificationRequestInfoTest.java
new file mode 100644
index 0000000..fe2633f
--- /dev/null
+++ b/src/test/model/csr/CertificationRequestInfoTest.java
@@ -0,0 +1,174 @@
+package model.csr;
+
+import model.TestConstants;
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.pki.AlgorithmIdentifier;
+import model.pki.SubjectPublicKeyInfo;
+import org.junit.jupiter.api.Test;
+
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CertificationRequestInfoTest {
+ static final Byte[] CSR_1 = new Byte[] {
+ 0x30, -126, 0x02, 0x03, // SEQUENCE CertificationRequestInfo
+ 0x02, 0x01, 0x00, // Version
+ 0x30, 0x1c, // SEQUENCE Subject
+ 0x31, 0x1a, // RDN
+ 0x30, 0x18, // AttributeTypeAndValue
+ 0x06, 0x03, 0x55, 0x04, 0x03, // OID (CN)
+ 0x0c, 0x11, 0x4d, 0x49, 0x4b, 0x55, 0x2e, 0x41, // PrintableString (MIKU.AD.YUUTA.MOE)
+ 0x44, 0x2e, 0x59, 0x55, 0x55, 0x54, 0x41, 0x2e,
+ 0x4d, 0x4f, 0x45,
+ 0x30, -127, -97, // SEQUENCE SubjectPublicKeyInfo
+ 0x30, 0x0d, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x09, 0x2a, -122, 0x48, -122, -9, 0x0d, // OID (rsaEncryption)
+ 0x01, 0x01, 0x01,
+ 0x05, 0x00, // Null (Parameter)
+ 0x03, -127, -115, 0x00, 0x30, -127, -119, 0x02, // BIT STRING (subjectPublicKey)
+ -127, -127, 0x00, -67, -1, 0x4e, 0x6d, -22,
+ 0x62, 0x6a, 0x11, -120, 0x77, 0x0a, -92, 0x32,
+ -124, -37, 0x22, 0x2f, 0x3d, 0x5d, 0x2a, 0x63,
+ -71, -109, 0x11, -50, -92, 0x4f, -119, 0x3b,
+ 0x14, 0x3b, -54, 0x3c, -106, -42, 0x11, 0x42,
+ 0x78, -110, 0x68, -100, -25, -25, -50, 0x75,
+ -101, 0x21, 0x41, -34, -31, -85, -13, 0x1e,
+ 0x51, -81, 0x25, 0x4f, -1, 0x56, 0x77, 0x5e,
+ -30, 0x27, -104, 0x34, 0x67, -28, -56, 0x55,
+ 0x6a, 0x3c, 0x6f, -38, -85, -63, 0x5f, 0x16,
+ 0x7a, -93, -19, -35, 0x7f, 0x35, 0x0f, -47,
+ -7, -22, -12, -24, -48, 0x25, 0x6d, -114,
+ 0x66, 0x1a, 0x53, -77, 0x67, 0x32, -69, -39,
+ 0x57, -42, -65, -13, 0x5f, 0x6f, 0x53, 0x6d,
+ 0x62, -95, 0x42, 0x12, 0x7b, 0x13, 0x4f, 0x1a,
+ -26, 0x00, -72, -32, 0x2b, -83, 0x3c, 0x35,
+ -103, 0x18, 0x51, 0x02, 0x03, 0x01, 0x00, 0x01,
+ -96, -126, 0x01, 0x3c, // SEQUENCE (attributes)
+ 0x30, 0x1c, 0x06, 0x0a, // SEQUENCE (attribute)
+ 0x2b, 0x06, 0x01, 0x04, 0x01, -126, 0x37, 0x0d,
+ 0x02, 0x03, 0x31, 0x0e, 0x16, 0x0c, 0x31, 0x30,
+ 0x2e, 0x30, 0x2e, 0x31, 0x39, 0x30, 0x34, 0x35,
+ 0x2e, 0x32,
+ 0x30, 0x42, 0x06, // SEQUENCE (attribute)
+ 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, -126, 0x37,
+ 0x15, 0x14, 0x31, 0x35, 0x30, 0x33, 0x02, 0x01,
+ 0x05, 0x0c, 0x11, 0x4d, 0x49, 0x4b, 0x55, 0x2e,
+ 0x41, 0x44, 0x2e, 0x59, 0x55, 0x55, 0x54, 0x41,
+ 0x2e, 0x4d, 0x4f, 0x45, 0x0c, 0x12, 0x4d, 0x49,
+ 0x4b, 0x55, 0x5c, 0x41, 0x64, 0x6d, 0x69, 0x6e,
+ 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72,
+ 0x0c, 0x07, 0x4d, 0x4d, 0x43, 0x2e, 0x45, 0x58,
+ 0x45,
+ 0x30, 0x66, // SEQUENCE (attribute)
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, -126,
+ 0x37, 0x0d, 0x02, 0x02, 0x31, 0x58, 0x30, 0x56,
+ 0x02, 0x01, 0x00, 0x1e, 0x4e, 0x00, 0x4d, 0x00,
+ 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00,
+ 0x73, 0x00, 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00,
+ 0x20, 0x00, 0x53, 0x00, 0x6f, 0x00, 0x66, 0x00,
+ 0x74, 0x00, 0x77, 0x00, 0x61, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x20, 0x00, 0x4b, 0x00, 0x65, 0x00,
+ 0x79, 0x00, 0x20, 0x00, 0x53, 0x00, 0x74, 0x00,
+ 0x6f, 0x00, 0x72, 0x00, 0x61, 0x00, 0x67, 0x00,
+ 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x72, 0x00,
+ 0x6f, 0x00, 0x76, 0x00, 0x69, 0x00, 0x64, 0x00,
+ 0x65, 0x00, 0x72, 0x03, 0x01, 0x00,
+ 0x30, 0x70, 0x06, 0x09, // SEQUENCE (attribute)
+ 0x2a, -122, 0x48, -122, -9, 0x0d, 0x01, 0x09,
+ 0x0e, 0x31, 0x63, 0x30, 0x61, 0x30, 0x0e, 0x06,
+ 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, -1, 0x04,
+ 0x04, 0x03, 0x02, 0x05, -96, 0x30, 0x13, 0x06,
+ 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x03, 0x01, 0x30, 0x1b, 0x06, 0x09, 0x2b, 0x06,
+ 0x01, 0x04, 0x01, -126, 0x37, 0x15, 0x0a, 0x04,
+ 0x0e, 0x30, 0x0c, 0x30, 0x0a, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x30,
+ 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+ 0x04, 0x14, -15, 0x3e, -110, -16, 0x4d, 0x1b,
+ -47, 0x6e, 0x53, 0x7f, -102, 0x1d, 0x19, -75,
+ 0x5e, -22, 0x64, 0x7f, 0x1f, -110,
+ };
+
+ @Test
+ void testConstructor() {
+ final CertificationRequestInfo info = new CertificationRequestInfo(
+ ASN1Object.TAG_SEQUENCE, null,
+ new Int(Int.TAG, null, CertificationRequestInfo.VERSION_V1),
+ TestConstants.NAME_2,
+ new SubjectPublicKeyInfo(ASN1Object.TAG_SEQUENCE, null,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null,
+ 0, new Byte[]{ 1, 2, 3, 4, 5 })),
+ TestConstants.CSR_ATTRS_2);
+ assertEquals(CertificationRequestInfo.VERSION_V1, info.getVersion().getLong());
+ assertEquals(3, info.getSubject().getRdnSequence().length);
+ assertArrayEquals(ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION,
+ info.getSubjectPKInfo().getAlgorithm().getType().getInts());
+ assertEquals(2, info.getAttributes().getArray().length);
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ final CertificationRequestInfo parsed =
+ new CertificationRequestInfo(new BytesReader(CSR_1), false);
+ assertEquals("CN=MIKU.AD.YUUTA.MOE", parsed.getSubject().toString());
+ assertArrayEquals(ObjectIdentifier.OID_RSA_ENCRYPTION,
+ parsed.getSubjectPKInfo().getAlgorithm().getType().getInts());
+ assertEquals(4, parsed.getAttributes().getArray().length);
+ assertEquals(1, parsed.getAttributes().getArray()[3].getValues().getArray().length);
+ }
+
+ @Test
+ void testParseFail() {
+ // No version
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(new Byte[]{
+ 0x30, 0
+ }), false);
+ });
+ // Incorrect version tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(CSR_1, 4, (byte) Int.TAG.getNumber(), 0x3)),
+ false);
+ });
+ // Incorrect version
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(CSR_1, 6, CertificationRequestInfo.VERSION_V1, 1)),
+ false);
+ });
+ // No subject
+ assertThrows(ParseException.class, () -> {
+ Byte[] test = new Byte[5];
+ test[0] = 0x30;
+ test[1] = 3;
+ System.arraycopy(CSR_1, 4, test, 2, 3);
+ new CertificationRequestInfo(new BytesReader(test), false);
+ });
+ // Incorrect subject tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(CSR_1, 7, 0x30, 0x31)),
+ false);
+ });
+ // Incorrect subject pk info tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(CSR_1, 37, 0x30, 0x31)),
+ false);
+ });
+ // Incorrect attributes tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(CSR_1, 199, -96, 0x31)),
+ false);
+ });
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(CSR_1, new CertificationRequestInfo(new BytesReader(CSR_1), false).encodeDER());
+ }
+}
diff --git a/src/test/model/csr/CertificationRequestTest.java b/src/test/model/csr/CertificationRequestTest.java
new file mode 100644
index 0000000..962e90b
--- /dev/null
+++ b/src/test/model/csr/CertificationRequestTest.java
@@ -0,0 +1,114 @@
+package model.csr;
+
+import model.TestConstants;
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.pki.AlgorithmIdentifier;
+import model.pki.SubjectPublicKeyInfo;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CertificationRequestTest {
+ private static final Byte[] CSR_1 = Stream.of(
+ // SEQUENCE (CertificationRequest)
+ Arrays.asList(new Byte[]{ 0x30, -126, 0x02, -102 }),
+ // SEQUENCE (CertificationRequestInfo)
+ Arrays.asList(CertificationRequestInfoTest.CSR_1),
+ // SEQUENCE (AlgorithmIdentifier)
+ Arrays.asList(new Byte[]{
+ 0x30, 0x0D, 0x06, 0x09, 0x2A, -122, 0x48, -122,
+ -9, 0x0D, 0x01, 0x01, 0x0B, 0x05, 0x00
+ }),
+ // BIT STRING (Signature)
+ Arrays.asList(new Byte[]{
+ 0x03, -127, -127,
+ 0x00, 0x6F, 0x61, 0x5C, -25, 0x29, 0x48, 0x3F,
+ -78, 0x1B, -117, 0x2C, -93, -114, 0x7D, -77,
+ 0x62, 0x14, 0x21, 0x4B, -99, 0x74, -95, -93,
+ 0x16, 0x38, 0x31, 0x40, 0x5E, 0x72, -77, -55,
+ 0x6D, -69, 0x19, -108, 0x52, -95, 0x19, -121,
+ -81, -71, 0x74, -123, 0x6B, -27, -20, 0x4C,
+ -126, 0x42, -89, 0x66, 0x6A, 0x52, -34, 0x62,
+ 0x72, 0x40, 0x2C, -79, 0x78, -117, -100, -70,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x7E,
+ })).flatMap(Collection::stream).toArray(Byte[]::new);
+
+ @Test
+ void testConstructor() {
+ final CertificationRequest request = new CertificationRequest(
+ ASN1Object.TAG_SEQUENCE, null,
+ new CertificationRequestInfo(
+ ASN1Object.TAG_SEQUENCE, null,
+ new Int(Int.TAG, null, CertificationRequestInfo.VERSION_V1),
+ TestConstants.NAME_2,
+ new SubjectPublicKeyInfo(ASN1Object.TAG_SEQUENCE, null,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null,
+ 0, new Byte[]{ 1, 2, 3, 4, 5 })),
+ TestConstants.CSR_ATTRS_2),
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null,
+ 0, new Byte[]{ 2, 4, 6, 8, 10 }));
+
+ assertEquals(CertificationRequestInfo.VERSION_V1,
+ request.getCertificationRequestInfo().getVersion().getLong());
+ assertEquals(3,
+ request.getCertificationRequestInfo().getSubject().getRdnSequence().length);
+ assertArrayEquals(ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION,
+ request.getSignatureAlgorithm().getType().getInts());
+ assertArrayEquals(new Byte[]{ 2, 4, 6, 8, 10 },
+ request.getSignature().getConvertedVal());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ final CertificationRequest parsed =
+ new CertificationRequest(new BytesReader(CSR_1), false);
+ assertEquals("CN=MIKU.AD.YUUTA.MOE",
+ parsed.getCertificationRequestInfo().getSubject().toString());
+ assertArrayEquals(ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION,
+ parsed.getSignatureAlgorithm().getType().getInts());
+ }
+
+ @Test
+ void testParseFail() throws ParseException {
+ // Incorrect info tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequest(new BytesReader(mutate(CSR_1, 4, 0x30, 0x31)), false);
+ });
+ // Incorrect algorithm info tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequest(new BytesReader(mutate(CSR_1, 523, 0x30, 0x31)), false);
+ });
+ // Incorrect signature tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequest(new BytesReader(mutate(CSR_1, 538, 0x3, 0x31)), false);
+ });
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(CSR_1, new CertificationRequest(new BytesReader(CSR_1), false).encodeDER());
+ }
+}
diff --git a/src/test/model/csr/ValuesTest.java b/src/test/model/csr/ValuesTest.java
new file mode 100644
index 0000000..93229a4
--- /dev/null
+++ b/src/test/model/csr/ValuesTest.java
@@ -0,0 +1,133 @@
+package model.csr;
+
+import model.asn1.ASN1Object;
+import model.asn1.Null;
+import model.asn1.ObjectIdentifier;
+import model.asn1.PrintableString;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.x501.AttributeTypeAndValue;
+import model.x501.RelativeDistinguishedName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static model.asn1.ASN1Object.TAG_SET;
+import static model.asn1.ObjectIdentifier.OID_C;
+import static model.asn1.ObjectIdentifier.OID_OU;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ValuesTest {
+ // An opaque value of extended key usage block.
+ private ASN1Object extKeyUsage;
+
+ // Example SubjectAlternativeName request attribute.
+ private ASN1Object san;
+
+ private Values values;
+
+ @BeforeEach
+ void setup() throws ParseException {
+ extKeyUsage = ASN1Object.parse(new BytesReader(new Byte[]{
+ 0x30, 0x1F, // SEQUENCE (AttributeValue)
+ 0x30, 0x1D, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x25, // 2.5.29.37 extKeyUsage
+ 0x04, 0x16, // OCTET STRING
+ 0x30, 0x14, // SEQUENCE
+ // 1.3.6.1.5.5.7.3.1 serverAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ // 1.3.6.1.5.5.7.3.2 clientAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
+ }), false);
+
+ san = ASN1Object.parse(new BytesReader(new Byte[]{
+ 0x30, 0x21, // SEQUENCE (AttributeValue)
+ 0x30, 0x1F, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x11, // 2.5.29.17 subjectAltName
+ 0x04, 0x18, // OCTET STRING
+ 0x30, 0x16, // SEQUENCE
+ -126, 0x14, // [2]
+ 0x6C, 0x70, 0x2D, 0x62, 0x32, 0x35, 0x35, 0x2E, 0x61, // lp-b255.yuuta.moe
+ 0x64, 0x2E, 0x79, 0x75, 0x75, 0x74, 0x61, 0x2E, 0x6D, 0x6F, 0x65
+ }), false);
+
+ values = new Values(TAG_SET, null, new ASN1Object[]{
+ extKeyUsage,
+ san
+ });
+ }
+
+ @Test
+ void testConstructor() {
+ assertEquals(0x1F, values.getArray()[0].getLength());
+ assertEquals(0x21, values.getArray()[1].getLength());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertEquals(0x1F, new Values(new BytesReader(new Byte[]{
+ 0x31, 0x21,
+ 0x30, 0x1F, // SEQUENCE (AttributeValue)
+ 0x30, 0x1D, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x25, // 2.5.29.37 extKeyUsage
+ 0x04, 0x16, // OCTET STRING
+ 0x30, 0x14, // SEQUENCE
+ // 1.3.6.1.5.5.7.3.1 serverAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ // 1.3.6.1.5.5.7.3.2 clientAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
+ }), false).getArray()[0].getLength());
+ assertEquals(0x21, new Values(new BytesReader(new Byte[]{
+ 0x31, 0x23,
+ 0x30, 0x21, // SEQUENCE (AttributeValue)
+ 0x30, 0x1F, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x11, // 2.5.29.17 subjectAltName
+ 0x04, 0x18, // OCTET STRING
+ 0x30, 0x16, // SEQUENCE
+ -126, 0x14, // [2]
+ 0x6C, 0x70, 0x2D, 0x62, 0x32, 0x35, 0x35, 0x2E, 0x61, // lp-b255.yuuta.moe
+ 0x64, 0x2E, 0x79, 0x75, 0x75, 0x74, 0x61, 0x2E, 0x6D, 0x6F, 0x65
+ }), false).getArray()[0].getLength());
+ }
+
+ @Test
+ void testParseFail() {
+ // Incorrect length
+ assertThrows(ParseException.class, () -> new Values(new BytesReader(new Byte[]{
+ 0x31, 0x29, // Incorrect length!
+ 0x30, 0x21, // SEQUENCE (AttributeValue)
+ 0x30, 0x1F, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x11, // 2.5.29.17 subjectAltName
+ 0x04, 0x18, // OCTET STRING
+ 0x30, 0x16, // SEQUENCE
+ -126, 0x14, // [2]
+ 0x6C, 0x70, 0x2D, 0x62, 0x32, 0x35, 0x35, 0x2E, 0x61, // lp-b255.yuuta.moe
+ 0x64, 0x2E, 0x79, 0x75, 0x75, 0x74, 0x61, 0x2E, 0x6D, 0x6F, 0x65
+ }), false));
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(new Byte[]{
+ 0x31, 68,
+
+ 0x30, 0x1F, // SEQUENCE (AttributeValue)
+ 0x30, 0x1D, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x25, // 2.5.29.37 extKeyUsage
+ 0x04, 0x16, // OCTET STRING
+ 0x30, 0x14, // SEQUENCE
+ // 1.3.6.1.5.5.7.3.1 serverAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01,
+ // 1.3.6.1.5.5.7.3.2 clientAuth
+ 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02,
+
+ 0x30, 0x21, // SEQUENCE (AttributeValue)
+ 0x30, 0x1F, // SEQUENCE
+ 0x06, 0x03, 0x55, 0x1D, 0x11, // 2.5.29.17 subjectAltName
+ 0x04, 0x18, // OCTET STRING
+ 0x30, 0x16, // SEQUENCE
+ -126, 0x14, // [2]
+ 0x6C, 0x70, 0x2D, 0x62, 0x32, 0x35, 0x35, 0x2E, 0x61, // lp-b255.yuuta.moe
+ 0x64, 0x2E, 0x79, 0x75, 0x75, 0x74, 0x61, 0x2E, 0x6D, 0x6F, 0x65
+ }, values.encodeDER());
+ }
+}
diff --git a/src/test/model/pki/AlgorithmIdentifierTest.java b/src/test/model/pki/AlgorithmIdentifierTest.java
new file mode 100644
index 0000000..8bcc80e
--- /dev/null
+++ b/src/test/model/pki/AlgorithmIdentifierTest.java
@@ -0,0 +1,84 @@
+package model.pki;
+
+import model.asn1.ASN1Object;
+import model.asn1.Null;
+import model.asn1.ObjectIdentifier;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static model.asn1.ObjectIdentifier.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AlgorithmIdentifierTest {
+ @Test
+ void testConstructor() {
+ assertArrayEquals(OID_SHA256_WITH_RSA_ENCRYPTION,
+ new AlgorithmIdentifier(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null))
+ .getType().getInts());
+ assertEquals(Null.TAG.getNumber(),
+ new AlgorithmIdentifier(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null))
+ .getParameters().getTag().getNumber());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ AlgorithmIdentifier parsed = new AlgorithmIdentifier(new BytesReader(new Byte[]{
+ 0x30, 0x0d, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x09, 0x2a, -122, 0x48, -122, -9, 0x0d, // OID (rsaEncryption)
+ 0x01, 0x01, 0x01,
+ 0x05, 0x00, // Null (Parameter)
+ }), false);
+ assertArrayEquals(OID_RSA_ENCRYPTION, parsed.getType().getInts());
+ assertEquals(Null.TAG.getNumber(), parsed.getParameters().getTag().getNumber());
+
+ parsed = new AlgorithmIdentifier(new BytesReader(new Byte[]{
+ 0x30, 0x0D, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x09, 0x2A, -122, 0x48, -122, -9, 0x0D, // OID (sha256WithRsaEncryption)
+ 0x01, 0x01, 0x0B,
+ 0x05, 0x00 // Null (Parameter)
+ }), false);
+ assertArrayEquals(OID_SHA256_WITH_RSA_ENCRYPTION, parsed.getType().getInts());
+ assertEquals(Null.TAG.getNumber(), parsed.getParameters().getTag().getNumber());
+
+ parsed = new AlgorithmIdentifier(new BytesReader(new Byte[]{
+ 0x30, 0x0A, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x08, 0x2A, -122, 0x48, -50, 0x3D, 0x04, 0x03, 0x02 // OID (ecdsaWithSHA256)
+ }), false);
+ assertArrayEquals(OID_ECDSA_WITH_SHA256, parsed.getType().getInts());
+ assertNull(parsed.getParameters());
+ }
+
+ @Test
+ void testParseFail() {
+ // No type
+ assertThrows(ParseException.class, () -> new AlgorithmIdentifier(new BytesReader(new Byte[]{
+ 0x30, 0x0
+ }), false));
+ // Incorrect type tag (should be OID)
+ assertThrows(ParseException.class, () -> new AlgorithmIdentifier(new BytesReader(new Byte[]{
+ 0x30, 0x0B, // SEQUENCE AlgorithmIdentifier
+ 0x07, 0x09, 0x2A, -122, 0x48, -122, -9, 0x0D, // Incorrect tag
+ 0x01, 0x01, 0x0B,
+ }), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(new Byte[]{
+ 0x30, 0x0D, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x09, 0x2A, -122, 0x48, -122, -9, 0x0D, // OID (sha256WithRsaEncryption)
+ 0x01, 0x01, 0x0B,
+ 0x05, 0x00 // Null (Parameter)
+ }, new AlgorithmIdentifier(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)).encodeDER());
+ }
+}
diff --git a/src/test/model/pki/SubjectPublicKeyInfoTest.java b/src/test/model/pki/SubjectPublicKeyInfoTest.java
new file mode 100644
index 0000000..f1cfff0
--- /dev/null
+++ b/src/test/model/pki/SubjectPublicKeyInfoTest.java
@@ -0,0 +1,115 @@
+package model.pki;
+
+import model.asn1.ASN1Object;
+import model.asn1.BitString;
+import model.asn1.Null;
+import model.asn1.ObjectIdentifier;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.csr.CertificationRequestInfo;
+import model.csr.CertificationRequestInfoTest;
+import org.junit.jupiter.api.Test;
+
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class SubjectPublicKeyInfoTest {
+ private static final Byte[] RSA = new Byte[] {
+ 0x30, -127, -97, // SEQUENCE SubjectPublicKeyInfo
+ 0x30, 0x0d, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x09, 0x2a, -122, 0x48, -122, -9, 0x0d, // OID (rsaEncryption)
+ 0x01, 0x01, 0x01,
+ 0x05, 0x00, // Null (Parameter)
+ 0x03, -127, -115, 0x00, 0x30, -127, -119, 0x02, // BIT STRING (subjectPublicKey)
+ -127, -127, 0x00, -67, -1, 0x4e, 0x6d, -22,
+ 0x62, 0x6a, 0x11, -120, 0x77, 0x0a, -92, 0x32,
+ -124, -37, 0x22, 0x2f, 0x3d, 0x5d, 0x2a, 0x63,
+ -71, -109, 0x11, -50, -92, 0x4f, -119, 0x3b,
+ 0x14, 0x3b, -54, 0x3c, -106, -42, 0x11, 0x42,
+ 0x78, -110, 0x68, -100, -25, -25, -50, 0x75,
+ -101, 0x21, 0x41, -34, -31, -85, -13, 0x1e,
+ 0x51, -81, 0x25, 0x4f, -1, 0x56, 0x77, 0x5e,
+ -30, 0x27, -104, 0x34, 0x67, -28, -56, 0x55,
+ 0x6a, 0x3c, 0x6f, -38, -85, -63, 0x5f, 0x16,
+ 0x7a, -93, -19, -35, 0x7f, 0x35, 0x0f, -47,
+ -7, -22, -12, -24, -48, 0x25, 0x6d, -114,
+ 0x66, 0x1a, 0x53, -77, 0x67, 0x32, -69, -39,
+ 0x57, -42, -65, -13, 0x5f, 0x6f, 0x53, 0x6d,
+ 0x62, -95, 0x42, 0x12, 0x7b, 0x13, 0x4f, 0x1a,
+ -26, 0x00, -72, -32, 0x2b, -83, 0x3c, 0x35,
+ -103, 0x18, 0x51, 0x02, 0x03, 0x01, 0x00, 0x01,
+ };
+
+ private static final Byte[] ECC = new Byte[] {
+ 0x30, 0x59, // SEQUENCE SubjectPublicKeyInfo
+ 0x30, 0x13, // SEQUENCE AlgorithmIdentifier
+ 0x06, 0x07, 0x2A, -122, 0x48, -50, 0x3D, 0x02, 0x01, // OID (ecPublicKey)
+ 0x06, 0x08, 0x2A, -122, 0x48, -50, 0x3D, 0x03, 0x01, // OID Parameter (prime256v1)
+ 0x07,
+ 0x03, 0x42, // BIT STRING
+ 0x00, 0x04, 0x1D, -24, 0x71, -68, -35, 0x48, 0x70,
+ 0x26, 0x71, 0x6C, -35, 0x04, 0x5B, 0x3F, 0x5D, -34,
+ 0x14, 0x31, -117, 0x3F, 0x31, -128, 0x18, 0x2A, 0x33,
+ -27, 0x19, -122, 0x13, -42, -25, 0x48, 0x2F, -107,
+ 0x15, 0x3A, 0x59, -115, -19, 0x09, -28, 0x53, 0x1A,
+ -13, 0x61, -78, 0x35, 0x61, 0x6E, 0x66, 0x5F, 0x5F,
+ -49, 0x0A, -30, 0x65, 0x65, 0x3D, 0x22, 0x2B, 0x30,
+ 0x71, 0x2C, 0x24
+ };
+
+ @Test
+ void testConstructor() {
+ assertArrayEquals(ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION,
+ new SubjectPublicKeyInfo(ASN1Object.TAG_SEQUENCE, null,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null,
+ 0, new Byte[]{ 1, 2, 3}))
+ .getAlgorithm().getType().getInts());
+ assertArrayEquals(new Byte[]{ 1, 2, 3 },
+ new SubjectPublicKeyInfo(ASN1Object.TAG_SEQUENCE, null,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null,
+ ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null,
+ 0, new Byte[]{ 1, 2, 3}))
+ .getSubjectPublicKey().getConvertedVal());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ SubjectPublicKeyInfo parsed = new SubjectPublicKeyInfo(new BytesReader(RSA), false);
+ assertArrayEquals(ObjectIdentifier.OID_RSA_ENCRYPTION, parsed.getAlgorithm().getType().getInts());
+ assertEquals(Null.TAG.getNumber(), parsed.getAlgorithm().getParameters().getTag().getNumber());
+ assertEquals(140, parsed.getSubjectPublicKey().getConvertedVal().length);
+
+ parsed = new SubjectPublicKeyInfo(new BytesReader(ECC), false);
+ assertArrayEquals(ObjectIdentifier.OID_EC_PUBLIC_KEY, parsed.getAlgorithm().getType().getInts());
+ assertEquals(ObjectIdentifier.TAG.getNumber(), parsed.getAlgorithm().getParameters().getTag().getNumber());
+ assertArrayEquals(ObjectIdentifier.OID_PRIME256_V1,
+ ((ObjectIdentifier) parsed.getAlgorithm().getParameters()).getInts());
+ assertEquals(65, parsed.getSubjectPublicKey().getConvertedVal().length);
+ }
+
+ @Test
+ void testParseFail() {
+ // No algorithm
+ assertThrows(ParseException.class, () -> {
+ new SubjectPublicKeyInfo(new BytesReader(new Byte[]{
+ 0x30, 0
+ }), false);
+ });
+ // Incorrect algorithm ID tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(RSA, 3, 0x30, 0x31)), false);
+ });
+ // Incorrect public key tag
+ assertThrows(ParseException.class, () -> {
+ new CertificationRequestInfo(new BytesReader(mutate(RSA, 18, BitString.TAG.getNumber(), 0x31)),
+ false);
+ });
+ }
+}
diff --git a/src/test/model/pki/cert/CertificateTest.java b/src/test/model/pki/cert/CertificateTest.java
new file mode 100644
index 0000000..70564fc
--- /dev/null
+++ b/src/test/model/pki/cert/CertificateTest.java
@@ -0,0 +1,81 @@
+package model.pki.cert;
+
+import model.TestConstants;
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.csr.CertificationRequest;
+import model.csr.CertificationRequestInfo;
+import model.csr.CertificationRequestInfoTest;
+import model.pki.AlgorithmIdentifier;
+import model.pki.SubjectPublicKeyInfo;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CertificateTest {
+ @Test
+ void testConstructor() {
+ final Certificate certificate = new Certificate(ASN1Object.TAG_SEQUENCE, null,
+ TestConstants.CERT_GENERATED,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null, 0, new Byte[]{ 1, 2, 3 }));
+
+ assertEquals(TbsCertificate.VERSION_V3,
+ certificate.getCertificate().getVersion().getLong());
+ assertArrayEquals(ObjectIdentifier.OID_RSA_ENCRYPTION,
+ certificate.getSignatureAlgorithm().getType().getInts());
+ assertArrayEquals(new Byte[]{ 1, 2, 3 },
+ certificate.getSignature().getConvertedVal());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ Certificate parsed = new Certificate(new BytesReader(TestConstants.CERT_L2_RSA), false);
+ assertEquals(TbsCertificate.VERSION_V3,
+ parsed.getCertificate().getVersion().getLong());
+ assertArrayEquals(ObjectIdentifier.OID_ECDSA_WITH_SHA512, parsed.getSignatureAlgorithm().getType().getInts());
+ assertNull(parsed.getSignatureAlgorithm().getParameters());
+ assertEquals(70, parsed.getSignature().getVal().length);
+
+ parsed = new Certificate(new BytesReader(TestConstants.CERT_L1_ECC), false);
+ assertEquals(TbsCertificate.VERSION_V3,
+ parsed.getCertificate().getVersion().getLong());
+ assertArrayEquals(ObjectIdentifier.OID_ECDSA_WITH_SHA256, parsed.getSignatureAlgorithm().getType().getInts());
+ assertNull(parsed.getSignatureAlgorithm().getParameters());
+ assertEquals(71, parsed.getSignature().getVal().length);
+ }
+
+ @Test
+ void testParseFail() {
+ // Incorrect certificate tag
+ assertThrows(ParseException.class, () ->
+ new Certificate(new BytesReader(mutate(TestConstants.CERT_L1_ECC, 4, 0x30, 0x31)), false)
+ );
+ // Incorrect signatureAlgorithm tag
+ assertThrows(ParseException.class, () ->
+ new Certificate(new BytesReader(mutate(TestConstants.CERT_L1_ECC, 349, 0x30, 0x31)), false)
+ );
+ // Incorrect signature tag
+ assertThrows(ParseException.class, () ->
+ new Certificate(new BytesReader(mutate(TestConstants.CERT_L1_ECC, 361, 0x3, 0x5)), false)
+ );
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(TestConstants.CERT_V1,
+ new Certificate(new BytesReader(TestConstants.CERT_V1), false).encodeDER());
+ assertArrayEquals(TestConstants.CERT_L1_ECC,
+ new Certificate(new BytesReader(TestConstants.CERT_L1_ECC), false).encodeDER());
+ assertArrayEquals(TestConstants.CERT_L2_RSA,
+ new Certificate(new BytesReader(TestConstants.CERT_L2_RSA), false).encodeDER());
+ }
+}
diff --git a/src/test/model/pki/cert/ExtensionTest.java b/src/test/model/pki/cert/ExtensionTest.java
new file mode 100644
index 0000000..06561ba
--- /dev/null
+++ b/src/test/model/pki/cert/ExtensionTest.java
@@ -0,0 +1,118 @@
+package model.pki.cert;
+
+import model.asn1.ASN1Object;
+import model.asn1.Bool;
+import model.asn1.ObjectIdentifier;
+import model.asn1.OctetString;
+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 ExtensionTest {
+ static final Byte[] EXT_SUBJECT_KEY_ID = new Byte[] {
+ 0x30, 0x1D, // SEQUENCE Extension
+ 0x06, 0x03, 0x55, 0x1D, 0x0E, // OID subjectKeyIdentifier
+ 0x04, 0x16, // OCTET STRING
+ 0x04, 0x14, -79, -62, -89, -127, 0x63, 0x66,
+ 0x4B, 0x72, 0x0A, -35, -3, 0x7D, 0x20, 0x29,
+ -67, 0x6B, 0x49, 0x09, 0x61, -64
+ };
+
+ static final Byte[] EXT_KEY_USAGE = new Byte[] {
+ 0x30, 0x0E, // SEQUENCE Extension
+ 0x06, 0x03, 0x55, 0x1D, 0x0F, // OID keyUsage
+ 0x01, 0x01, -1, // BOOLEAN critical
+ 0x04, 0x04, // OCTET STRING
+ 0x03, 0x02, 0x01, -122
+ };
+
+ @Test
+ void testConstructor() throws ParseException {
+ final Extension ext = new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_BASIC_CONSTRAINTS),
+ new Bool(Bool.TAG, null, true),
+ new OctetString(OctetString.TAG, null, new Byte[]{ 0x30, 0x03, 0x01, 0x01, -1 }));
+ assertArrayEquals(ObjectIdentifier.OID_BASIC_CONSTRAINTS, ext.getExtnId().getInts());
+ assertTrue(ext.getCritical().getValue());
+ assertArrayEquals(new Byte[]{ 0x30, 0x03, 0x01, 0x01, -1 }, ext.getExtnValue().getBytes());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ Extension parsed = new Extension(new BytesReader(EXT_SUBJECT_KEY_ID), false);
+ assertArrayEquals(ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER, parsed.getExtnId().getInts());
+ assertNull(parsed.getCritical());
+ assertArrayEquals(new Byte[] {
+ 0x04, 0x14, -79, -62, -89, -127, 0x63, 0x66,
+ 0x4B, 0x72, 0x0A, -35, -3, 0x7D, 0x20, 0x29,
+ -67, 0x6B, 0x49, 0x09, 0x61, -64
+ }, parsed.getExtnValue().getBytes());
+
+ parsed = new Extension(new BytesReader(EXT_KEY_USAGE), false);
+ assertArrayEquals(ObjectIdentifier.OID_KEY_USAGE, parsed.getExtnId().getInts());
+ assertTrue(parsed.getCritical().getValue());
+ assertArrayEquals(new Byte[] {
+ 0x03, 0x02, 0x01, -122
+ }, parsed.getExtnValue().getBytes());
+ }
+
+ @Test
+ void testParseFail() throws ParseException {
+ // Too short (no ID)
+ assertThrows(ParseException.class, () -> new Extension(new BytesReader(new Byte[]{
+ 0x30, 0x00
+ }), false));
+ // Wrong ID tag
+ assertThrows(ParseException.class, () -> new Extension(new BytesReader(new Byte[]{
+ 0x30, 0x0E, // SEQUENCE Extension
+ 0x07, 0x03, 0x55, 0x1D, 0x0F, // OID keyUsage
+ 0x01, 0x01, -1, // BOOLEAN critical
+ 0x04, 0x04, // OCTET STRING
+ 0x03, 0x02, 0x01, -122
+ }), false));
+ // Wrong critical tag (neither bool nor sequence)
+ assertThrows(ParseException.class, () -> new Extension(new BytesReader(new Byte[]{
+ 0x30, 0x0E, // SEQUENCE Extension
+ 0x06, 0x03, 0x55, 0x1D, 0x0F, // OID keyUsage
+ 0x05, 0x01, -1, // BOOLEAN critical
+ 0x04, 0x04, // OCTET STRING
+ 0x03, 0x02, 0x01, -122
+ }), false));
+ // Critical and wrong value tag
+ assertThrows(ParseException.class, () -> new Extension(new BytesReader(new Byte[]{
+ 0x30, 0x0E, // SEQUENCE Extension
+ 0x06, 0x03, 0x55, 0x1D, 0x0F, // OID keyUsage
+ 0x01, 0x01, -1, // BOOLEAN critical
+ 0x09, 0x04, // OCTET STRING
+ 0x03, 0x02, 0x01, -122
+ }), false));
+
+ // No critical and wrong value tag
+ assertThrows(ParseException.class, () -> new Extension(new BytesReader(new Byte[]{
+ 0x30, 0x0B, // SEQUENCE Extension
+ 0x06, 0x03, 0x55, 0x1D, 0x0F, // OID keyUsage
+ 0x09, 0x04, // OCTET STRING
+ 0x03, 0x02, 0x01, -122
+ }), false));
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(EXT_SUBJECT_KEY_ID, new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER),
+ null,
+ new OctetString(OctetString.TAG, null, new Byte[] {
+ 0x04, 0x14, -79, -62, -89, -127, 0x63, 0x66,
+ 0x4B, 0x72, 0x0A, -35, -3, 0x7D, 0x20, 0x29,
+ -67, 0x6B, 0x49, 0x09, 0x61, -64
+ })).encodeDER());
+ assertArrayEquals(EXT_KEY_USAGE, new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_KEY_USAGE),
+ new Bool(Bool.TAG, null, true),
+ new OctetString(OctetString.TAG, null, new Byte[] {
+ 0x03, 0x02, 0x01, -122
+ })).encodeDER());
+ }
+}
diff --git a/src/test/model/pki/cert/ExtensionsTest.java b/src/test/model/pki/cert/ExtensionsTest.java
new file mode 100644
index 0000000..e50b3e6
--- /dev/null
+++ b/src/test/model/pki/cert/ExtensionsTest.java
@@ -0,0 +1,112 @@
+package model.pki.cert;
+
+import model.asn1.ASN1Object;
+import model.asn1.Bool;
+import model.asn1.ObjectIdentifier;
+import model.asn1.OctetString;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ExtensionsTest {
+ @Test
+ void testConstructor() {
+ final Extension ext1 = new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_BASIC_CONSTRAINTS),
+ new Bool(Bool.TAG, null, true),
+ new OctetString(OctetString.TAG, null, new Byte[]{0x30, 0x03, 0x01, 0x01, -1}));
+ final Extension ext2 = new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER),
+ null,
+ new OctetString(OctetString.TAG, null, new Byte[]{
+ 0x04, 0x14, -79, -62, -89, -127, 0x63, 0x66,
+ 0x4B, 0x72, 0x0A, -35, -3, 0x7D, 0x20, 0x29,
+ -67, 0x6B, 0x49, 0x09, 0x61, -64
+ }));
+ final Extensions extensions = new Extensions(ASN1Object.TAG_SEQUENCE, null, new Extension[]{
+ ext1, ext2
+ });
+
+ assertEquals(2, extensions.getExtensions().length);
+ assertArrayEquals(ObjectIdentifier.OID_BASIC_CONSTRAINTS, extensions.getExtensions()[0].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER, extensions.getExtensions()[1].getExtnId().getInts());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ final Extensions parsed = new Extensions(new BytesReader(
+ Stream.of(Arrays.asList(new Byte[]{0x30,
+ (byte) (ExtensionTest.EXT_KEY_USAGE.length + ExtensionTest.EXT_SUBJECT_KEY_ID.length)}),
+ Arrays.asList(ExtensionTest.EXT_KEY_USAGE),
+ Arrays.asList(ExtensionTest.EXT_SUBJECT_KEY_ID))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new)), false);
+ assertArrayEquals(ObjectIdentifier.OID_KEY_USAGE, parsed.getExtensions()[0].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER, parsed.getExtensions()[1].getExtnId().getInts());
+ }
+
+ @Test
+ void testParseFail() {
+ assertThrows(ParseException.class, () -> {
+ new Extensions(new BytesReader(new Byte[]{0x30, 0x1}), false);
+ });
+ assertThrows(ParseException.class, () -> {
+ Byte[] bytes =
+ Stream.of(Arrays.asList(new Byte[]{0x30,
+ (byte) (ExtensionTest.EXT_KEY_USAGE.length + ExtensionTest.EXT_SUBJECT_KEY_ID.length)}),
+ Arrays.asList(ExtensionTest.EXT_KEY_USAGE),
+ Arrays.asList(ExtensionTest.EXT_SUBJECT_KEY_ID))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ assertEquals((byte) 0x30, bytes[2]);
+ bytes[2] = 0x31;
+ new Extensions(new BytesReader(bytes), false);
+ });
+ assertThrows(ParseException.class, () -> {
+ Byte[] bytes =
+ Stream.of(Arrays.asList(new Byte[]{0x30,
+ (byte) (ExtensionTest.EXT_KEY_USAGE.length + ExtensionTest.EXT_SUBJECT_KEY_ID.length)}),
+ Arrays.asList(ExtensionTest.EXT_KEY_USAGE),
+ Arrays.asList(ExtensionTest.EXT_SUBJECT_KEY_ID))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new);
+ assertEquals((byte) 0x30, bytes[2 + ExtensionTest.EXT_KEY_USAGE.length]);
+ bytes[2 + ExtensionTest.EXT_KEY_USAGE.length] = 0x31;
+ new Extensions(new BytesReader(bytes), false);
+ });
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(
+ Stream.of(Arrays.asList(new Byte[]{0x30,
+ (byte) (ExtensionTest.EXT_KEY_USAGE.length +
+ ExtensionTest.EXT_SUBJECT_KEY_ID.length)}),
+ Arrays.asList(ExtensionTest.EXT_SUBJECT_KEY_ID),
+ Arrays.asList(ExtensionTest.EXT_KEY_USAGE))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new),
+ new Extensions(ASN1Object.TAG_SEQUENCE, null, new Extension[]{
+ new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER),
+ null,
+ new OctetString(OctetString.TAG, null, new Byte[]{
+ 0x04, 0x14, -79, -62, -89, -127, 0x63, 0x66,
+ 0x4B, 0x72, 0x0A, -35, -3, 0x7D, 0x20, 0x29,
+ -67, 0x6B, 0x49, 0x09, 0x61, -64
+ })),
+ new Extension(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_KEY_USAGE),
+ new Bool(Bool.TAG, null, true),
+ new OctetString(OctetString.TAG, null, new Byte[]{
+ 0x03, 0x02, 0x01, -122
+ }))
+ }).encodeDER());
+ }
+}
diff --git a/src/test/model/pki/cert/TbsCertificateTest.java b/src/test/model/pki/cert/TbsCertificateTest.java
new file mode 100644
index 0000000..ae92ace
--- /dev/null
+++ b/src/test/model/pki/cert/TbsCertificateTest.java
@@ -0,0 +1,184 @@
+package model.pki.cert;
+
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.TestConstants;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TbsCertificateTest {
+ @Test
+ void testConstructor() {
+ assertEquals(TbsCertificate.VERSION_V3, TestConstants.CERT_GENERATED.getVersion().getLong());
+ assertEquals(100, TestConstants.CERT_GENERATED.getSerialNumber().getLong());
+ assertArrayEquals(ObjectIdentifier.OID_RSA_ENCRYPTION, TestConstants.CERT_GENERATED.getSignature().getType().getInts());
+ assertEquals("CN=Test CA,C=CA", TestConstants.CERT_GENERATED.getIssuer().toString());
+ assertEquals(TestConstants.NOW, TestConstants.CERT_GENERATED.getValidity().getNotBefore().getTimestamp());
+ assertEquals(TestConstants.NOW.plusYears(1),
+ TestConstants.CERT_GENERATED.getValidity().getNotAfter().getTimestamp());
+ assertEquals("CN=Yuuta Liang,C=CA", TestConstants.CERT_GENERATED.getSubject().toString());
+ assertArrayEquals(ObjectIdentifier.OID_EC_PUBLIC_KEY,
+ TestConstants.CERT_GENERATED.getSubjectPublicKeyInfo().getAlgorithm().getType().getInts());
+ assertEquals(2, TestConstants.CERT_GENERATED.getExtensions().getExtensions().length);
+ assertArrayEquals(ObjectIdentifier.OID_BASIC_CONSTRAINTS,
+ TestConstants.CERT_GENERATED.getExtensions().getExtensions()[0].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_KEY_USAGE,
+ TestConstants.CERT_GENERATED.getExtensions().getExtensions()[1].getExtnId().getInts());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ TbsCertificate parsed = new TbsCertificate(new BytesReader(trimToTbs(TestConstants.CERT_L1_ECC)),
+ false);
+ assertEquals(TbsCertificate.VERSION_V3, parsed.getVersion().getLong());
+ assertEquals(0, parsed.getSerialNumber().getValue()
+ .compareTo(new BigInteger("644983544608556543477205958886697401602227090424")));
+ assertArrayEquals(ObjectIdentifier.OID_ECDSA_WITH_SHA256,
+ parsed.getSignature().getType().getInts());
+ assertNull(parsed.getSignature().getParameters());
+ assertEquals("CN=Yuuta Root CA,C=CA", parsed.getIssuer().toString());
+ assertEquals(ZonedDateTime.of(2023, 6, 23,
+ 2, 50, 46, 0, ZoneId.of("UTC")),
+ parsed.getValidity().getNotBefore().getTimestamp());
+ assertEquals(ZonedDateTime.of(2048, 6, 23,
+ 2, 50, 46, 0, ZoneId.of("UTC")),
+ parsed.getValidity().getNotAfter().getTimestamp());
+ assertEquals("CN=Yuuta Root CA,C=CA", parsed.getSubject().toString());
+ assertArrayEquals(ObjectIdentifier.OID_EC_PUBLIC_KEY,
+ parsed.getSubjectPublicKeyInfo().getAlgorithm().getType().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_PRIME256_V1,
+ ((ObjectIdentifier) parsed.getSubjectPublicKeyInfo().getAlgorithm().getParameters()).getInts());
+ assertEquals(4, parsed.getExtensions().getExtensions().length);
+ assertArrayEquals(ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER,
+ parsed.getExtensions().getExtensions()[0].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_AUTHORITY_KEY_IDENTIFIER,
+ parsed.getExtensions().getExtensions()[1].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_BASIC_CONSTRAINTS,
+ parsed.getExtensions().getExtensions()[2].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_KEY_USAGE,
+ parsed.getExtensions().getExtensions()[3].getExtnId().getInts());
+
+ parsed = new TbsCertificate(
+ new BytesReader(trimToTbs(TestConstants.CERT_L2_RSA)),
+ false);
+ assertEquals(TbsCertificate.VERSION_V3, parsed.getVersion().getLong());
+ assertEquals(0, parsed.getSerialNumber().getValue()
+ .compareTo(new BigInteger("354327098948136693059815576591331472151989570311")));
+ assertArrayEquals(ObjectIdentifier.OID_ECDSA_WITH_SHA512,
+ parsed.getSignature().getType().getInts());
+ assertNull(parsed.getSignature().getParameters());
+ assertEquals("CN=Yuuta Root CA,C=CA", parsed.getIssuer().toString());
+ assertEquals(ZonedDateTime.of(2023, 6, 24,
+ 0, 15, 22, 0, ZoneId.of("UTC")),
+ parsed.getValidity().getNotBefore().getTimestamp());
+ assertEquals(ZonedDateTime.of(2033, 6, 21,
+ 0, 15, 22, 0, ZoneId.of("UTC")),
+ parsed.getValidity().getNotAfter().getTimestamp());
+ assertEquals("DC=MOE,DC=YUUTA,DC=AD,CN=Yuuta Home Issuing CA", parsed.getSubject().toString());
+ assertArrayEquals(ObjectIdentifier.OID_RSA_ENCRYPTION,
+ parsed.getSubjectPublicKeyInfo().getAlgorithm().getType().getInts());
+ assertEquals(Null.TAG.getNumber(),
+ parsed.getSubjectPublicKeyInfo().getAlgorithm().getParameters().getTag().getNumber());
+ assertEquals(526, parsed.getSubjectPublicKeyInfo().getSubjectPublicKey().getVal().length);
+ assertEquals(6, parsed.getExtensions().getExtensions().length);
+ assertArrayEquals(ObjectIdentifier.OID_SUBJECT_KEY_IDENTIFIER,
+ parsed.getExtensions().getExtensions()[0].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_AUTHORITY_KEY_IDENTIFIER,
+ parsed.getExtensions().getExtensions()[1].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_BASIC_CONSTRAINTS,
+ parsed.getExtensions().getExtensions()[2].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_KEY_USAGE,
+ parsed.getExtensions().getExtensions()[3].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_CRL_DISTRIBUTION_POINTS,
+ parsed.getExtensions().getExtensions()[4].getExtnId().getInts());
+ assertArrayEquals(ObjectIdentifier.OID_AUTHORITY_INFO_ACCESS,
+ parsed.getExtensions().getExtensions()[5].getExtnId().getInts());
+
+ parsed = new TbsCertificate(
+ new BytesReader(Arrays.stream(TestConstants.CERT_V1).skip(4).toArray(Byte[]::new)),
+ false);
+ assertNull(parsed.getVersion());
+ assertNull(parsed.getExtensions());
+ }
+
+ @Test
+ void testParseFail() throws ParseException {
+ final Byte[] in = trimToTbs(TestConstants.CERT_L2_RSA);
+ // Wrong version parent tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 4, -96, 2)), false));
+ // Wrong version inner tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 6, 0x2, 3)), false));
+ // Wrong serial number tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 9, 0x2, 3)), false));
+ // Wrong signature tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 31, 0x30, 3)), false));
+ // Wrong issuer tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 43, 0x30, 0x31)), false));
+ // Wrong validity tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 82, 0x30, 0x31)), false));
+ // Wrong subject tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 114, 0x30, 0x31)), false));
+ // Wrong subject public key info tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 212, 0x30, 0x31)), false));
+ // Wrong extensions parent tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 762, -93, 0x31)), false));
+ // Wrong extensions inner tag
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 765, 0x30, 0x31)), false));
+ // Extensions exist, but wrong version
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 8, 0x2, TbsCertificate.VERSION_V2)),
+ false));
+ // Totally wrong version
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(mutate(in, 8, 0x2, TbsCertificate.VERSION_V3 + 1)),
+ false));
+
+ // Extensions exist, but no version
+ final TbsCertificate certV1 = new TbsCertificate(new BytesReader(trimToTbs(TestConstants.CERT_V1)), false);
+ final TbsCertificate certV3 = new TbsCertificate(new BytesReader(trimToTbs(TestConstants.CERT_L2_RSA)), false);
+ Byte[] wrongCert = new TbsCertificate(ASN1Object.TAG_SEQUENCE, null,
+ null,
+ certV1.getSerialNumber(),
+ certV1.getSignature(),
+ certV1.getIssuer(),
+ certV1.getValidity(),
+ certV1.getSubject(),
+ certV1.getSubjectPublicKeyInfo(),
+ certV3.getExtensions())
+ .encodeDER();
+ assertThrows(ParseException.class, () ->
+ new TbsCertificate(new BytesReader(wrongCert), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ Byte[] in = trimToTbs(TestConstants.CERT_L1_ECC);
+ assertArrayEquals(Arrays.copyOfRange(in, 0, 345), new TbsCertificate(new BytesReader(in), false).encodeDER());
+ in = trimToTbs(TestConstants.CERT_L2_RSA);
+ assertArrayEquals(Arrays.copyOfRange(in, 0, 989), new TbsCertificate(new BytesReader(in), false).encodeDER());
+ in = trimToTbs(TestConstants.CERT_V1);
+ assertArrayEquals(Arrays.copyOfRange(in, 0, 583), new TbsCertificate(new BytesReader(in), false).encodeDER());
+ }
+
+ private static Byte[] trimToTbs(Byte[] in) {
+ return Arrays.stream(in).skip(4).toArray(Byte[]::new);
+ }
+}
diff --git a/src/test/model/pki/cert/ValidityTest.java b/src/test/model/pki/cert/ValidityTest.java
new file mode 100644
index 0000000..eba5092
--- /dev/null
+++ b/src/test/model/pki/cert/ValidityTest.java
@@ -0,0 +1,118 @@
+package model.pki.cert;
+
+import jdk.jshell.EvalException;
+import 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.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Stream;
+
+import static model.TestConstants.combine;
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ValidityTest {
+ private ZonedDateTime now;
+
+ @BeforeEach
+ void setup() {
+ now = ZonedDateTime.now(ZoneId.of("UTC")).withNano(0);
+ }
+
+ @Test
+ void testConstructor() {
+ final ASN1Time time = new GeneralizedTime(GeneralizedTime.TAG, null, now);
+ assertEquals(time.getTimestamp(), new Validity(ASN1Object.TAG_SEQUENCE, null, time, time)
+ .getNotBefore().getTimestamp());
+ assertEquals(time.getTimestamp(), new Validity(ASN1Object.TAG_SEQUENCE, null, time, time)
+ .getNotAfter().getTimestamp());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ final ASN1Time utc = new UtcTime(UtcTime.TAG, null, now);
+ final ASN1Time gen = new GeneralizedTime(GeneralizedTime.TAG, null, now);
+ final Byte[] utcBytes = utc.encodeDER();
+ final Byte[] genBytes = gen.encodeDER();
+
+ // UTC, Generalized
+ Validity parsed = new Validity(new BytesReader(combine((byte) ASN1Object.TAG_SEQUENCE.getNumber(), utcBytes,
+ genBytes)), false);
+ assertEquals(UtcTime.TAG.getNumber(), parsed.getNotBefore().getTag().getNumber());
+ assertEquals(now, parsed.getNotBefore().getTimestamp());
+ assertEquals(GeneralizedTime.TAG.getNumber(), parsed.getNotAfter().getTag().getNumber());
+ assertEquals(now, parsed.getNotAfter().getTimestamp());
+
+ // UTC, UTC
+ parsed = new Validity(new BytesReader(combine((byte) ASN1Object.TAG_SEQUENCE.getNumber(), utcBytes, utcBytes)),
+ false);
+ assertEquals(UtcTime.TAG.getNumber(), parsed.getNotBefore().getTag().getNumber());
+ assertEquals(now, parsed.getNotBefore().getTimestamp());
+ assertEquals(UtcTime.TAG.getNumber(), parsed.getNotAfter().getTag().getNumber());
+ assertEquals(now, parsed.getNotAfter().getTimestamp());
+
+ // Generalized, Generalized
+ parsed = new Validity(new BytesReader(combine((byte) ASN1Object.TAG_SEQUENCE.getNumber(), genBytes, genBytes)),
+ false);
+ assertEquals(GeneralizedTime.TAG.getNumber(), parsed.getNotBefore().getTag().getNumber());
+ assertEquals(now, parsed.getNotBefore().getTimestamp());
+ assertEquals(GeneralizedTime.TAG.getNumber(), parsed.getNotAfter().getTag().getNumber());
+ assertEquals(now, parsed.getNotAfter().getTimestamp());
+
+ // Generalized, UTC
+ parsed = new Validity(new BytesReader(combine((byte) ASN1Object.TAG_SEQUENCE.getNumber(), genBytes, utcBytes)),
+ false);
+ assertEquals(GeneralizedTime.TAG.getNumber(), parsed.getNotBefore().getTag().getNumber());
+ assertEquals(now, parsed.getNotBefore().getTimestamp());
+ assertEquals(UtcTime.TAG.getNumber(), parsed.getNotAfter().getTag().getNumber());
+ assertEquals(now, parsed.getNotAfter().getTimestamp());
+ }
+
+ @Test
+ void testParseFail() {
+ final ASN1Time utc = new UtcTime(UtcTime.TAG, null, now);
+ final Byte[] utcBytes = utc.encodeDER();
+
+ // Too short
+ assertThrows(ParseException.class, () ->
+ new Validity(new BytesReader(new Byte[] {
+ 0x30, 0x0
+ }), false));
+ assertThrows(ParseException.class, () -> {
+ new Validity(new BytesReader(combine((byte) 0x30, utcBytes)), false);
+ });
+
+ // Illegal notBefore tag
+ assertThrows(ParseException.class, () -> {
+ new Validity(new BytesReader(mutate(combine((byte) 0x30, utcBytes, utcBytes), 2,
+ UtcTime.TAG.getNumber(), 0x2)), false);
+ });
+ // Illegal notAfter tag
+ assertThrows(ParseException.class, () -> {
+ new Validity(new BytesReader(mutate(combine((byte) 0x30, utcBytes, utcBytes), utcBytes.length + 2,
+ UtcTime.TAG.getNumber(), 0x2)), false);
+ });
+ }
+
+ @Test
+ void testEncode() {
+ final ASN1Time utc = new UtcTime(UtcTime.TAG, null, now);
+ final ASN1Time gen = new GeneralizedTime(GeneralizedTime.TAG, null, now);
+ final Byte[] utcBytes = utc.encodeDER();
+ final Byte[] genBytes = gen.encodeDER();
+
+ assertArrayEquals(Stream.of(Arrays.asList(new Byte[]{ 0x30, (byte) (utcBytes.length + genBytes.length) }),
+ Arrays.asList(utcBytes),
+ Arrays.asList(genBytes))
+ .flatMap(Collection::stream)
+ .toArray(Byte[]::new),
+ new Validity(ASN1Object.TAG_SEQUENCE, null, utc, gen)
+ .encodeDER());
+ }
+}
diff --git a/src/test/model/pki/crl/CertificateListContentTest.java b/src/test/model/pki/crl/CertificateListContentTest.java
new file mode 100644
index 0000000..ec18629
--- /dev/null
+++ b/src/test/model/pki/crl/CertificateListContentTest.java
@@ -0,0 +1,37 @@
+package model.pki.crl;
+
+import model.TestConstants;
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.pki.AlgorithmIdentifier;
+import model.x501.AttributeTypeAndValue;
+import model.x501.Name;
+import model.x501.RelativeDistinguishedName;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import static model.TestConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CertificateListContentTest {
+ @Test
+ void testConstructor() {
+ assertEquals(1, CRL_CONTENT_1.getVersion().getLong());
+ assertEquals("CN=Test CA", CRL_CONTENT_1.getIssuer().toString());
+ assertArrayEquals(ObjectIdentifier.OID_SHA256_WITH_RSA_ENCRYPTION,
+ CRL_CONTENT_1.getSignature().getType().getInts());
+ assertEquals(GeneralizedTime.TAG.getNumber(),
+ CRL_CONTENT_1.getThisUpdate().getTag().getNumber());
+ assertNull(CRL_CONTENT_1.getNextUpdate());
+ assertEquals(2, CRL_CONTENT_1.getRevokedCertificates().length);
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(CRL_CONTENT_1_DER, CRL_CONTENT_1.encodeDER());
+ assertArrayEquals(CRL_CONTENT_2_DER, CRL_CONTENT_2.encodeDER());
+ }
+}
diff --git a/src/test/model/pki/crl/CertificateListTest.java b/src/test/model/pki/crl/CertificateListTest.java
new file mode 100644
index 0000000..0f4f06c
--- /dev/null
+++ b/src/test/model/pki/crl/CertificateListTest.java
@@ -0,0 +1,52 @@
+package model.pki.crl;
+
+import model.TestConstants;
+import model.asn1.ASN1Object;
+import model.asn1.BitString;
+import model.asn1.Null;
+import model.asn1.ObjectIdentifier;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.pki.AlgorithmIdentifier;
+import model.pki.cert.Certificate;
+import model.pki.cert.TbsCertificate;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static model.TestConstants.combine;
+import static model.TestConstants.mutate;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class CertificateListTest {
+ private CertificateList crl;
+
+ @BeforeEach
+ void setup() {
+ crl = new CertificateList(ASN1Object.TAG_SEQUENCE, null,
+ TestConstants.CRL_CONTENT_1,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)),
+ new BitString(BitString.TAG, null, 0, new Byte[]{ 1, 2, 3 }));
+ }
+
+ @Test
+ void testConstructor() {
+ assertEquals(2, crl.getCrl().getRevokedCertificates().length);
+ assertArrayEquals(ObjectIdentifier.OID_RSA_ENCRYPTION,
+ crl.getSignatureAlgorithm().getType().getInts());
+ assertArrayEquals(new Byte[]{ 1, 2, 3 },
+ crl.getSignature().getConvertedVal());
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(combine((byte) 0x30,
+ TestConstants.CRL_CONTENT_1_DER,
+ new AlgorithmIdentifier(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, ObjectIdentifier.OID_RSA_ENCRYPTION),
+ new Null(Null.TAG, null)).encodeDER(),
+ new BitString(BitString.TAG, null, 0, new Byte[]{ 1, 2, 3 }).encodeDER()),
+ crl.encodeDER());
+ }
+}
diff --git a/src/test/model/pki/crl/RevokedCertificateTest.java b/src/test/model/pki/crl/RevokedCertificateTest.java
new file mode 100644
index 0000000..659e421
--- /dev/null
+++ b/src/test/model/pki/crl/RevokedCertificateTest.java
@@ -0,0 +1,25 @@
+package model.pki.crl;
+
+import model.asn1.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import static model.TestConstants.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class RevokedCertificateTest {
+ @Test
+ void testConstructor() {
+ assertEquals(123, REVOKED_CESSATION.getSerialNumber().getLong());
+ assertEquals(UtcTime.TAG.getNumber(), REVOKED_CESSATION.getRevocationDate().getTag().getNumber());
+ assertEquals(Reason.CESSATION_OF_OPERATION, REVOKED_CESSATION.getReason());
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(REVOKED_KEY_COMPROMISE_DER, REVOKED_KEY_COMPROMISE.encodeDER());
+ }
+}
diff --git a/src/test/model/x501/AttributeTypeAndValueTest.java b/src/test/model/x501/AttributeTypeAndValueTest.java
new file mode 100644
index 0000000..ea9c17e
--- /dev/null
+++ b/src/test/model/x501/AttributeTypeAndValueTest.java
@@ -0,0 +1,90 @@
+package model.x501;
+
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import org.junit.jupiter.api.Test;
+
+import static model.asn1.ObjectIdentifier.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AttributeTypeAndValueTest {
+ @Test
+ void testConstructor() throws ParseException {
+ assertArrayEquals(OID_OU,
+ new AttributeTypeAndValue(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_OU),
+ new Null(Null.TAG, null))
+ .getType().getInts());
+ assertEquals("123",
+ new AttributeTypeAndValue(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_OU),
+ new PrintableString(PrintableString.TAG, null, "123"))
+ .getValue().toString());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ // C = IT
+ assertArrayEquals(OID_C, new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x49, 0x54
+ }), false).getType().getInts());
+ assertEquals("IT", ((PrintableString) new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x49, 0x54
+ }), false).getValue()).getString());
+
+ // CN = Test ed25519
+ assertArrayEquals(OID_CN, new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C,
+ 0x0C, 0x54, 0x65, 0x73, 0x74, 0x20, 0x65, 0x64,
+ 0x32, 0x35, 0x35, 0x31, 0x39
+ }), false).getType().getInts());
+ assertEquals("Test ed25519", ((ASN1String) new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C,
+ 0x0C, 0x54, 0x65, 0x73, 0x74, 0x20, 0x65, 0x64,
+ 0x32, 0x35, 0x35, 0x31, 0x39
+ }), false).getValue()).getString());
+ }
+
+ @Test
+ void testParseFail() {
+ // No type
+ assertThrows(ParseException.class, () -> new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x0
+ }), false));
+ // No value
+ assertThrows(ParseException.class, () -> new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x5, 0x6, 0x3, 0x55, 0x4, 0x6
+ }), false));
+ // Incorrect type tag (should be OID)
+ assertThrows(ParseException.class, () -> new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x9, 0x7, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x49, 0x54
+ }), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(new Byte[]{
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C,
+ 0x0C, 0x54, 0x65, 0x73, 0x74, 0x20, 0x65, 0x64,
+ 0x32, 0x35, 0x35, 0x31, 0x39
+ }, new AttributeTypeAndValue(ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_CN),
+ new UTF8String(UTF8String.TAG, null, "Test ed25519"))
+ .encodeDER());
+ }
+
+ @Test
+ void testToString() throws ParseException {
+ assertEquals("CN=Test ed25519", new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C,
+ 0x0C, 0x54, 0x65, 0x73, 0x74, 0x20, 0x65, 0x64,
+ 0x32, 0x35, 0x35, 0x31, 0x39
+ }), false).toString());
+ assertEquals("C=IT", new AttributeTypeAndValue(new BytesReader(new Byte[]{
+ 0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x49, 0x54
+ }), false).toString());
+ }
+}
diff --git a/src/test/model/x501/NameTest.java b/src/test/model/x501/NameTest.java
new file mode 100644
index 0000000..c649798
--- /dev/null
+++ b/src/test/model/x501/NameTest.java
@@ -0,0 +1,167 @@
+package model.x501;
+
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.TestConstants;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class NameTest {
+ @Test
+ void testConstructor() {
+ assertEquals("users",
+ TestConstants.NAME_1.getRdnSequence()[1].getArray()[0].getValue().toString());
+ assertEquals("CN",
+ TestConstants.NAME_2.getRdnSequence()[2].getArray()[0].getValue().toString());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertEquals("CA", new Name(new BytesReader(new Byte[]{
+ 0x30, 45, // Name
+
+ 0x31, 14, // RDN[0]
+ 0x30, 12, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x03, // CN
+ 0x13, 5, 'y', 'u', 'u', 't', 'a', // yuuta
+
+ 0x31, 14, // RDN[1]
+ 0x30, 12, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xB, // OU
+ 0x13, 5, 'u', 's', 'e', 'r', 's', // users
+
+ 0x31, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'A' // CA
+ }), false).getRdnSequence()[2].getArray()[0].getValue().toString());
+
+ assertEquals("SN=Qwq", new Name(new BytesReader(new Byte[]{
+ 0x30, 38, // Name
+
+ 0x31, 12, // RDN[0]
+ 0x30, 10, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x04, // CN
+ 0x13, 3, 'Q', 'w', 'q', // Qwq
+
+ 0x31, 9, // RDN[1]
+ 0x30, 7, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xA, // O
+ 0x13, 2, 'I', 'T', // IT
+
+ 0x31, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'N' // CN
+ }), false).getRdnSequence()[0].toString());
+ }
+
+ @Test
+ void testParseFail() {
+ assertThrows(ParseException.class, () -> new Name(new BytesReader(new Byte[]{
+ 0x30, 38, // Name
+
+ // Wrong tag here
+ 0x30, 12, // RDN[0]
+ 0x30, 10, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x04, // CN
+ 0x13, 3, 'Q', 'w', 'q', // Qwq
+
+ 0x31, 9, // RDN[1]
+ 0x30, 7, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xA, // O
+ 0x13, 2, 'I', 'T', // IT
+
+ 0x31, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'N' // CN
+ }), false));
+ assertThrows(ParseException.class, () -> new Name(new BytesReader(new Byte[]{
+ 0x30, 38, // Name
+
+ 0x31, 12, // RDN[0]
+ 0x30, 10, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x04, // CN
+ 0x13, 3, 'Q', 'w', 'q', // Qwq
+
+ 0x31, 9, // RDN[1]
+ // Wrong tag here
+ 0x31, 7, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xA, // O
+ 0x13, 2, 'I', 'T', // IT
+
+ 0x31, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'N' // CN
+ }), false));
+ assertThrows(ParseException.class, () -> new Name(new BytesReader(new Byte[]{
+ 0x30, 38, // Name
+
+ 0x31, 12, // RDN[0]
+ 0x30, 10, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x04, // CN
+ 0x13, 3, 'Q', 'w', 'q', // Qwq
+
+ 0x31, 9, // RDN[1]
+ 0x30, 7, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xA, // O
+ 0x13, 2, 'I', 'T', // IT
+
+ // Wrong tag here
+ 0x30, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'N' // CN
+ }), false));
+ }
+
+ @Test
+ void testEncode() {
+ assertArrayEquals(new Byte[]{
+ 0x30, 45, // Name
+
+ 0x31, 14, // RDN[0]
+ 0x30, 12, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x03, // CN
+ 0x13, 5, 'y', 'u', 'u', 't', 'a', // yuuta
+
+ 0x31, 14, // RDN[1]
+ 0x30, 12, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xB, // OU
+ 0x13, 5, 'u', 's', 'e', 'r', 's', // users
+
+ 0x31, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'A' // CA
+ }, TestConstants.NAME_1.encodeDER());
+
+ assertArrayEquals(new Byte[]{
+ 0x30, 40, // Name
+
+ 0x31, 12, // RDN[0]
+ 0x30, 10, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x04, // CN
+ 0x13, 3, 'Q', 'w', 'q', // Qwq
+
+ 0x31, 11, // RDN[1]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0xA, // O
+ 0x13, 2, 'I', 'T', // IT
+
+ 0x31, 11, // RDN[2]
+ 0x30, 9, // KV[0]
+ 0x6, 3, 0x55, 0x04, 0x6, // C
+ 0x13, 2, 'C', 'N' // CN
+ }, TestConstants.NAME_2.encodeDER());
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("CN=yuuta,OU=users,C=CA", TestConstants.NAME_1.toString());
+ assertEquals("SN=Qwq,O=IT,C=CN", TestConstants.NAME_2.toString());
+ }
+}
diff --git a/src/test/model/x501/RelativeDistinguishedNameTest.java b/src/test/model/x501/RelativeDistinguishedNameTest.java
new file mode 100644
index 0000000..d066010
--- /dev/null
+++ b/src/test/model/x501/RelativeDistinguishedNameTest.java
@@ -0,0 +1,95 @@
+package model.x501;
+
+import model.asn1.*;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.TestConstants;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static model.asn1.ASN1Object.TAG_SET;
+import static model.asn1.ObjectIdentifier.OID_C;
+import static model.asn1.ObjectIdentifier.OID_OU;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class RelativeDistinguishedNameTest {
+ @Test
+ void testConstructor() throws ParseException {
+ assertArrayEquals(OID_OU,
+ new RelativeDistinguishedName(TAG_SET, null,
+ new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_OU),
+ new Null(Null.TAG, null))
+ }).getArray()[0].getType().getInts());
+ assertEquals("123",
+ new RelativeDistinguishedName(TAG_SET, null,
+ new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_OU),
+ new PrintableString(PrintableString.TAG, null, "123"))
+ }).getArray()[0].getValue().toString());
+ }
+
+ @Test
+ void testParse() throws ParseException {
+ assertEquals(1, TestConstants.L_MILANO.getArray().length);
+ assertEquals("Milano", TestConstants.L_MILANO.getArray()[0].getValue().toString());
+
+ assertEquals(2, TestConstants.L_MILANO_CN_TEST_ED25519.getArray().length);
+ assertEquals("Test ed25519",
+ TestConstants.L_MILANO_CN_TEST_ED25519.getArray()[0].getValue().toString());
+ }
+
+ @Test
+ void testParseFail() {
+ // Invalid child tag
+ assertThrows(ParseException.class, () ->
+ new RelativeDistinguishedName(new BytesReader(new Byte[] {
+ 0x31, 0x0F,
+ 0x31, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C,
+ 0x06, 0x4D, 0x69, 0x6C, 0x61, 0x6E, 0x6F
+ }), false));
+ assertThrows(ParseException.class, () ->
+ new RelativeDistinguishedName(new BytesReader(new Byte[] {
+ 0x31, 0x23,
+ // CN
+ 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C,
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x65, 0x64, 0x32,
+ 0x35, 0x35, 0x31, 0x39,
+ // L
+ 0x31, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0C,
+ 0x06, 0x4D, 0x69, 0x6C, 0x61, 0x6E, 0x6F
+ }), false));
+ }
+
+ @Test
+ void testEncode() throws ParseException {
+ assertArrayEquals(TestConstants.combine((byte) 0x31,
+ TestConstants.CN_TEST_ED25519_DER,
+ TestConstants.L_MILANO_DER),
+ TestConstants.L_MILANO_CN_TEST_ED25519.encodeDER());
+ assertArrayEquals(new Byte[]{
+ 0x31, 15,
+ 0x30, 13,
+ 0x06, 3, 0x55, 0x04, 0x06,
+ 0x13, 6, '1', '2', '3', '1', '2', '3'
+
+ }, new RelativeDistinguishedName(TAG_SET, null,
+ new AttributeTypeAndValue[]{
+ new AttributeTypeAndValue(
+ ASN1Object.TAG_SEQUENCE, null,
+ new ObjectIdentifier(ObjectIdentifier.TAG, null, OID_C),
+ new PrintableString(PrintableString.TAG, null, "123123"))})
+ .encodeDER());
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("L=Milano", TestConstants.L_MILANO.toString());
+ assertEquals("CN=Test ed25519+L=Milano", TestConstants.L_MILANO_CN_TEST_ED25519.toString());
+ }
+}