From d342a45d98c4795b3a3fe1aaef5236ad4a782b55 Mon Sep 17 00:00:00 2001 From: Yuuta Liang Date: Thu, 12 Oct 2023 12:10:33 +0800 Subject: Implement data structures from X.680, X.501, X.509, and PKCS#10, with X.690 encoding / decoding support The implementation took four days, and it is still a little bit rough. Updated version should arrive soon. Signed-off-by: Yuuta Liang --- src/test/model/MyModelTest.java | 5 - src/test/model/TestConstants.java | 544 +++++++++++++++++++++ src/test/model/asn1/ASN1LengthTest.java | 94 ++++ src/test/model/asn1/ASN1ObjectTest.java | 177 +++++++ src/test/model/asn1/BitStringTest.java | 75 +++ src/test/model/asn1/BoolTest.java | 45 ++ src/test/model/asn1/GeneralizedTimeTest.java | 119 +++++ src/test/model/asn1/IA5StringTest.java | 79 +++ src/test/model/asn1/IntTest.java | 96 ++++ src/test/model/asn1/NullTest.java | 38 ++ src/test/model/asn1/ObjectIdentifierTest.java | 89 ++++ src/test/model/asn1/OctetStringTest.java | 44 ++ src/test/model/asn1/PrintableStringTest.java | 71 +++ src/test/model/asn1/TagClassTest.java | 15 + src/test/model/asn1/TagTest.java | 107 ++++ src/test/model/asn1/UTF8StringTest.java | 64 +++ src/test/model/asn1/UtcTimeTest.java | 121 +++++ src/test/model/asn1/parsing/BytesReaderTest.java | 95 ++++ src/test/model/csr/AttributeTest.java | 53 ++ src/test/model/csr/AttributesTest.java | 69 +++ .../model/csr/CertificationRequestInfoTest.java | 174 +++++++ src/test/model/csr/CertificationRequestTest.java | 114 +++++ src/test/model/csr/ValuesTest.java | 133 +++++ src/test/model/pki/AlgorithmIdentifierTest.java | 84 ++++ src/test/model/pki/SubjectPublicKeyInfoTest.java | 115 +++++ src/test/model/pki/cert/CertificateTest.java | 81 +++ src/test/model/pki/cert/ExtensionTest.java | 118 +++++ src/test/model/pki/cert/ExtensionsTest.java | 112 +++++ src/test/model/pki/cert/TbsCertificateTest.java | 184 +++++++ src/test/model/pki/cert/ValidityTest.java | 118 +++++ .../model/pki/crl/CertificateListContentTest.java | 37 ++ src/test/model/pki/crl/CertificateListTest.java | 52 ++ src/test/model/pki/crl/RevokedCertificateTest.java | 25 + src/test/model/x501/AttributeTypeAndValueTest.java | 90 ++++ src/test/model/x501/NameTest.java | 167 +++++++ .../model/x501/RelativeDistinguishedNameTest.java | 95 ++++ 36 files changed, 3694 insertions(+), 5 deletions(-) delete mode 100644 src/test/model/MyModelTest.java create mode 100644 src/test/model/TestConstants.java create mode 100644 src/test/model/asn1/ASN1LengthTest.java create mode 100644 src/test/model/asn1/ASN1ObjectTest.java create mode 100644 src/test/model/asn1/BitStringTest.java create mode 100644 src/test/model/asn1/BoolTest.java create mode 100644 src/test/model/asn1/GeneralizedTimeTest.java create mode 100644 src/test/model/asn1/IA5StringTest.java create mode 100644 src/test/model/asn1/IntTest.java create mode 100644 src/test/model/asn1/NullTest.java create mode 100644 src/test/model/asn1/ObjectIdentifierTest.java create mode 100644 src/test/model/asn1/OctetStringTest.java create mode 100644 src/test/model/asn1/PrintableStringTest.java create mode 100644 src/test/model/asn1/TagClassTest.java create mode 100644 src/test/model/asn1/TagTest.java create mode 100644 src/test/model/asn1/UTF8StringTest.java create mode 100644 src/test/model/asn1/UtcTimeTest.java create mode 100644 src/test/model/asn1/parsing/BytesReaderTest.java create mode 100644 src/test/model/csr/AttributeTest.java create mode 100644 src/test/model/csr/AttributesTest.java create mode 100644 src/test/model/csr/CertificationRequestInfoTest.java create mode 100644 src/test/model/csr/CertificationRequestTest.java create mode 100644 src/test/model/csr/ValuesTest.java create mode 100644 src/test/model/pki/AlgorithmIdentifierTest.java create mode 100644 src/test/model/pki/SubjectPublicKeyInfoTest.java create mode 100644 src/test/model/pki/cert/CertificateTest.java create mode 100644 src/test/model/pki/cert/ExtensionTest.java create mode 100644 src/test/model/pki/cert/ExtensionsTest.java create mode 100644 src/test/model/pki/cert/TbsCertificateTest.java create mode 100644 src/test/model/pki/cert/ValidityTest.java create mode 100644 src/test/model/pki/crl/CertificateListContentTest.java create mode 100644 src/test/model/pki/crl/CertificateListTest.java create mode 100644 src/test/model/pki/crl/RevokedCertificateTest.java create mode 100644 src/test/model/x501/AttributeTypeAndValueTest.java create mode 100644 src/test/model/x501/NameTest.java create mode 100644 src/test/model/x501/RelativeDistinguishedNameTest.java (limited to 'src/test') 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()); + } +} -- cgit v1.2.3