aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/.gitignore1
-rw-r--r--data/invalid_cert.json3
-rw-r--r--data/invalid_key_1.json20
-rw-r--r--data/invalid_key_2.json6
-rw-r--r--data/invalid_log.json70
-rw-r--r--data/invalid_revoked_1.json70
-rw-r--r--data/invalid_revoked_2.json70
-rw-r--r--data/invalid_signed.json70
-rw-r--r--data/invalid_template_1.json14
-rw-r--r--data/invalid_template_2.json15
-rw-r--r--data/valid_full.json70
-rw-r--r--data/valid_minimal.json1
-rw-r--r--src/main/model/asn1/exceptions/InvalidDBException.java10
-rw-r--r--src/main/model/ca/CertificationAuthority.java80
-rw-r--r--src/main/persistence/Decoder.java284
-rw-r--r--src/main/persistence/FS.java46
-rw-r--r--src/main/ui/IssueScreen.java8
-rw-r--r--src/main/ui/JCA.java95
-rw-r--r--src/main/ui/MainScreen.java6
-rw-r--r--src/main/ui/MgmtScreen.java3
-rw-r--r--src/main/ui/TemplateSetScreen.java8
-rw-r--r--src/main/ui/TemplatesScreen.java2
-rw-r--r--src/main/ui/Utils.java6
-rw-r--r--src/test/persistence/DecoderTest.java81
-rw-r--r--src/test/persistence/FSTest.java50
-rw-r--r--src/test/ui/UtilsTest.java6
26 files changed, 1063 insertions, 32 deletions
diff --git a/data/.gitignore b/data/.gitignore
new file mode 100644
index 0000000..64a5436
--- /dev/null
+++ b/data/.gitignore
@@ -0,0 +1 @@
+ca.json
diff --git a/data/invalid_cert.json b/data/invalid_cert.json
new file mode 100644
index 0000000..4e97919
--- /dev/null
+++ b/data/invalid_cert.json
@@ -0,0 +1,3 @@
+{
+ "cert": "-----BEGIN CERTIFICATE-----\nMIICYjCCAgigAwIBAgIUHflZOHj+NxNnCdHe68pxL5ed4GowCgYIKoZIzj0EAwQwJDEVMBMGA1UEAwwMVGVzdCBSb290IENBMQswCQYDVQQGEwJDQTAeFw0yMzEwMjQwNTMwMDJaFw0zMzEwMjEwNTMwMDJaMA4xDDAKBgNVBAMTA0pDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn+2EiysX45uNH8JyFuBtChADDDLJBE5GCMrFpLSqT6L2148WeuWsVlADl2JpbwXlqnfnNOK7O+zzt7po7/MPVpl8LLMHeHpUMAMmKDqvkXZJ5bZqp3RZMJ10pw4uVM6MJCM11/bkuLpwIJ6mW6ntO85oWV4ZKZVXaw97xiuNHW20x7pEp6X1toMhoRCGn9tWSbo5aDwpG77PiayVEyW+JtvPRJgmdzKDdaT/wyV7wP6KJhAhy4sTFnI7JuBc16vQMMwOs4ZgT5zl612sUEe3ACufSeeOOQxicFxgmnMPX/Lln2ALRt03//KeATxrDNS5JLNl3QsqRfgzwfpYGAt9UCAwEAAaNjMGEwHQYDVR0OBBYEFKLntJQ7phJGu7A9dfIovZy6rV6SMB8GA1UdIwQYMBaAFPMn0b1st26LXxJKvjFSvn8X1IetMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMEA0gAMEUCIEMXMU30a3M5fjhq2M2wsAe5j2d1iuRIn+mXf4BB1uVhAiEA5UynPbqF1za=\n-----END CERTIFICATE-----"
+}
diff --git a/data/invalid_key_1.json b/data/invalid_key_1.json
new file mode 100644
index 0000000..8d8ff3f
--- /dev/null
+++ b/data/invalid_key_1.json
@@ -0,0 +1,20 @@
+{
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ],
+ "key": {
+ "p": "abcdefg",
+ "e": "10001",
+ "n": "c9fed848b2b17e39b8d1fc27216e06d0a10030c32c9044e4608cac5a4b4aa4fa2f6d78f167ae5ac5650039762696f05e5aa77e734e2bb3becf3b7ba68eff30f56997c2cb307787a54300326283aaf917649e5b66aa77459309d74a70e2e54ce8c242335d7f6e4b8ba70209ea65ba9ed3bce68595e192995576b0f7bc62b8d1d6db4c7ba44a7a5f5b68321a110869fdb5649ba39683c291bbecf89ac951325be26dbcf44982677328375a4ffc3257bc0fe8a261021cb8b1316723b26e05cd7abd030cc0eb386604f9ce5eb5dac5047b7002b9f49e78e390c62705c609a730f5ff2e59f600b46dd37fff29e013c6b0cd4b924b365dd0b2a45f833c1fa58180b7d5"
+ }
+}
diff --git a/data/invalid_key_2.json b/data/invalid_key_2.json
new file mode 100644
index 0000000..9edc578
--- /dev/null
+++ b/data/invalid_key_2.json
@@ -0,0 +1,6 @@
+{
+ "key": {
+ "p": "2e432f8270e8ad55e782324bbd008fcf82fc41eec57b5247f2e3ed0a6e118db8de1966b8754c4dae456c587cbaa859ab667c537df1929943737f7659a689044bc4b0151547c7ac79b95f676ac028ad8d81c631fd50bfe9df9c02a2a239991634fddebf186419dcf4025f3969a57c692969eb6aff718f0b8eb315235c12492d87ad047537854adf8ebcc5f69c930e2f3a51f818225e64885431049accb474b764aeebae052d50a094354f2905a600fb0e3314de9c4f8af3afc16ae2231b09511d60ecba0bf5f7abaa74a0d2208b52558f238385e06672280014a9d545547938606b947549a0ea9b6e92fa84ac42bdbe9186c1e428246d635aec1fc0209c0d1b29",
+ "e": "10001"
+ }
+}
diff --git a/data/invalid_log.json b/data/invalid_log.json
new file mode 100644
index 0000000..b9af2e5
--- /dev/null
+++ b/data/invalid_log.json
@@ -0,0 +1,70 @@
+{
+ "serial": 3,
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ],
+ "signed": [
+ "-----BEGIN CERTIFICATE-----\nMIIDpTCCAo2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUxOTU5NTRaFw0yNDAyMDIxOTU5NTRaMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp9VC97vud9riIoljgkp0Bvrmhj+DTC9670a4ZhFyN0FOZ1t6j5S0soxnrMtJW4Eq9Ga/FNb/jY4LUKuxshGWdp8+2+4tiakbIg3xZE4KJSLGFNr9asme5a1nvuOPM5UPYPnDbNa/q2t4sOx4kVVPQvWpZAr6epr445askazxq/DKfENzE/GV3m+0ivRYX/sMtDOnp7rzNd1U5Q8e3Wr56aAH8al5qdVv2LXrxSe2iORL3RB8Y4um8EllILb/d+euZ3XJ/6LvpkStgDe886ZFDN7v4VKIYoZ2tFhso/srKMjgB+ZD+OTwbVZMZRqB45tfZoZsfT6uDg+FlUBsjOGA0AiqdAo77Fi90JStxz5jFNsNj24h33M/DKtwLLc4f5LFqagL9KyAZhT5ofjKRpFg13p6v7XO/WWbiB5V6QUVpOzZj/HtcpMWjT4p/6sr/LvQ4uPgfjlrkdBnaXidVorjNQk+Pnsi922g3fbULN1X7iTnWZ7fH5XCVWiQ7WdVlqeIMnlLlbQvzvL+a6hrxF/OfUUyHsIRylYn2v6rsxtM9sHM0vOSbdYpOlq/9ve6fSnucDy/Bh7RWkRqRSlgJl1PHhfGRCE3YOL7ZsucA+rKBOXVH0qCwerygMA60Hkt9WPb5nhNLTafCbn1Mhw9WzLP2aun0rIin5pBc1B0/4SO95UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYd5V/S6k8dvy5gjjKaezfaPeuYfES3mwYmu6D3WNF4k8LHtru7dbJrH9aBNhDA3mHPr8hRtNX2gskQWWcbvQlGOq6HZ6TZsdyOrZdrFw265RseJmrI/fGTYaWPiVCUf1NQM9gzwmvnSxJV0GobSaTJ1IEndEd5nW0Cnce5/wWiI0nBkm0lAVQDR+gOFqJOIEgQyUi3qzH4WmGLqWqZNfHrzkUEF/r0dkahfZatkhY9aIrLpYhebTHqcnmQqfoFlIkP0l48w7263yl/utLzZBi9iIrzLu6QLhihm6m/lR9+mU7Tk+qjNpcfbXVGxnGND7kCqj/8w501oaJ+7BVewA7Q==\n-----END CERTIFICATE-----",
+ "-----BEGIN CERTIFICATE-----\nMIIDqjCCApKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUyMDAwMzVaFw0yNDA1MTIyMDAwMzVaMCExCzAJBgNVBAYTAkNBMRIwEAYDVQQDDAlUZXN0IExlYWYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn1UL3u+532uIiiWOCSnQG+uaGP4NML3rvRrhmEXI3QU5nW3qPlLSyjGesy0lbgSr0Zr8U1v+NjgtQq7GyEZZ2nz7b7i2JqRsiDfFkTgolIsYU2v1qyZ7lrWe+448zlQ9g+cNs1r+ra3iw7HiRVU9C9alkCvp6mvjjlqyRrPGr8Mp8Q3MT8ZXeb7SK9Fhf+wy0M6enuvM13VTlDx7davnpoAfxqXmp1W/YtevFJ7aI5EvdEHxji6bwSWUgtv93565ndcn/ou+mRK2AN7zzpkUM3u/hUohihna0WGyj+ysoyOAH5kP45PBtVkxlGoHjm19mhmx9Pq4OD4WVQGyM4YDQCKp0CjvsWL3QlK3HPmMU2w2PbiHfcz8Mq3Astzh/ksWpqAv0rIBmFPmh+MpGkWDXenq/tc79ZZuIHlXpBRWk7NmP8e1ykxaNPin/qyv8u9Di4+B+OWuR0GdpeJ1WiuM1CT4+eyL3baDd9tQs3VfuJOdZnt8flcJVaJDtZ1WWp4gyeUuVtC/O8v5rqGvEX859RTIewhHKVifa/quzG0z2wczS85Jt1ik6Wr/297p9Ke5wPL8GHtFaRGpFKWAmXU8eF8ZEITdg4vtmy5wD6soE5dUfSoLB6vKAwDrQeS31Y9vmeE0tNp8JufUyHD1bMs/Zq6fSsiKfmkFzUHT/hI73lQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/Pfi9EuE8NVdPPCQKcQ96ZLC8j7m9qh40nFzbtQFf5efCv8wpP8UVC5faIJJad/4HIwy6LJwBYGk6PtP1z31ovumNTHpZEs5LtRQnrIAlWRHUl9XQyo6+vZdfkX0fBS4KvnV8wHNNCJP1lBAzOM5GVk5JA1jw65AHDfVea3F5LgSClfR+w//adCYOAIO6sOJFM9fLKRp0kQKTceNf/2cX8B1WPTs6D8Rb5tauMP7JtbiuRDjaDGIHoSyISOGMbaf/+JCb/l5BlY5pv8NGBdbSeR5Lv81qzbOSAqVniAuM8rxIlB6WbYpmS1M2EhHgMjUPXo3Wxkx/VeChLviL34H5\n-----END CERTIFICATE-----"
+ ],
+ "cert": "-----BEGIN CERTIFICATE-----\nMIICYjCCAgigAwIBAgIUHflZOHj+NxNnCdHe68pxL5ed4GowCgYIKoZIzj0EAwQwJDEVMBMGA1UEAwwMVGVzdCBSb290IENBMQswCQYDVQQGEwJDQTAeFw0yMzEwMjQwNTMwMDJaFw0zMzEwMjEwNTMwMDJaMA4xDDAKBgNVBAMTA0pDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn+2EiysX45uNH8JyFuBtChADDDLJBE5GCMrFpLSqT6L2148WeuWsVlADl2JpbwXlqnfnNOK7O+zzt7po7/MPVpl8LLMHeHpUMAMmKDqvkXZJ5bZqp3RZMJ10pw4uVM6MJCM11/bkuLpwIJ6mW6ntO85oWV4ZKZVXaw97xiuNHW20x7pEp6X1toMhoRCGn9tWSbo5aDwpG77PiayVEyW+JtvPRJgmdzKDdaT/wyV7wP6KJhAhy4sTFnI7JuBc16vQMMwOs4ZgT5zl612sUEe3ACufSeeOOQxicFxgmnMPX/Lln2ALRt03//KeATxrDNS5JLNl3QsqRfgzwfpYGAt9UCAwEAAaNjMGEwHQYDVR0OBBYEFKLntJQ7phJGu7A9dfIovZy6rV6SMB8GA1UdIwQYMBaAFPMn0b1st26LXxJKvjFSvn8X1IetMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMEA0gAMEUCIEMXMU30a3M5fjhq2M2wsAe5j2d1iuRIn+mXf4BB1uVhAiEA5UynPbqF1zav4/fqPaDB3UyWArzFqi6mjXQUdHOyvXo=\n-----END CERTIFICATE-----",
+ "revoked": [
+ {
+ "date": "-----BEGIN DATE-----\nFw0yMzEwMjUyMDAwNTZa\n-----END DATE-----",
+ "reason": "KEY_COMPROMISE",
+ "serial": "1"
+ }
+ ],
+ "logs": [
+ {
+ "action": "Added a new template: TLS-Server",
+ "time": "2023-10-26T03:30:41.320397+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Added a new template: User",
+ "time": "2023-10-26T03:30:49",
+ "user": "yuuta"
+ },
+ {
+ "action": "Template User has been enabled",
+ "time": "2023-10-26T03:30:50.8239+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@18bf3d14",
+ "time": "2023-10-26T03:59:54.721842+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@8b87145",
+ "time": "2023-10-26T04:00:36.025426+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Certificate 1 is revoked with reason KEY_COMPROMISE",
+ "time": "2023-10-26T04:00:56.95637+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed CRL with 1 revoked certs.",
+ "time": "2023-10-26T04:01:08.074272+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ }
+ ],
+ "key": {
+ "p": "2e432f8270e8ad55e782324bbd008fcf82fc41eec57b5247f2e3ed0a6e118db8de1966b8754c4dae456c587cbaa859ab667c537df1929943737f7659a689044bc4b0151547c7ac79b95f676ac028ad8d81c631fd50bfe9df9c02a2a239991634fddebf186419dcf4025f3969a57c692969eb6aff718f0b8eb315235c12492d87ad047537854adf8ebcc5f69c930e2f3a51f818225e64885431049accb474b764aeebae052d50a094354f2905a600fb0e3314de9c4f8af3afc16ae2231b09511d60ecba0bf5f7abaa74a0d2208b52558f238385e06672280014a9d545547938606b947549a0ea9b6e92fa84ac42bdbe9186c1e428246d635aec1fc0209c0d1b29",
+ "e": "10001",
+ "n": "c9fed848b2b17e39b8d1fc27216e06d0a10030c32c9044e4608cac5a4b4aa4fa2f6d78f167ae5ac5650039762696f05e5aa77e734e2bb3becf3b7ba68eff30f56997c2cb307787a54300326283aaf917649e5b66aa77459309d74a70e2e54ce8c242335d7f6e4b8ba70209ea65ba9ed3bce68595e192995576b0f7bc62b8d1d6db4c7ba44a7a5f5b68321a110869fdb5649ba39683c291bbecf89ac951325be26dbcf44982677328375a4ffc3257bc0fe8a261021cb8b1316723b26e05cd7abd030cc0eb386604f9ce5eb5dac5047b7002b9f49e78e390c62705c609a730f5ff2e59f600b46dd37fff29e013c6b0cd4b924b365dd0b2a45f833c1fa58180b7d5"
+ }
+}
diff --git a/data/invalid_revoked_1.json b/data/invalid_revoked_1.json
new file mode 100644
index 0000000..1864595
--- /dev/null
+++ b/data/invalid_revoked_1.json
@@ -0,0 +1,70 @@
+{
+ "serial": 3,
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ],
+ "signed": [
+ "-----BEGIN CERTIFICATE-----\nMIIDpTCCAo2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUxOTU5NTRaFw0yNDAyMDIxOTU5NTRaMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp9VC97vud9riIoljgkp0Bvrmhj+DTC9670a4ZhFyN0FOZ1t6j5S0soxnrMtJW4Eq9Ga/FNb/jY4LUKuxshGWdp8+2+4tiakbIg3xZE4KJSLGFNr9asme5a1nvuOPM5UPYPnDbNa/q2t4sOx4kVVPQvWpZAr6epr445askazxq/DKfENzE/GV3m+0ivRYX/sMtDOnp7rzNd1U5Q8e3Wr56aAH8al5qdVv2LXrxSe2iORL3RB8Y4um8EllILb/d+euZ3XJ/6LvpkStgDe886ZFDN7v4VKIYoZ2tFhso/srKMjgB+ZD+OTwbVZMZRqB45tfZoZsfT6uDg+FlUBsjOGA0AiqdAo77Fi90JStxz5jFNsNj24h33M/DKtwLLc4f5LFqagL9KyAZhT5ofjKRpFg13p6v7XO/WWbiB5V6QUVpOzZj/HtcpMWjT4p/6sr/LvQ4uPgfjlrkdBnaXidVorjNQk+Pnsi922g3fbULN1X7iTnWZ7fH5XCVWiQ7WdVlqeIMnlLlbQvzvL+a6hrxF/OfUUyHsIRylYn2v6rsxtM9sHM0vOSbdYpOlq/9ve6fSnucDy/Bh7RWkRqRSlgJl1PHhfGRCE3YOL7ZsucA+rKBOXVH0qCwerygMA60Hkt9WPb5nhNLTafCbn1Mhw9WzLP2aun0rIin5pBc1B0/4SO95UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYd5V/S6k8dvy5gjjKaezfaPeuYfES3mwYmu6D3WNF4k8LHtru7dbJrH9aBNhDA3mHPr8hRtNX2gskQWWcbvQlGOq6HZ6TZsdyOrZdrFw265RseJmrI/fGTYaWPiVCUf1NQM9gzwmvnSxJV0GobSaTJ1IEndEd5nW0Cnce5/wWiI0nBkm0lAVQDR+gOFqJOIEgQyUi3qzH4WmGLqWqZNfHrzkUEF/r0dkahfZatkhY9aIrLpYhebTHqcnmQqfoFlIkP0l48w7263yl/utLzZBi9iIrzLu6QLhihm6m/lR9+mU7Tk+qjNpcfbXVGxnGND7kCqj/8w501oaJ+7BVewA7Q==\n-----END CERTIFICATE-----",
+ "-----BEGIN CERTIFICATE-----\nMIIDqjCCApKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUyMDAwMzVaFw0yNDA1MTIyMDAwMzVaMCExCzAJBgNVBAYTAkNBMRIwEAYDVQQDDAlUZXN0IExlYWYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn1UL3u+532uIiiWOCSnQG+uaGP4NML3rvRrhmEXI3QU5nW3qPlLSyjGesy0lbgSr0Zr8U1v+NjgtQq7GyEZZ2nz7b7i2JqRsiDfFkTgolIsYU2v1qyZ7lrWe+448zlQ9g+cNs1r+ra3iw7HiRVU9C9alkCvp6mvjjlqyRrPGr8Mp8Q3MT8ZXeb7SK9Fhf+wy0M6enuvM13VTlDx7davnpoAfxqXmp1W/YtevFJ7aI5EvdEHxji6bwSWUgtv93565ndcn/ou+mRK2AN7zzpkUM3u/hUohihna0WGyj+ysoyOAH5kP45PBtVkxlGoHjm19mhmx9Pq4OD4WVQGyM4YDQCKp0CjvsWL3QlK3HPmMU2w2PbiHfcz8Mq3Astzh/ksWpqAv0rIBmFPmh+MpGkWDXenq/tc79ZZuIHlXpBRWk7NmP8e1ykxaNPin/qyv8u9Di4+B+OWuR0GdpeJ1WiuM1CT4+eyL3baDd9tQs3VfuJOdZnt8flcJVaJDtZ1WWp4gyeUuVtC/O8v5rqGvEX859RTIewhHKVifa/quzG0z2wczS85Jt1ik6Wr/297p9Ke5wPL8GHtFaRGpFKWAmXU8eF8ZEITdg4vtmy5wD6soE5dUfSoLB6vKAwDrQeS31Y9vmeE0tNp8JufUyHD1bMs/Zq6fSsiKfmkFzUHT/hI73lQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/Pfi9EuE8NVdPPCQKcQ96ZLC8j7m9qh40nFzbtQFf5efCv8wpP8UVC5faIJJad/4HIwy6LJwBYGk6PtP1z31ovumNTHpZEs5LtRQnrIAlWRHUl9XQyo6+vZdfkX0fBS4KvnV8wHNNCJP1lBAzOM5GVk5JA1jw65AHDfVea3F5LgSClfR+w//adCYOAIO6sOJFM9fLKRp0kQKTceNf/2cX8B1WPTs6D8Rb5tauMP7JtbiuRDjaDGIHoSyISOGMbaf/+JCb/l5BlY5pv8NGBdbSeR5Lv81qzbOSAqVniAuM8rxIlB6WbYpmS1M2EhHgMjUPXo3Wxkx/VeChLviL34H5\n-----END CERTIFICATE-----"
+ ],
+ "cert": "-----BEGIN CERTIFICATE-----\nMIICYjCCAgigAwIBAgIUHflZOHj+NxNnCdHe68pxL5ed4GowCgYIKoZIzj0EAwQwJDEVMBMGA1UEAwwMVGVzdCBSb290IENBMQswCQYDVQQGEwJDQTAeFw0yMzEwMjQwNTMwMDJaFw0zMzEwMjEwNTMwMDJaMA4xDDAKBgNVBAMTA0pDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn+2EiysX45uNH8JyFuBtChADDDLJBE5GCMrFpLSqT6L2148WeuWsVlADl2JpbwXlqnfnNOK7O+zzt7po7/MPVpl8LLMHeHpUMAMmKDqvkXZJ5bZqp3RZMJ10pw4uVM6MJCM11/bkuLpwIJ6mW6ntO85oWV4ZKZVXaw97xiuNHW20x7pEp6X1toMhoRCGn9tWSbo5aDwpG77PiayVEyW+JtvPRJgmdzKDdaT/wyV7wP6KJhAhy4sTFnI7JuBc16vQMMwOs4ZgT5zl612sUEe3ACufSeeOOQxicFxgmnMPX/Lln2ALRt03//KeATxrDNS5JLNl3QsqRfgzwfpYGAt9UCAwEAAaNjMGEwHQYDVR0OBBYEFKLntJQ7phJGu7A9dfIovZy6rV6SMB8GA1UdIwQYMBaAFPMn0b1st26LXxJKvjFSvn8X1IetMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMEA0gAMEUCIEMXMU30a3M5fjhq2M2wsAe5j2d1iuRIn+mXf4BB1uVhAiEA5UynPbqF1zav4/fqPaDB3UyWArzFqi6mjXQUdHOyvXo=\n-----END CERTIFICATE-----",
+ "revoked": [
+ {
+ "date": "-----BEGIN DATE-----\nFw0yMzEwMjUyMDAwNTZi0i22930a\n-----END DATE-----",
+ "reason": "KEY_COMPROMISE",
+ "serial": "1"
+ }
+ ],
+ "logs": [
+ {
+ "action": "Added a new template: TLS-Server",
+ "time": "2023-10-26T03:30:41.320397+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Added a new template: User",
+ "time": "2023-10-26T03:30:49.200216+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Template User has been enabled",
+ "time": "2023-10-26T03:30:50.8239+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@18bf3d14",
+ "time": "2023-10-26T03:59:54.721842+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@8b87145",
+ "time": "2023-10-26T04:00:36.025426+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Certificate 1 is revoked with reason KEY_COMPROMISE",
+ "time": "2023-10-26T04:00:56.95637+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed CRL with 1 revoked certs.",
+ "time": "2023-10-26T04:01:08.074272+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ }
+ ],
+ "key": {
+ "p": "2e432f8270e8ad55e782324bbd008fcf82fc41eec57b5247f2e3ed0a6e118db8de1966b8754c4dae456c587cbaa859ab667c537df1929943737f7659a689044bc4b0151547c7ac79b95f676ac028ad8d81c631fd50bfe9df9c02a2a239991634fddebf186419dcf4025f3969a57c692969eb6aff718f0b8eb315235c12492d87ad047537854adf8ebcc5f69c930e2f3a51f818225e64885431049accb474b764aeebae052d50a094354f2905a600fb0e3314de9c4f8af3afc16ae2231b09511d60ecba0bf5f7abaa74a0d2208b52558f238385e06672280014a9d545547938606b947549a0ea9b6e92fa84ac42bdbe9186c1e428246d635aec1fc0209c0d1b29",
+ "e": "10001",
+ "n": "c9fed848b2b17e39b8d1fc27216e06d0a10030c32c9044e4608cac5a4b4aa4fa2f6d78f167ae5ac5650039762696f05e5aa77e734e2bb3becf3b7ba68eff30f56997c2cb307787a54300326283aaf917649e5b66aa77459309d74a70e2e54ce8c242335d7f6e4b8ba70209ea65ba9ed3bce68595e192995576b0f7bc62b8d1d6db4c7ba44a7a5f5b68321a110869fdb5649ba39683c291bbecf89ac951325be26dbcf44982677328375a4ffc3257bc0fe8a261021cb8b1316723b26e05cd7abd030cc0eb386604f9ce5eb5dac5047b7002b9f49e78e390c62705c609a730f5ff2e59f600b46dd37fff29e013c6b0cd4b924b365dd0b2a45f833c1fa58180b7d5"
+ }
+}
diff --git a/data/invalid_revoked_2.json b/data/invalid_revoked_2.json
new file mode 100644
index 0000000..88541aa
--- /dev/null
+++ b/data/invalid_revoked_2.json
@@ -0,0 +1,70 @@
+{
+ "serial": 3,
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ],
+ "signed": [
+ "-----BEGIN CERTIFICATE-----\nMIIDpTCCAo2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUxOTU5NTRaFw0yNDAyMDIxOTU5NTRaMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp9VC97vud9riIoljgkp0Bvrmhj+DTC9670a4ZhFyN0FOZ1t6j5S0soxnrMtJW4Eq9Ga/FNb/jY4LUKuxshGWdp8+2+4tiakbIg3xZE4KJSLGFNr9asme5a1nvuOPM5UPYPnDbNa/q2t4sOx4kVVPQvWpZAr6epr445askazxq/DKfENzE/GV3m+0ivRYX/sMtDOnp7rzNd1U5Q8e3Wr56aAH8al5qdVv2LXrxSe2iORL3RB8Y4um8EllILb/d+euZ3XJ/6LvpkStgDe886ZFDN7v4VKIYoZ2tFhso/srKMjgB+ZD+OTwbVZMZRqB45tfZoZsfT6uDg+FlUBsjOGA0AiqdAo77Fi90JStxz5jFNsNj24h33M/DKtwLLc4f5LFqagL9KyAZhT5ofjKRpFg13p6v7XO/WWbiB5V6QUVpOzZj/HtcpMWjT4p/6sr/LvQ4uPgfjlrkdBnaXidVorjNQk+Pnsi922g3fbULN1X7iTnWZ7fH5XCVWiQ7WdVlqeIMnlLlbQvzvL+a6hrxF/OfUUyHsIRylYn2v6rsxtM9sHM0vOSbdYpOlq/9ve6fSnucDy/Bh7RWkRqRSlgJl1PHhfGRCE3YOL7ZsucA+rKBOXVH0qCwerygMA60Hkt9WPb5nhNLTafCbn1Mhw9WzLP2aun0rIin5pBc1B0/4SO95UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYd5V/S6k8dvy5gjjKaezfaPeuYfES3mwYmu6D3WNF4k8LHtru7dbJrH9aBNhDA3mHPr8hRtNX2gskQWWcbvQlGOq6HZ6TZsdyOrZdrFw265RseJmrI/fGTYaWPiVCUf1NQM9gzwmvnSxJV0GobSaTJ1IEndEd5nW0Cnce5/wWiI0nBkm0lAVQDR+gOFqJOIEgQyUi3qzH4WmGLqWqZNfHrzkUEF/r0dkahfZatkhY9aIrLpYhebTHqcnmQqfoFlIkP0l48w7263yl/utLzZBi9iIrzLu6QLhihm6m/lR9+mU7Tk+qjNpcfbXVGxnGND7kCqj/8w501oaJ+7BVewA7Q==\n-----END CERTIFICATE-----",
+ "-----BEGIN CERTIFICATE-----\nMIIDqjCCApKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUyMDAwMzVaFw0yNDA1MTIyMDAwMzVaMCExCzAJBgNVBAYTAkNBMRIwEAYDVQQDDAlUZXN0IExlYWYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn1UL3u+532uIiiWOCSnQG+uaGP4NML3rvRrhmEXI3QU5nW3qPlLSyjGesy0lbgSr0Zr8U1v+NjgtQq7GyEZZ2nz7b7i2JqRsiDfFkTgolIsYU2v1qyZ7lrWe+448zlQ9g+cNs1r+ra3iw7HiRVU9C9alkCvp6mvjjlqyRrPGr8Mp8Q3MT8ZXeb7SK9Fhf+wy0M6enuvM13VTlDx7davnpoAfxqXmp1W/YtevFJ7aI5EvdEHxji6bwSWUgtv93565ndcn/ou+mRK2AN7zzpkUM3u/hUohihna0WGyj+ysoyOAH5kP45PBtVkxlGoHjm19mhmx9Pq4OD4WVQGyM4YDQCKp0CjvsWL3QlK3HPmMU2w2PbiHfcz8Mq3Astzh/ksWpqAv0rIBmFPmh+MpGkWDXenq/tc79ZZuIHlXpBRWk7NmP8e1ykxaNPin/qyv8u9Di4+B+OWuR0GdpeJ1WiuM1CT4+eyL3baDd9tQs3VfuJOdZnt8flcJVaJDtZ1WWp4gyeUuVtC/O8v5rqGvEX859RTIewhHKVifa/quzG0z2wczS85Jt1ik6Wr/297p9Ke5wPL8GHtFaRGpFKWAmXU8eF8ZEITdg4vtmy5wD6soE5dUfSoLB6vKAwDrQeS31Y9vmeE0tNp8JufUyHD1bMs/Zq6fSsiKfmkFzUHT/hI73lQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/Pfi9EuE8NVdPPCQKcQ96ZLC8j7m9qh40nFzbtQFf5efCv8wpP8UVC5faIJJad/4HIwy6LJwBYGk6PtP1z31ovumNTHpZEs5LtRQnrIAlWRHUl9XQyo6+vZdfkX0fBS4KvnV8wHNNCJP1lBAzOM5GVk5JA1jw65AHDfVea3F5LgSClfR+w//adCYOAIO6sOJFM9fLKRp0kQKTceNf/2cX8B1WPTs6D8Rb5tauMP7JtbiuRDjaDGIHoSyISOGMbaf/+JCb/l5BlY5pv8NGBdbSeR5Lv81qzbOSAqVniAuM8rxIlB6WbYpmS1M2EhHgMjUPXo3Wxkx/VeChLviL34H5\n-----END CERTIFICATE-----"
+ ],
+ "cert": "-----BEGIN CERTIFICATE-----\nMIICYjCCAgigAwIBAgIUHflZOHj+NxNnCdHe68pxL5ed4GowCgYIKoZIzj0EAwQwJDEVMBMGA1UEAwwMVGVzdCBSb290IENBMQswCQYDVQQGEwJDQTAeFw0yMzEwMjQwNTMwMDJaFw0zMzEwMjEwNTMwMDJaMA4xDDAKBgNVBAMTA0pDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn+2EiysX45uNH8JyFuBtChADDDLJBE5GCMrFpLSqT6L2148WeuWsVlADl2JpbwXlqnfnNOK7O+zzt7po7/MPVpl8LLMHeHpUMAMmKDqvkXZJ5bZqp3RZMJ10pw4uVM6MJCM11/bkuLpwIJ6mW6ntO85oWV4ZKZVXaw97xiuNHW20x7pEp6X1toMhoRCGn9tWSbo5aDwpG77PiayVEyW+JtvPRJgmdzKDdaT/wyV7wP6KJhAhy4sTFnI7JuBc16vQMMwOs4ZgT5zl612sUEe3ACufSeeOOQxicFxgmnMPX/Lln2ALRt03//KeATxrDNS5JLNl3QsqRfgzwfpYGAt9UCAwEAAaNjMGEwHQYDVR0OBBYEFKLntJQ7phJGu7A9dfIovZy6rV6SMB8GA1UdIwQYMBaAFPMn0b1st26LXxJKvjFSvn8X1IetMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMEA0gAMEUCIEMXMU30a3M5fjhq2M2wsAe5j2d1iuRIn+mXf4BB1uVhAiEA5UynPbqF1zav4/fqPaDB3UyWArzFqi6mjXQUdHOyvXo=\n-----END CERTIFICATE-----",
+ "revoked": [
+ {
+ "date": "-----BEGIN DATE-----\nFw0yMzEwMjUyMDAwNTZa\n-----END DATE-----",
+ "reason": "123123",
+ "serial": "1"
+ }
+ ],
+ "logs": [
+ {
+ "action": "Added a new template: TLS-Server",
+ "time": "2023-10-26T03:30:41.320397+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Added a new template: User",
+ "time": "2023-10-26T03:30:49.200216+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Template User has been enabled",
+ "time": "2023-10-26T03:30:50.8239+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@18bf3d14",
+ "time": "2023-10-26T03:59:54.721842+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@8b87145",
+ "time": "2023-10-26T04:00:36.025426+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Certificate 1 is revoked with reason KEY_COMPROMISE",
+ "time": "2023-10-26T04:00:56.95637+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed CRL with 1 revoked certs.",
+ "time": "2023-10-26T04:01:08.074272+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ }
+ ],
+ "key": {
+ "p": "2e432f8270e8ad55e782324bbd008fcf82fc41eec57b5247f2e3ed0a6e118db8de1966b8754c4dae456c587cbaa859ab667c537df1929943737f7659a689044bc4b0151547c7ac79b95f676ac028ad8d81c631fd50bfe9df9c02a2a239991634fddebf186419dcf4025f3969a57c692969eb6aff718f0b8eb315235c12492d87ad047537854adf8ebcc5f69c930e2f3a51f818225e64885431049accb474b764aeebae052d50a094354f2905a600fb0e3314de9c4f8af3afc16ae2231b09511d60ecba0bf5f7abaa74a0d2208b52558f238385e06672280014a9d545547938606b947549a0ea9b6e92fa84ac42bdbe9186c1e428246d635aec1fc0209c0d1b29",
+ "e": "10001",
+ "n": "c9fed848b2b17e39b8d1fc27216e06d0a10030c32c9044e4608cac5a4b4aa4fa2f6d78f167ae5ac5650039762696f05e5aa77e734e2bb3becf3b7ba68eff30f56997c2cb307787a54300326283aaf917649e5b66aa77459309d74a70e2e54ce8c242335d7f6e4b8ba70209ea65ba9ed3bce68595e192995576b0f7bc62b8d1d6db4c7ba44a7a5f5b68321a110869fdb5649ba39683c291bbecf89ac951325be26dbcf44982677328375a4ffc3257bc0fe8a261021cb8b1316723b26e05cd7abd030cc0eb386604f9ce5eb5dac5047b7002b9f49e78e390c62705c609a730f5ff2e59f600b46dd37fff29e013c6b0cd4b924b365dd0b2a45f833c1fa58180b7d5"
+ }
+}
diff --git a/data/invalid_signed.json b/data/invalid_signed.json
new file mode 100644
index 0000000..70ab89b
--- /dev/null
+++ b/data/invalid_signed.json
@@ -0,0 +1,70 @@
+{
+ "serial": 3,
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ],
+ "signed": [
+ "-----BEGIN CERTIFICATE-----\nMIIDpTCCAo2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUxOTU5NTRaFw0yNDAyMDIxOTU5NTRaMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp9VC97vud9riIoljgkp0Bvrmhj+DTC9670a4ZhFyN0FOZ1t6j5S0soxnrMtJW4Eq9Ga/FNb/jY4LUKuxshGWdp8+2+4tiakbIg3xZE4KJSLGFNr9asme5a1nvuOPM5UPYPnDbNa/q2t4sOx4kVVPQvWpZAr6epr445askazxq/DKfENzE/GV3m+0ivRYX/sMtDOnp7rzNd1U5Q8e3Wr56aAH8al5qdVv2LXrxSe2iORL3RB8Y4um8EllILb/d+euZ3XJ/6LvpkStgDe886ZFDN7v4VKIYoZ2tFhso/srKMjgB+ZD+OTwbVZMZRqB45tfZoZsfT6uDg+FlUBsjOGA0AiqdAo77Fi90JStxz5jFNsNj24h33M/DKtwLLc4f5LFqagL9KyAZhT5ofjKRpFg13p6v7XO/WWbiB5V6QUVpOzZj/HtcpMWjT4p/6sr/LvQ4uPgfjlrkdBnaXidVorjNQk+Pnsi922g3fbULN1X7iTnWZ7fH5XCVWiQ7WdVlqeIMnlLlbQvzvL+a6hrxF/OfUUyHsIRylYn2v6rsxtM9sHM0vOSbdYpOlq/9ve6fSnucDy/Bh7RWkRqRSlgJl1PHhfGRCE3YOL7ZsucA+rKBOXVH0qCwerygMA60Hkt9WPb5nhNLTafCbn1Mhw9WzLP2aun0rIin5pBc1B0/4SO95UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYd5V/S6k8dvy5gjjKaezfaPeuYfES3mwYmu6D3WNF4k8LHtru7dbJrH9aBNhDA3mHPr8hRtNX2gskQWWcbvQlGOq6HZ6TZsdyOrZdrFw265RseJmrI/fGTYaWPiVCUf1NQM9gzwmvnSxJV0GobSaTJ1IEndEd5nW0Cnce5/wWiI0nBkm0lAVQDR+gOFqJOIEgQyUi3qzH4WmGLqWqZNfHrzkUEF/r0dkahfZatkhY9aIrLpYhebTHqcnmQqfoFlIkP0l48w7263yl/utLzZBi9iIrzLu6QLhihm6m/lR9+mU7Tk+qjNpcfbXVGxnGND7kCqj/8w501oaJ+7BVewA7Q==\n-----END CERTIFICATE-----",
+ "-----BEGIN CERTIFICATE-----\nMIIDqjCCApKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUyMDAwMzVaFw0yNDA1MTIyMDAwMzVaMCExCzAJBgNVBAYTAkNBMRIwEAYDVQQDDAlUZXN0IExlYWYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn1UL3u+532uIiiWOCSnQG+uaGP4NML3rvRrhmEXI3QU5nW3qPlLSyjGesy0lbgSr0Zr8U1v+8q0rjweijf90seejf9wei0i9dfow-jdweiojfiweojfoiweNjgtQq7GyEZZ2nz7b7i2JqRsiDfFkTgolIsYU2v1qyZ7lrWe+448zlQ9g+cNs1r+ra3iw7HiRVU9C9alkCvp6mvjjlqyRrPGr8Mp8Q3MT8ZXeb7SK9Fhf+wy0M6enuvM13VTlDx7davnpoAfxqXmp1W/YtevFJ7aI5EvdEHxji6bwSWUgtv93565ndcn/ou+mRK2AN7zzpkUM3u/hUohihna0WGyj+ysoyOAH5kP45PBtVkxlGoHjm19mhmx9Pq4OD4WVQGyM4YDQCKp0CjvsWL3QlK3HPmMU2w2PbiHfcz8Mq3Astzh/ksWpqAv0rIBmFPmh+MpGkWDXenq/tc79ZZuIHlXpBRWk7NmP8e1ykxaNPin/qyv8u9Di4+B+OWuR0GdpeJ1WiuM1CT4+eyL3baDd9tQs3VfuJOdZnt8flcJVaJDtZ1WWp4gyeUuVtC/O8v5rqGvEX859RTIewhHKVifa/quzG0z2wczS85Jt1ik6Wr/297p9Ke5wPL8GHtFaRGpFKWAmXU8eF8ZEITdg4vtmy5wD6soE5dUfSoLB6vKAwDrQeS31Y9vmeE0tNp8JufUyHD1bMs/Zq6fSsiKfmkFzUHT/hI73lQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/Pfi9EuE8NVdPPCQKcQ96ZLC8j7m9qh40nFzbtQFf5efCv8wpP8UVC5faIJJad/4HIwy6LJwBYGk6PtP1z31ovumNTHpZEs5LtRQnrIAlWRHUl9XQyo6+vZdfkX0fBS4KvnV8wHNNCJP1lBAzOM5GVk5JA1jw65AHDfVea3F5LgSClfR+w//adCYOAIO6sOJFM9fLKRp0kQKTceNf/2cX8B1WPTs6D8Rb5tauMP7JtbiuRDjaDGIHoSyISOGMbaf/+JCb/l5BlY5pv8NGBdbSeR5Lv81qzbOSAqVniAuM8rxIlB6WbYpmS1M2EhHgMjUPXo3Wxkx/VeChLviL34H5\n-----END CERTIFICATE-----"
+ ],
+ "cert": "-----BEGIN CERTIFICATE-----\nMIICYjCCAgigAwIBAgIUHflZOHj+NxNnCdHe68pxL5ed4GowCgYIKoZIzj0EAwQwJDEVMBMGA1UEAwwMVGVzdCBSb290IENBMQswCQYDVQQGEwJDQTAeFw0yMzEwMjQwNTMwMDJaFw0zMzEwMjEwNTMwMDJaMA4xDDAKBgNVBAMTA0pDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn+2EiysX45uNH8JyFuBtChADDDLJBE5GCMrFpLSqT6L2148WeuWsVlADl2JpbwXlqnfnNOK7O+zzt7po7/MPVpl8LLMHeHpUMAMmKDqvkXZJ5bZqp3RZMJ10pw4uVM6MJCM11/bkuLpwIJ6mW6ntO85oWV4ZKZVXaw97xiuNHW20x7pEp6X1toMhoRCGn9tWSbo5aDwpG77PiayVEyW+JtvPRJgmdzKDdaT/wyV7wP6KJhAhy4sTFnI7JuBc16vQMMwOs4ZgT5zl612sUEe3ACufSeeOOQxicFxgmnMPX/Lln2ALRt03//KeATxrDNS5JLNl3QsqRfgzwfpYGAt9UCAwEAAaNjMGEwHQYDVR0OBBYEFKLntJQ7phJGu7A9dfIovZy6rV6SMB8GA1UdIwQYMBaAFPMn0b1st26LXxJKvjFSvn8X1IetMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMEA0gAMEUCIEMXMU30a3M5fjhq2M2wsAe5j2d1iuRIn+mXf4BB1uVhAiEA5UynPbqF1zav4/fqPaDB3UyWArzFqi6mjXQUdHOyvXo=\n-----END CERTIFICATE-----",
+ "revoked": [
+ {
+ "date": "-----BEGIN DATE-----\nFw0yMzEwMjUyMDAwNTZa\n-----END DATE-----",
+ "reason": "KEY_COMPROMISE",
+ "serial": "1"
+ }
+ ],
+ "logs": [
+ {
+ "action": "Added a new template: TLS-Server",
+ "time": "2023-10-26T03:30:41.320397+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Added a new template: User",
+ "time": "2023-10-26T03:30:49.200216+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Template User has been enabled",
+ "time": "2023-10-26T03:30:50.8239+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@18bf3d14",
+ "time": "2023-10-26T03:59:54.721842+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@8b87145",
+ "time": "2023-10-26T04:00:36.025426+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Certificate 1 is revoked with reason KEY_COMPROMISE",
+ "time": "2023-10-26T04:00:56.95637+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed CRL with 1 revoked certs.",
+ "time": "2023-10-26T04:01:08.074272+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ }
+ ],
+ "key": {
+ "p": "2e432f8270e8ad55e782324bbd008fcf82fc41eec57b5247f2e3ed0a6e118db8de1966b8754c4dae456c587cbaa859ab667c537df1929943737f7659a689044bc4b0151547c7ac79b95f676ac028ad8d81c631fd50bfe9df9c02a2a239991634fddebf186419dcf4025f3969a57c692969eb6aff718f0b8eb315235c12492d87ad047537854adf8ebcc5f69c930e2f3a51f818225e64885431049accb474b764aeebae052d50a094354f2905a600fb0e3314de9c4f8af3afc16ae2231b09511d60ecba0bf5f7abaa74a0d2208b52558f238385e06672280014a9d545547938606b947549a0ea9b6e92fa84ac42bdbe9186c1e428246d635aec1fc0209c0d1b29",
+ "e": "10001",
+ "n": "c9fed848b2b17e39b8d1fc27216e06d0a10030c32c9044e4608cac5a4b4aa4fa2f6d78f167ae5ac5650039762696f05e5aa77e734e2bb3becf3b7ba68eff30f56997c2cb307787a54300326283aaf917649e5b66aa77459309d74a70e2e54ce8c242335d7f6e4b8ba70209ea65ba9ed3bce68595e192995576b0f7bc62b8d1d6db4c7ba44a7a5f5b68321a110869fdb5649ba39683c291bbecf89ac951325be26dbcf44982677328375a4ffc3257bc0fe8a261021cb8b1316723b26e05cd7abd030cc0eb386604f9ce5eb5dac5047b7002b9f49e78e390c62705c609a730f5ff2e59f600b46dd37fff29e013c6b0cd4b924b365dd0b2a45f833c1fa58180b7d5"
+ }
+}
diff --git a/data/invalid_template_1.json b/data/invalid_template_1.json
new file mode 100644
index 0000000..a34b09e
--- /dev/null
+++ b/data/invalid_template_1.json
@@ -0,0 +1,14 @@
+{
+ "templates": [
+ {
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ]
+}
diff --git a/data/invalid_template_2.json b/data/invalid_template_2.json
new file mode 100644
index 0000000..e023b28
--- /dev/null
+++ b/data/invalid_template_2.json
@@ -0,0 +1,15 @@
+{
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ]
+}
diff --git a/data/valid_full.json b/data/valid_full.json
new file mode 100644
index 0000000..5a0fea6
--- /dev/null
+++ b/data/valid_full.json
@@ -0,0 +1,70 @@
+{
+ "serial": 3,
+ "templates": [
+ {
+ "name": "TLS-Server",
+ "validity": 30,
+ "enabled": false
+ },
+ {
+ "subject": "-----BEGIN DISTINGUISHED NAME-----\nMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNB\n-----END DISTINGUISHED NAME-----",
+ "name": "User",
+ "validity": 100,
+ "enabled": true
+ }
+ ],
+ "signed": [
+ "-----BEGIN CERTIFICATE-----\nMIIDpTCCAo2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUxOTU5NTRaFw0yNDAyMDIxOTU5NTRaMBwxDTALBgNVBAMTBFVzZXIxCzAJBgNVBAYTAkNBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp9VC97vud9riIoljgkp0Bvrmhj+DTC9670a4ZhFyN0FOZ1t6j5S0soxnrMtJW4Eq9Ga/FNb/jY4LUKuxshGWdp8+2+4tiakbIg3xZE4KJSLGFNr9asme5a1nvuOPM5UPYPnDbNa/q2t4sOx4kVVPQvWpZAr6epr445askazxq/DKfENzE/GV3m+0ivRYX/sMtDOnp7rzNd1U5Q8e3Wr56aAH8al5qdVv2LXrxSe2iORL3RB8Y4um8EllILb/d+euZ3XJ/6LvpkStgDe886ZFDN7v4VKIYoZ2tFhso/srKMjgB+ZD+OTwbVZMZRqB45tfZoZsfT6uDg+FlUBsjOGA0AiqdAo77Fi90JStxz5jFNsNj24h33M/DKtwLLc4f5LFqagL9KyAZhT5ofjKRpFg13p6v7XO/WWbiB5V6QUVpOzZj/HtcpMWjT4p/6sr/LvQ4uPgfjlrkdBnaXidVorjNQk+Pnsi922g3fbULN1X7iTnWZ7fH5XCVWiQ7WdVlqeIMnlLlbQvzvL+a6hrxF/OfUUyHsIRylYn2v6rsxtM9sHM0vOSbdYpOlq/9ve6fSnucDy/Bh7RWkRqRSlgJl1PHhfGRCE3YOL7ZsucA+rKBOXVH0qCwerygMA60Hkt9WPb5nhNLTafCbn1Mhw9WzLP2aun0rIin5pBc1B0/4SO95UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAYd5V/S6k8dvy5gjjKaezfaPeuYfES3mwYmu6D3WNF4k8LHtru7dbJrH9aBNhDA3mHPr8hRtNX2gskQWWcbvQlGOq6HZ6TZsdyOrZdrFw265RseJmrI/fGTYaWPiVCUf1NQM9gzwmvnSxJV0GobSaTJ1IEndEd5nW0Cnce5/wWiI0nBkm0lAVQDR+gOFqJOIEgQyUi3qzH4WmGLqWqZNfHrzkUEF/r0dkahfZatkhY9aIrLpYhebTHqcnmQqfoFlIkP0l48w7263yl/utLzZBi9iIrzLu6QLhihm6m/lR9+mU7Tk+qjNpcfbXVGxnGND7kCqj/8w501oaJ+7BVewA7Q==\n-----END CERTIFICATE-----",
+ "-----BEGIN CERTIFICATE-----\nMIIDqjCCApKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNKQ0EwIBgPMjAyMzEwMjUyMDAwMzVaFw0yNDA1MTIyMDAwMzVaMCExCzAJBgNVBAYTAkNBMRIwEAYDVQQDDAlUZXN0IExlYWYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCn1UL3u+532uIiiWOCSnQG+uaGP4NML3rvRrhmEXI3QU5nW3qPlLSyjGesy0lbgSr0Zr8U1v+NjgtQq7GyEZZ2nz7b7i2JqRsiDfFkTgolIsYU2v1qyZ7lrWe+448zlQ9g+cNs1r+ra3iw7HiRVU9C9alkCvp6mvjjlqyRrPGr8Mp8Q3MT8ZXeb7SK9Fhf+wy0M6enuvM13VTlDx7davnpoAfxqXmp1W/YtevFJ7aI5EvdEHxji6bwSWUgtv93565ndcn/ou+mRK2AN7zzpkUM3u/hUohihna0WGyj+ysoyOAH5kP45PBtVkxlGoHjm19mhmx9Pq4OD4WVQGyM4YDQCKp0CjvsWL3QlK3HPmMU2w2PbiHfcz8Mq3Astzh/ksWpqAv0rIBmFPmh+MpGkWDXenq/tc79ZZuIHlXpBRWk7NmP8e1ykxaNPin/qyv8u9Di4+B+OWuR0GdpeJ1WiuM1CT4+eyL3baDd9tQs3VfuJOdZnt8flcJVaJDtZ1WWp4gyeUuVtC/O8v5rqGvEX859RTIewhHKVifa/quzG0z2wczS85Jt1ik6Wr/297p9Ke5wPL8GHtFaRGpFKWAmXU8eF8ZEITdg4vtmy5wD6soE5dUfSoLB6vKAwDrQeS31Y9vmeE0tNp8JufUyHD1bMs/Zq6fSsiKfmkFzUHT/hI73lQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA/Pfi9EuE8NVdPPCQKcQ96ZLC8j7m9qh40nFzbtQFf5efCv8wpP8UVC5faIJJad/4HIwy6LJwBYGk6PtP1z31ovumNTHpZEs5LtRQnrIAlWRHUl9XQyo6+vZdfkX0fBS4KvnV8wHNNCJP1lBAzOM5GVk5JA1jw65AHDfVea3F5LgSClfR+w//adCYOAIO6sOJFM9fLKRp0kQKTceNf/2cX8B1WPTs6D8Rb5tauMP7JtbiuRDjaDGIHoSyISOGMbaf/+JCb/l5BlY5pv8NGBdbSeR5Lv81qzbOSAqVniAuM8rxIlB6WbYpmS1M2EhHgMjUPXo3Wxkx/VeChLviL34H5\n-----END CERTIFICATE-----"
+ ],
+ "cert": "-----BEGIN CERTIFICATE-----\nMIICYjCCAgigAwIBAgIUHflZOHj+NxNnCdHe68pxL5ed4GowCgYIKoZIzj0EAwQwJDEVMBMGA1UEAwwMVGVzdCBSb290IENBMQswCQYDVQQGEwJDQTAeFw0yMzEwMjQwNTMwMDJaFw0zMzEwMjEwNTMwMDJaMA4xDDAKBgNVBAMTA0pDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMn+2EiysX45uNH8JyFuBtChADDDLJBE5GCMrFpLSqT6L2148WeuWsVlADl2JpbwXlqnfnNOK7O+zzt7po7/MPVpl8LLMHeHpUMAMmKDqvkXZJ5bZqp3RZMJ10pw4uVM6MJCM11/bkuLpwIJ6mW6ntO85oWV4ZKZVXaw97xiuNHW20x7pEp6X1toMhoRCGn9tWSbo5aDwpG77PiayVEyW+JtvPRJgmdzKDdaT/wyV7wP6KJhAhy4sTFnI7JuBc16vQMMwOs4ZgT5zl612sUEe3ACufSeeOOQxicFxgmnMPX/Lln2ALRt03//KeATxrDNS5JLNl3QsqRfgzwfpYGAt9UCAwEAAaNjMGEwHQYDVR0OBBYEFKLntJQ7phJGu7A9dfIovZy6rV6SMB8GA1UdIwQYMBaAFPMn0b1st26LXxJKvjFSvn8X1IetMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMEA0gAMEUCIEMXMU30a3M5fjhq2M2wsAe5j2d1iuRIn+mXf4BB1uVhAiEA5UynPbqF1zav4/fqPaDB3UyWArzFqi6mjXQUdHOyvXo=\n-----END CERTIFICATE-----",
+ "revoked": [
+ {
+ "date": "-----BEGIN DATE-----\nFw0yMzEwMjUyMDAwNTZa\n-----END DATE-----",
+ "reason": "KEY_COMPROMISE",
+ "serial": "1"
+ }
+ ],
+ "logs": [
+ {
+ "action": "Added a new template: TLS-Server",
+ "time": "2023-10-26T03:30:41.320397+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Added a new template: User",
+ "time": "2023-10-26T03:30:49.200216+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Template User has been enabled",
+ "time": "2023-10-26T03:30:50.8239+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@18bf3d14",
+ "time": "2023-10-26T03:59:54.721842+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed a cert with serial number model.asn1.Int@8b87145",
+ "time": "2023-10-26T04:00:36.025426+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Certificate 1 is revoked with reason KEY_COMPROMISE",
+ "time": "2023-10-26T04:00:56.95637+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ },
+ {
+ "action": "Signed CRL with 1 revoked certs.",
+ "time": "2023-10-26T04:01:08.074272+08:00[Asia/Shanghai]",
+ "user": "yuuta"
+ }
+ ],
+ "key": {
+ "p": "2e432f8270e8ad55e782324bbd008fcf82fc41eec57b5247f2e3ed0a6e118db8de1966b8754c4dae456c587cbaa859ab667c537df1929943737f7659a689044bc4b0151547c7ac79b95f676ac028ad8d81c631fd50bfe9df9c02a2a239991634fddebf186419dcf4025f3969a57c692969eb6aff718f0b8eb315235c12492d87ad047537854adf8ebcc5f69c930e2f3a51f818225e64885431049accb474b764aeebae052d50a094354f2905a600fb0e3314de9c4f8af3afc16ae2231b09511d60ecba0bf5f7abaa74a0d2208b52558f238385e06672280014a9d545547938606b947549a0ea9b6e92fa84ac42bdbe9186c1e428246d635aec1fc0209c0d1b29",
+ "e": "10001",
+ "n": "c9fed848b2b17e39b8d1fc27216e06d0a10030c32c9044e4608cac5a4b4aa4fa2f6d78f167ae5ac5650039762696f05e5aa77e734e2bb3becf3b7ba68eff30f56997c2cb307787a54300326283aaf917649e5b66aa77459309d74a70e2e54ce8c242335d7f6e4b8ba70209ea65ba9ed3bce68595e192995576b0f7bc62b8d1d6db4c7ba44a7a5f5b68321a110869fdb5649ba39683c291bbecf89ac951325be26dbcf44982677328375a4ffc3257bc0fe8a261021cb8b1316723b26e05cd7abd030cc0eb386604f9ce5eb5dac5047b7002b9f49e78e390c62705c609a730f5ff2e59f600b46dd37fff29e013c6b0cd4b924b365dd0b2a45f833c1fa58180b7d5"
+ }
+}
diff --git a/data/valid_minimal.json b/data/valid_minimal.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/data/valid_minimal.json
@@ -0,0 +1 @@
+{}
diff --git a/src/main/model/asn1/exceptions/InvalidDBException.java b/src/main/model/asn1/exceptions/InvalidDBException.java
new file mode 100644
index 0000000..4068a4b
--- /dev/null
+++ b/src/main/model/asn1/exceptions/InvalidDBException.java
@@ -0,0 +1,10 @@
+package model.asn1.exceptions;
+
+/**
+ * The database is invalid.
+ */
+public class InvalidDBException extends RuntimeException {
+ public InvalidDBException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/model/ca/CertificationAuthority.java b/src/main/model/ca/CertificationAuthority.java
index feb557c..038d209 100644
--- a/src/main/model/ca/CertificationAuthority.java
+++ b/src/main/model/ca/CertificationAuthority.java
@@ -35,6 +35,8 @@ import java.util.stream.Stream;
* Holds a CA private key, its certificate, signed / revoked list, template list, and logs list.
*/
public class CertificationAuthority {
+ public static final int SERIAL_DEFAULT = 1;
+
/**
* The RSA2048 private key.
*/
@@ -81,14 +83,45 @@ public class CertificationAuthority {
private final String user;
/**
- * EFFECT: Init with a null key and null certificate, empty signed, revoked template, and log list, serial at 1, and
- * user "yuuta".
+ * EFFECT: Init with the given parameters and user "yuuta".
+ * Throws {@link NoSuchAlgorithmException} if the key is specified but RSA is not supported.
+ * Throws {@link InvalidKeySpecException} if the key specified is invalid.
+ * Throws {@link InvalidCAException} or {@link ParseException} if the CA specified is invalid.
+ * REQUIRES: n / p / e must be either all null or all non-null containing RSA2048 module and exponents.
+ * If certificate is non-null, n / p / e must be non-null.
+ */
+ public CertificationAuthority(BigInteger n, BigInteger p, BigInteger e,
+ Certificate certificate,
+ List<Certificate> signed,
+ int serial,
+ List<RevokedCertificate> revoked,
+ List<Template> templates,
+ List<AuditLogEntry> logs)
+ throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidCAException, ParseException {
+ if (n != null) {
+ setKey(n, p, e);
+ }
+ if (certificate != null) {
+ validateCertificate(certificate);
+ }
+ this.certificate = certificate;
+ this.signed = new ArrayList<>(signed);
+ this.serial = serial;
+ this.revoked = new ArrayList<>(revoked);
+ this.templates = new ArrayList<>(templates);
+ this.logs = new ArrayList<>(logs);
+ this.user = "yuuta";
+ }
+
+ /**
+ * EFFECT: Init with a null key and null certificate, empty signed, revoked template, and log list,
+ * serial at SERIAL_DEFAULT, and user "yuuta".
*/
public CertificationAuthority() {
this.key = null;
this.publicKey = null;
this.certificate = null;
- this.serial = 1;
+ this.serial = SERIAL_DEFAULT;
this.signed = new ArrayList<>();
this.revoked = new ArrayList<>();
this.templates = new ArrayList<>();
@@ -111,17 +144,29 @@ public class CertificationAuthority {
}
/**
- * EFFECTS: Load the RSA private and public exponents. This action will be logged.
+ * EFFECTS: Load the RSA private and public exponents.
* Throws {@link NoSuchAlgorithmException} if RSA is not available on the platform.
* Throws {@link InvalidKeySpecException} if the input is invalid.
* REQUIRES: getPublicKey() is null (i.e., no private key had been installed)
* MODIFIES: this
*/
- public void loadKey(BigInteger n, BigInteger p, BigInteger e)
+ private void setKey(BigInteger n, BigInteger p, BigInteger e)
throws NoSuchAlgorithmException, InvalidKeySpecException {
this.key = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new RSAPrivateKeySpec(n, p));
this.publicKey =
(RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(n, e));
+ }
+
+ /**
+ * EFFECTS: Load the RSA private and public exponents. This action will be logged.
+ * Throws {@link NoSuchAlgorithmException} if RSA is not available on the platform.
+ * Throws {@link InvalidKeySpecException} if the input is invalid.
+ * REQUIRES: getPublicKey() is null (i.e., no private key had been installed)
+ * MODIFIES: this
+ */
+ public void loadKey(BigInteger n, BigInteger p, BigInteger e)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ setKey(n, p, e);
log("Installed CA private key.");
}
@@ -185,6 +230,22 @@ public class CertificationAuthority {
}
/**
+ * EFFECT: Validate the CA certificate. Throws {@link InvalidCAException} if any of the
+ * following are violated:
+ * - It must be a v3 certificate
+ * - The new certificate must have the same algorithm and public key as getPublicKey()
+ * - It must have basicConstraints { cA = TRUE }
+ * - It must contain key usage Digital Signature, Certificate Sign, CRL Sign
+ * Throws {@link ParseException} if the cert has invalid extension values.
+ */
+ private void validateCertificate(Certificate certificate) throws InvalidCAException, ParseException {
+ validateCACertificateVersion(certificate);
+ validateCACertificatePublicKey(certificate);
+ validateCACertificateBasicConstraints(certificate);
+ validateCACertificateKeyUsage(certificate);
+ }
+
+ /**
* EFFECT: Install the CA certificate. Throws {@link InvalidCAException} if any of the
* following are violated:
* - It must be a v3 certificate
@@ -198,10 +259,7 @@ public class CertificationAuthority {
* MODIFIES: this
*/
public void installCertificate(Certificate certificate) throws InvalidCAException, ParseException {
- validateCACertificateVersion(certificate);
- validateCACertificatePublicKey(certificate);
- validateCACertificateBasicConstraints(certificate);
- validateCACertificateKeyUsage(certificate);
+ validateCertificate(certificate);
this.certificate = certificate;
log("CA certificate is installed.");
}
@@ -477,4 +535,8 @@ public class CertificationAuthority {
public RSAPublicKey getPublicKey() {
return publicKey;
}
+
+ public RSAPrivateKey getKey() {
+ return key;
+ }
}
diff --git a/src/main/persistence/Decoder.java b/src/main/persistence/Decoder.java
new file mode 100644
index 0000000..799a4ad
--- /dev/null
+++ b/src/main/persistence/Decoder.java
@@ -0,0 +1,284 @@
+package persistence;
+
+import model.asn1.ASN1Object;
+import model.asn1.ASN1Time;
+import model.asn1.Int;
+import model.asn1.exceptions.InvalidCAException;
+import model.asn1.exceptions.InvalidDBException;
+import model.asn1.exceptions.ParseException;
+import model.asn1.parsing.BytesReader;
+import model.ca.AuditLogEntry;
+import model.ca.CertificationAuthority;
+import model.ca.Template;
+import model.pki.cert.Certificate;
+import model.pki.crl.Reason;
+import model.pki.crl.RevokedCertificate;
+import model.x501.Name;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import ui.Utils;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+/**
+ * Util class that encodes / decodes the CA into JSON and vice-versa.
+ */
+public class Decoder {
+ /**
+ * EFFECTS: Convert the list into a JSONArray using the supplied mapper and add to the JSONObject. Nothing is added
+ * if the given list is empty.
+ */
+ private static <T> void encodeList(JSONObject o, String key, List<T> list, Function<? super T, ?> mapper) {
+ if (list.isEmpty()) {
+ return;
+ }
+ o.put(key, new JSONArray(list.stream().map(mapper).collect(Collectors.toList())));
+ }
+
+ /**
+ * EFFECTS: Decode the JSONArray into a list of specific objects, using the given mapper. Return empty list if the
+ * o does not contain key.
+ * REQUIRES: If o contains key, it must be a JSONArray.
+ */
+ private static <T> List<T> decodeListFromString(final JSONObject o, String key,
+ Function<? super String, ? extends T> mapper) {
+ return o.keySet().contains(key)
+ ? StreamSupport.stream(o.getJSONArray(key).spliterator(), false)
+ .map(obj -> mapper.apply((String) obj))
+ .collect(Collectors.toList())
+ : Collections.emptyList();
+ }
+
+ /**
+ * EFFECTS: Decode the JSONArray into a list of specific objects, using the given mapper. Return empty list if the
+ * o does not contain key.
+ * REQUIRES: If o contains key, it must be a JSONArray.
+ */
+ private static <T> List<T> decodeListFromObject(final JSONObject o, String key,
+ Function<? super JSONObject, ? extends T> mapper) {
+ return o.keySet().contains(key)
+ ? StreamSupport.stream(o.getJSONArray(key).spliterator(), false)
+ .map(obj -> mapper.apply((JSONObject) obj))
+ .collect(Collectors.toList())
+ : Collections.emptyList();
+ }
+
+ /**
+ * EFFECTS: Decode the JSON object into CertificationAuthority
+ * Throws {@link InvalidDBException} if the JSON is invalid.
+ * Throws {@link NoSuchAlgorithmException} if RSA is not supported.
+ * JSON should be <pre>
+ * {
+ * "key": {
+ * "n": "octet string",
+ * "p": "octet string",
+ * "e": "octet string"
+ * },
+ * "cert": "-----BEGIN CERTIFICATE-----\n...",
+ * "templates": [
+ * {
+ * "name": "123",
+ * "enabled": true,
+ * "subject": "-----BEGIN DISTINGUISHED NAME-----\n...",
+ * "validity": 100
+ * }
+ * ],
+ * "signed": [
+ * "-----BEGIN CERTIFICATE-----\n...",
+ * ],
+ * "revoked": [
+ * {
+ * "serial": "octet string",
+ * "date": "-----BEGIN DATE-----\n",
+ * "reason": "KEY_COMPROMISED"
+ * }
+ * ],
+ * "logs": [
+ * {
+ * "user": "",
+ * "time": "ISO 8601",
+ * "action": ""
+ * }
+ * ],
+ * "serial": 1
+ * }
+ * </pre>
+ * Missing fields will be default.
+ */
+ public static CertificationAuthority decodeCA(JSONObject o) throws InvalidDBException, NoSuchAlgorithmException {
+ try {
+ Certificate caCert = o.keySet().contains("cert") ? decodeCertificate(o.getString("cert")) : null;
+ final List<Template> templates = decodeListFromObject(o, "templates", Decoder::decodeTemplate);
+ final List<Certificate> signed = decodeListFromString(o, "signed", Decoder::decodeCertificate);
+ final List<RevokedCertificate> revoked =
+ decodeListFromObject(o, "revoked", Decoder::decodeRevokedCertificate);
+ final List<AuditLogEntry> logs = decodeListFromObject(o, "logs", Decoder::decodeLog);
+ final int serial = o.keySet().contains("serial") ? o.getInt("serial") :
+ CertificationAuthority.SERIAL_DEFAULT;
+ final BigInteger n = o.keySet().contains("key")
+ ? new BigInteger(o.getJSONObject("key").getString("n"), 16) : null;
+ final BigInteger p = o.keySet().contains("key")
+ ? new BigInteger(o.getJSONObject("key").getString("p"), 16) : null;
+ final BigInteger e = o.keySet().contains("key")
+ ? new BigInteger(o.getJSONObject("key").getString("e"), 16) : null;
+ return new CertificationAuthority(n, p, e, caCert, signed, serial, revoked, templates, logs);
+ } catch (InvalidKeySpecException | NullPointerException | ParseException
+ | InvalidCAException | NumberFormatException | JSONException ex) {
+ throw new InvalidDBException("Invalid JSON format", ex);
+ }
+ }
+
+ /**
+ * EFFECTS: Encode the CertificationAuthority to JSON.
+ */
+ public static JSONObject encodeCA(CertificationAuthority ca) {
+ JSONObject obj = new JSONObject();
+ if (ca.getKey() != null) {
+ obj.put("key", encodeKey(ca.getKey(), ca.getPublicKey()));
+ }
+ if (ca.getCertificate() != null) {
+ obj.put("cert", encodeCertificate(ca.getCertificate()));
+ }
+ if (ca.getSerial() != CertificationAuthority.SERIAL_DEFAULT) {
+ obj.put("serial", ca.getSerial());
+ }
+ encodeList(obj, "templates", ca.getTemplates(), Decoder::encodeTemplate);
+ encodeList(obj, "signed", ca.getSigned(), Decoder::encodeCertificate);
+ encodeList(obj, "revoked", ca.getRevoked(), Decoder::encodeRevokedCertificate);
+ encodeList(obj, "logs", ca.getLogs(), Decoder::encodeLog);
+ return obj;
+ }
+
+ /**
+ * EFFECTS: Encode the n / p / e modulus and exponents into JSON.
+ */
+ private static JSONObject encodeKey(RSAPrivateKey privateKey, RSAPublicKey publicKey) {
+ JSONObject obj = new JSONObject();
+ obj.put("n", privateKey.getModulus().toString(16));
+ obj.put("p", privateKey.getPrivateExponent().toString(16));
+ obj.put("e", publicKey.getPublicExponent().toString(16));
+ return obj;
+ }
+
+ /**
+ * EFFECTS: Encode the log entry into a JSON object.
+ */
+ private static JSONObject encodeLog(AuditLogEntry entry) {
+ JSONObject o = new JSONObject();
+ o.put("user", entry.getUser());
+ o.put("time", entry.getTime().format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+ o.put("action", entry.getAction());
+ return o;
+ }
+
+ /**
+ * EFFECTS: Decode the log entry from JSON object.
+ * Throws {@link InvalidDBException} if the input is invalid.
+ * REQUIRES: { "user": "", "time": "ISO_LOCAL_DATE_TIME", "action": "" }
+ */
+ private static AuditLogEntry decodeLog(JSONObject o) throws InvalidDBException {
+ try {
+ return new AuditLogEntry(o.getString("user"),
+ ZonedDateTime.parse(o.getString("time"), DateTimeFormatter.ISO_ZONED_DATE_TIME),
+ o.getString("action"));
+ } catch (DateTimeParseException e) {
+ throw new InvalidDBException("Invalid date", e);
+ }
+ }
+
+ /**
+ * EFFECTS: Encode the templates into a JSON array.
+ */
+ private static JSONObject encodeTemplate(Template template) {
+ JSONObject o = new JSONObject();
+ o.put("name", template.getName());
+ o.put("enabled", template.isEnabled());
+ if (template.getSubject() != null) {
+ o.put("subject", Utils.toPEM(template.getSubject().encodeDER(), "DISTINGUISHED NAME"));
+ }
+ o.put("validity", template.getValidity());
+ return o;
+ }
+
+ /**
+ * EFFECTS: Decode the JSON object into a Template.
+ * Throws {@link InvalidDBException} if the subject is invalid.
+ * REQUIRES: { "name": "string", "enabled": boolean, "name": "-----BEGIN DISTINGUISHED NAME-----", "validity": long}
+ */
+ private static Template decodeTemplate(JSONObject o) throws InvalidDBException {
+ try {
+ return new Template(o.getString("name"),
+ o.getBoolean("enabled"),
+ o.keySet().contains("subject") ? new Name(new BytesReader(Utils.parsePEM(
+ Utils.byteToByte(o.getString("subject").getBytes()), "DISTINGUISHED NAME")),
+ false) : null,
+ o.getLong("validity"));
+ } catch (ParseException e) {
+ throw new InvalidDBException("Invalid template", e);
+ }
+ }
+
+ /**
+ * EFFECTS: Encode the certificate to PEM.
+ */
+ private static String encodeCertificate(Certificate cert) {
+ return Utils.toPEM(cert.encodeDER(), "CERTIFICATE");
+ }
+
+ /**
+ * EFFECTS: Decode the JSON pem into Certificate.
+ * Throws {@link InvalidDBException} if the value cannot be parsed.
+ * REQUIRES: -----BEGIN CERTIFICATE----- ...
+ */
+ private static Certificate decodeCertificate(String pem) {
+ try {
+ return new Certificate(new BytesReader(
+ Utils.parsePEM(Utils.byteToByte(pem.getBytes(StandardCharsets.UTF_8)), "CERTIFICATE")),
+ false);
+ } catch (ParseException e) {
+ throw new InvalidDBException("Invalid certificate", e);
+ }
+ }
+
+ /**
+ * EFFECTS: Encode the RevokedCertificate into a JSON object.
+ */
+ private static JSONObject encodeRevokedCertificate(RevokedCertificate rev) {
+ JSONObject o = new JSONObject();
+ o.put("serial", rev.getSerialNumber().getValue().toString(16));
+ o.put("date", Utils.toPEM(rev.getRevocationDate().encodeDER(), "DATE"));
+ o.put("reason", rev.getReason().name());
+ return o;
+ }
+
+ /**
+ * EFFECTS: Decode the RevokedCertificate from JSON.
+ * Throws {@link InvalidDBException} if the value cannot be parsed.
+ * REQUIRES: { "serial": "12ab", "date": "-----BEGIN DATE-----", "reason": "KEY_COMPROMISED" }
+ */
+ private static RevokedCertificate decodeRevokedCertificate(JSONObject o) {
+ try {
+ return new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null,
+ new Int(Int.TAG, null, new BigInteger(o.getString("serial"), 16)),
+ (ASN1Time) ASN1Object.parse(new BytesReader(Utils.parsePEM(Utils.byteToByte(o.getString("date")
+ .getBytes(StandardCharsets.UTF_8)), "DATE")), false),
+ Reason.valueOf(o.getString("reason")));
+ } catch (ParseException | IllegalArgumentException e) {
+ throw new InvalidDBException("Invalid revoked certificate", e);
+ }
+ }
+}
diff --git a/src/main/persistence/FS.java b/src/main/persistence/FS.java
new file mode 100644
index 0000000..3b5222d
--- /dev/null
+++ b/src/main/persistence/FS.java
@@ -0,0 +1,46 @@
+package persistence;
+
+import model.asn1.exceptions.InvalidDBException;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+/**
+ * Util class for file-system IO.
+ */
+public class FS {
+ /**
+ * EFFECTS: Read and decode the content of given path as UTF-8 into JSON.
+ * Throws {@link InvalidDBException} if IO error occurs or the input cannot be parsed.
+ */
+ public static JSONObject read(Path path) throws InvalidDBException {
+ try {
+ final InputStream fd = Files.newInputStream(path, StandardOpenOption.READ);
+ final JSONObject obj = new JSONObject(new JSONTokener(fd));
+ fd.close();
+ return obj;
+ } catch (IOException | JSONException e) {
+ throw new InvalidDBException("Cannot read or parse the file", e);
+ }
+ }
+
+ /**
+ * EFFECTS: Write the UTF-8 encoded JSON to the given path.
+ * open(2) parameters: <pre>O_WRONLY | O_TRUNC | O_CREAT</pre>
+ * Throws {@link IOException} if the file cannot be opened or written.
+ */
+ public static void write(Path path, JSONObject obj) throws IOException {
+ final OutputStream fd = Files.newOutputStream(path,
+ StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
+ fd.write(obj.toString().getBytes(StandardCharsets.UTF_8));
+ fd.close();
+ }
+}
diff --git a/src/main/ui/IssueScreen.java b/src/main/ui/IssueScreen.java
index 5e3ad50..3e70a0a 100644
--- a/src/main/ui/IssueScreen.java
+++ b/src/main/ui/IssueScreen.java
@@ -4,6 +4,7 @@ import model.asn1.exceptions.ParseException;
import model.ca.Template;
import model.csr.CertificationRequest;
import model.pki.cert.Certificate;
+import model.x501.Name;
/**
* The screen that accepts a CSR and template and allows user to change its properties and issue.
@@ -65,6 +66,7 @@ public class IssueScreen implements UIHandler {
public void commit() {
try {
Certificate certificate = session.getCa().signCert(incomingCSR.getCertificationRequestInfo(), template);
+ session.save();
System.out.println(Utils.toPEM(certificate.encodeDER(), "CERTIFICATE"));
session.setScreen(Screen.MAIN);
} catch (Throwable e) {
@@ -78,7 +80,11 @@ public class IssueScreen implements UIHandler {
*/
private void handleIssueSetSubject(String val) {
try {
- template = new Template(template.getName(), template.isEnabled(), val, template.getValidity());
+ if (val == null) {
+ template = new Template(template.getName(), template.isEnabled(), (Name) null, template.getValidity());
+ } else {
+ template = new Template(template.getName(), template.isEnabled(), val, template.getValidity());
+ }
} catch (ParseException e) {
System.out.println(e.getMessage());
}
diff --git a/src/main/ui/JCA.java b/src/main/ui/JCA.java
index 882c546..420ec10 100644
--- a/src/main/ui/JCA.java
+++ b/src/main/ui/JCA.java
@@ -1,9 +1,14 @@
package ui;
+import model.asn1.exceptions.InvalidDBException;
import model.asn1.exceptions.ParseException;
import model.ca.CertificationAuthority;
+import persistence.Decoder;
+import persistence.FS;
+import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Scanner;
@@ -13,6 +18,11 @@ import java.util.Scanner;
*/
public class JCA {
/**
+ * Default db file (./data/ca.json)
+ */
+ private static final Path PATH_DEFAULT = Path.of("data", "ca.json");
+
+ /**
* Instances of the five screens;
*/
private final UIHandler mainScreen;
@@ -23,13 +33,18 @@ public class JCA {
/**
* The CA
*/
- private final CertificationAuthority ca;
+ private CertificationAuthority ca;
/**
* The current screen.
*/
private UIHandler screen;
/**
+ * There are unsaved changes.
+ */
+ private boolean unsaved = false;
+
+ /**
* EFFECTS: Init with main screen and empty CA. No private key and no CA cert.
* Throws {@link NoSuchAlgorithmException} when crypto issue happens.
*/
@@ -106,34 +121,72 @@ public class JCA {
screen.enter(args);
}
+ /**
+ * EFFECTS: Read the database file and replace all local states.
+ * MODIFIES: this
+ */
+ private void load() {
+ if (unsaved) {
+ System.out.println("Current database is not saved yet.");
+ return;
+ }
+ try {
+ this.ca = Decoder.decodeCA(FS.read(PATH_DEFAULT));
+ } catch (InvalidDBException | NoSuchAlgorithmException e) {
+ System.out.println(e.getMessage());
+ if (e.getCause() != null) {
+ System.out.println(e.getCause().getMessage());
+ e.getCause().printStackTrace();
+ }
+ }
+ }
+
private void handleLine(String... args) {
if (args[0].equals("log")) {
ca.getLogs().forEach(System.out::println);
return;
}
- switch (args[0]) {
- case "help":
- screen.help();
- System.out.println("log\tView audit logs");
- break;
- case "show":
- screen.show();
- break;
- case "commit":
- screen.commit();
- break;
- case "exit":
- setScreen(screen.exit());
- break;
- default:
- screen.command(args);
- break;
+ if ("help".equals(args[0])) {
+ screen.help();
+ System.out.println("log\tView audit logs");
+ System.out.println("load\tLoad database");
+ System.out.println("save\tSave database");
+ } else if ("commit".equals(args[0])) {
+ screen.commit();
+ } else if ("show".equals(args[0])) {
+ screen.show();
+ } else if ("exit".equals(args[0])) {
+ setScreen(screen.exit());
+ } else if ("save".equals(args[0])) {
+ save();
+ } else if ("load".equals(args[0])) {
+ load();
+ } else {
+ screen.command(args);
}
printPS1();
}
+ /**
+ * EFFECTS: Print the '*user@JCA PS1' line
+ */
private void printPS1() {
- System.out.printf("%s@JCA %s ", ca.getUser(), screen.getPS1());
+ System.out.printf("%s%s@JCA %s ",
+ unsaved ? "*" : "",
+ ca.getUser(),
+ screen.getPS1());
+ }
+
+ /**
+ * EFFECTS: Save the DB
+ */
+ public void save() {
+ try {
+ FS.write(PATH_DEFAULT, Decoder.encodeCA(ca));
+ unsaved = false;
+ } catch (IOException e) {
+ System.out.println(e.getMessage());
+ }
}
/**
@@ -152,6 +205,10 @@ public class JCA {
}
}
+ public void setUnsaved(boolean unsaved) {
+ this.unsaved = unsaved;
+ }
+
public CertificationAuthority getCa() {
return ca;
}
diff --git a/src/main/ui/MainScreen.java b/src/main/ui/MainScreen.java
index 2eaf882..8a85881 100644
--- a/src/main/ui/MainScreen.java
+++ b/src/main/ui/MainScreen.java
@@ -7,6 +7,7 @@ import model.asn1.parsing.BytesReader;
import model.ca.Template;
import model.csr.CertificationRequest;
import model.pki.cert.Certificate;
+import model.pki.crl.CertificateList;
import model.pki.crl.Reason;
import model.pki.crl.RevokedCertificate;
@@ -138,6 +139,7 @@ public class MainScreen implements UIHandler {
session.getCa().revoke(new RevokedCertificate(ASN1Object.TAG_SEQUENCE, null,
c.getCertificate().getSerialNumber(),
new UtcTime(UtcTime.TAG, null, ZonedDateTime.now(ZoneId.of("UTC"))), reason));
+ session.save();
} catch (IllegalArgumentException ignored) {
System.out.println("Illegal serial number or reason");
}
@@ -177,7 +179,9 @@ public class MainScreen implements UIHandler {
return;
}
try {
- System.out.println(Utils.toPEM(session.getCa().signCRL().encodeDER(), "X509 CRL"));
+ CertificateList crl = session.getCa().signCRL();
+ session.save();
+ System.out.println(Utils.toPEM(crl.encodeDER(), "X509 CRL"));
} catch (Throwable e) {
System.out.println(e.getMessage());
}
diff --git a/src/main/ui/MgmtScreen.java b/src/main/ui/MgmtScreen.java
index 0a25bfe..c630a34 100644
--- a/src/main/ui/MgmtScreen.java
+++ b/src/main/ui/MgmtScreen.java
@@ -73,6 +73,7 @@ public class MgmtScreen implements UIHandler {
try {
CertificationRequest req = session.getCa().signCSR();
System.out.println(Utils.toPEM(req.encodeDER(), "CERTIFICATE REQUEST"));
+ session.setUnsaved(true);
} catch (Throwable e) {
System.out.println(e.getMessage());
}
@@ -90,6 +91,7 @@ public class MgmtScreen implements UIHandler {
final Byte[] in = session.handleInputPEM("CERTIFICATE");
final Certificate cert = new Certificate(new BytesReader(in), false);
session.getCa().installCertificate(cert);
+ session.setUnsaved(true);
} catch (InvalidCAException | ParseException e) {
System.out.println(e.getMessage());
}
@@ -105,6 +107,7 @@ public class MgmtScreen implements UIHandler {
}
try {
session.getCa().generateKey();
+ session.setUnsaved(true);
} catch (NoSuchAlgorithmException e) {
System.out.println(e.getMessage());
}
diff --git a/src/main/ui/TemplateSetScreen.java b/src/main/ui/TemplateSetScreen.java
index a0b39c1..30d25b9 100644
--- a/src/main/ui/TemplateSetScreen.java
+++ b/src/main/ui/TemplateSetScreen.java
@@ -2,6 +2,7 @@ package ui;
import model.asn1.exceptions.ParseException;
import model.ca.Template;
+import model.x501.Name;
/**
* The screen that modifies the properties of a single template and add it to the store.
@@ -36,7 +37,11 @@ public class TemplateSetScreen implements UIHandler {
*/
private void handleSetSubject(String val) {
try {
- template = new Template(template.getName(), template.isEnabled(), val, template.getValidity());
+ if (val == null) {
+ template = new Template(template.getName(), template.isEnabled(), (Name) null, template.getValidity());
+ } else {
+ template = new Template(template.getName(), template.isEnabled(), val, template.getValidity());
+ }
} catch (ParseException e) {
System.out.println(e.getMessage());
}
@@ -94,6 +99,7 @@ public class TemplateSetScreen implements UIHandler {
@Override
public void commit() {
session.getCa().addTemplate(template);
+ session.setUnsaved(true);
session.setScreen(Screen.TEMPLATES);
}
diff --git a/src/main/ui/TemplatesScreen.java b/src/main/ui/TemplatesScreen.java
index e08df50..e622709 100644
--- a/src/main/ui/TemplatesScreen.java
+++ b/src/main/ui/TemplatesScreen.java
@@ -75,6 +75,7 @@ public class TemplatesScreen implements UIHandler {
return;
}
session.getCa().setTemplateEnable(tmp, enable);
+ session.setUnsaved(true);
}
/**
@@ -92,6 +93,7 @@ public class TemplatesScreen implements UIHandler {
return;
}
session.getCa().removeTemplate(tmp);
+ session.setUnsaved(true);
}
/**
diff --git a/src/main/ui/Utils.java b/src/main/ui/Utils.java
index f653ffa..4a9beeb 100644
--- a/src/main/ui/Utils.java
+++ b/src/main/ui/Utils.java
@@ -80,7 +80,11 @@ public final class Utils {
throw new ParseException("Not a valid PEM");
}
final String b64 = matcher.group(1).replace("\n", "");
- return byteToByte(Base64.getDecoder().decode(b64));
+ try {
+ return byteToByte(Base64.getDecoder().decode(b64));
+ } catch (IllegalArgumentException e) {
+ throw new ParseException(e.getMessage());
+ }
}
/**
diff --git a/src/test/persistence/DecoderTest.java b/src/test/persistence/DecoderTest.java
new file mode 100644
index 0000000..96f94e7
--- /dev/null
+++ b/src/test/persistence/DecoderTest.java
@@ -0,0 +1,81 @@
+package persistence;
+
+import model.asn1.exceptions.InvalidDBException;
+import model.ca.CertificationAuthority;
+import org.json.JSONObject;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class DecoderTest {
+ private JSONObject invalidKey1;
+ private JSONObject invalidKey2;
+ private JSONObject invalidCert;
+ private JSONObject invalidTemplate1;
+ private JSONObject invalidTemplate2;
+ private JSONObject invalidSigned;
+ private JSONObject invalidRevoked1;
+ private JSONObject invalidRevoked2;
+ private JSONObject invalidLog;
+
+ private JSONObject validMinimal;
+ private JSONObject validFull;
+
+ @BeforeEach
+ void setup() {
+ invalidKey1 = FS.read(Path.of("data", "invalid_key_1.json"));
+ invalidKey2 = FS.read(Path.of("data", "invalid_key_2.json"));
+ invalidCert = FS.read(Path.of("data", "invalid_cert.json"));
+ invalidTemplate1 = FS.read(Path.of("data", "invalid_template_1.json"));
+ invalidTemplate2 = FS.read(Path.of("data", "invalid_template_2.json"));
+ invalidSigned = FS.read(Path.of("data", "invalid_signed.json"));
+ invalidRevoked1 = FS.read(Path.of("data", "invalid_revoked_1.json"));
+ invalidRevoked2 = FS.read(Path.of("data", "invalid_revoked_2.json"));
+ invalidLog = FS.read(Path.of("data", "invalid_log.json"));
+
+ validMinimal = FS.read(Path.of("data", "valid_minimal.json"));
+ validFull = FS.read(Path.of("data", "valid_full.json"));
+ }
+
+ @Test
+ void testDecodeSuccessful() throws Throwable {
+ CertificationAuthority ca = Decoder.decodeCA(validMinimal);
+ assertNull(ca.getPublicKey());
+ assertEquals(CertificationAuthority.SERIAL_DEFAULT, ca.getSerial());
+ assertEquals(0, ca.getTemplates().size());
+ assertEquals(0, ca.getSigned().size());
+ assertEquals(0, ca.getLogs().size());
+ assertEquals(0, ca.getRevoked().size());
+ assertNull(ca.getCertificate());
+
+ ca = Decoder.decodeCA(validFull);
+ assertNotNull(ca.getPublicKey());
+ assertNotNull(ca.getKey());
+ assertNotNull(ca.getCertificate());
+ assertEquals(1, ca.getRevoked().size());
+ assertEquals(2, ca.getSigned().size());
+ assertEquals(7, ca.getLogs().size());
+ }
+
+ @Test
+ void testDecodeFail() throws Throwable {
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidKey1));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidKey2));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidCert));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidSigned));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidTemplate1));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidTemplate2));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidLog));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidRevoked1));
+ assertThrows(InvalidDBException.class, () -> Decoder.decodeCA(invalidRevoked2));
+ }
+
+ @Test
+ void testEncode() throws Throwable {
+ assertTrue(validFull.similar(Decoder.encodeCA(Decoder.decodeCA(validFull))));
+ assertTrue(validMinimal.similar(Decoder.encodeCA(Decoder.decodeCA(validMinimal))));
+ }
+}
diff --git a/src/test/persistence/FSTest.java b/src/test/persistence/FSTest.java
new file mode 100644
index 0000000..d8b8660
--- /dev/null
+++ b/src/test/persistence/FSTest.java
@@ -0,0 +1,50 @@
+package persistence;
+
+import model.asn1.exceptions.InvalidDBException;
+import org.json.JSONObject;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class FSTest {
+ @Test
+ void testReadFail() {
+ // open(2) - EPERM
+ assertThrows(InvalidDBException.class, () -> FS.read(Path.of("/dev/mem")));
+ // open(2) - EISDIR
+ assertThrows(InvalidDBException.class, () -> FS.read(Path.of("/")));
+ // open(2) - ENOENT
+ assertThrows(InvalidDBException.class,
+ () -> FS.read(Path.of("919123082901382901", "9210388888888190231")));
+ // Cannot parse
+ assertThrows(InvalidDBException.class, () -> FS.read(Path.of("/dev/null")));
+ assertThrows(InvalidDBException.class, () -> FS.read(Path.of("README.md")));
+ }
+
+ @Test
+ void testReadSuccess() {
+ assertTrue(FS.read(Path.of("data", "valid_full.json")).keySet().contains("serial"));
+ }
+
+ @Test
+ void testWriteFail() {
+ // open(2) - EISDIR
+ assertThrows(IOException.class, () -> FS.write(Path.of("/"), new JSONObject()));
+ // open(2) - EPERM
+ assertThrows(IOException.class, () -> FS.write(Path.of("/dev/abc"), new JSONObject()));
+ // open(2) - ENOENT
+ assertThrows(IOException.class, () -> FS.write(Path.of("asdjiasoda", "jisdsaod"), new JSONObject()));
+ }
+
+ @Test
+ void testWriteSuccess() throws IOException {
+ FS.write(Path.of("data", ".tmp"), new JSONObject());
+ assertTrue(Files.exists(Path.of("data")));
+ Files.delete(Path.of("data", ".tmp"));
+ }
+}
diff --git a/src/test/ui/UtilsTest.java b/src/test/ui/UtilsTest.java
index 3a6c885..23d2b3e 100644
--- a/src/test/ui/UtilsTest.java
+++ b/src/test/ui/UtilsTest.java
@@ -50,6 +50,12 @@ public class UtilsTest {
"LALA");
});
assertThrows(ParseException.class, () -> {
+ Utils.parsePEM(Utils.byteToByte(("-----BEGIN BLABLA-----\n"
+ + "91023921083910298*@34908302130890123890\n"
+ + "-----END BLABLA-----").getBytes(StandardCharsets.UTF_8)),
+ "BLABLA");
+ });
+ assertThrows(ParseException.class, () -> {
Utils.parsePEM(Utils.byteToByte(("-----BEGIN BLABLA-----"
+ "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB"
+ "-----END BLABLA-----").getBytes(StandardCharsets.UTF_8)),