commit 64cfca84a727405c69a6ba22f5f451869dd864b2
Author: Jan Pašek <janpasek97@gmail.com>
Date:   Tue Apr 20 20:05:50 2021 +0200

    Re #8576 - cryptography.py implemented generate_crl()

diff --git a/src/constants.py b/src/constants.py
index c7bc13d..0408490 100644
--- a/src/constants.py
+++ b/src/constants.py
@@ -60,3 +60,29 @@ CERTIFICATE_REVOCATION_REASONS = {"unspecified", "keyCompromise",
                                   "superseded", "cessationOfOperation",
                                   "certificateHold", "removeFromCRL",
                                   "privilegeWithdrawn", "aACompromise"}
+
+
+# Insert values into the template using % (index_file, serial_file)
+CRL_CONFIG = """[ ca ]
+default_ca = crl_ca
+
+[ crl_ext ]
+authorityKeyIdentifier=keyid:always
+
+[ crl_ca ]
+database = %s
+serial = %s
+default_md = sha1
+policy = clr_policy
+default_crl_days = 30
+
+[ clr_policy ]
+commonName = supplied
+stateOrProvinceName = optional
+countryName = optional
+emailAddress = optional
+organizationName = optional
+organizationalUnitName = optional
+
+"""
+
diff --git a/src/services/cryptography.py b/src/services/cryptography.py
index 79f87a1..700007d 100644
--- a/src/services/cryptography.py
+++ b/src/services/cryptography.py
@@ -3,6 +3,9 @@ import subprocess
 import time
 import random
 
+from src.constants import CRL_CONFIG
+from src.model.certificate import Certificate
+from src.model.private_key import PrivateKey
 from src.model.subject import Subject
 from src.utils.temporary_file import TemporaryFile
 
@@ -129,7 +132,7 @@ class CryptographyService:
         # file instead of an extension file. Therefore the following code creates
         # the most basic configuration file with sscrt_ext section, that is later
         # reference in openssl req command using -extensions option.
-        extensions += "\n"+CA_EXTENSIONS
+        extensions += "\n" + CA_EXTENSIONS
         if len(config) == 0:
             config += MINIMAL_CONFIG_FILE
         config += "\n[ " + SSCRT_SECTION + " ]" + "\n" + extensions
@@ -146,7 +149,7 @@ class CryptographyService:
             if len(config) > 0:
                 args.extend(["-config", conf_path])
             if len(extensions) > 0:
-                args.extend(["-extensions", SSCRT_SECTION]) # when creating SSCRT, section references section in config
+                args.extend(["-extensions", SSCRT_SECTION])  # when creating SSCRT, section references section in config
 
             # it would be best to not send the pass phrase at all, but for some reason pytest then prompts for
             # the pass phrase (this does not happen when run from pycharm)
@@ -355,16 +358,24 @@ class CryptographyService:
         """
         return self.__run_for_output(["version"]).decode("utf-8")
 
-    def generate_crl(self, index_file_path: str) -> str:
+    def generate_crl(self, cert: Certificate, key: PrivateKey, index_file_path: str) -> str:
         """
         Generate a CertificateRevocationList for a specified
         certificate authority.
 
+        :param key: key that is used to sign the CRL (must belong to the given certificate)
+        :param cert: Certificate of the certificate authority that issue the CRL
         :param index_file_path: path to a file that contains the openssl index with all revoked certificates
         :return: CRL encoded in PEM format string
         """
-        # TODO
-        return ""
+        with TemporaryFile("serial.srl", "0") as serial_file, \
+             TemporaryFile("crl.conf", CRL_CONFIG % (index_file_path, serial_file)) as config_file, \
+             TemporaryFile("certificate.pem", cert.pem_data) as cert_file, \
+             TemporaryFile("private_key.pem", key.private_key) as key_file:
+
+            args = ["ca", "-config", config_file, "-gencrl", "-keyfile", key_file, "-cert", cert_file, "-outdir", "."]
+            return self.__run_for_output(args).decode("utf-8")
+
 
 class CryptographyException(Exception):
 
diff --git a/tests/unit_tests/services/crl/generate_crl_test.py b/tests/unit_tests/services/crl/generate_crl_test.py
new file mode 100644
index 0000000..dca1ebc
--- /dev/null
+++ b/tests/unit_tests/services/crl/generate_crl_test.py
@@ -0,0 +1,133 @@
+from src.model.certificate import Certificate
+from src.model.private_key import PrivateKey
+from src.services.cryptography import CryptographyService
+from src.utils.temporary_file import TemporaryFile
+
+
+CERT_TO_CHECK = 25
+
+CERTIFICATE_PEM = """-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
+"""
+
+KEY_PEM = """-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+"""
+
+INDEX = """R\t230401152645Z\t210401152748Z,keyCompromise\t01\tunknown\t/CN=IA CA/ST=Some-State/C=AU/O=Internet Widgits Pty Ltd
+R\t230401153537Z\t210401152748Z,keyCompromise\t02\tunknown\t/CN=Pasek Jan/ST=Pilsen/C=CZ/O=Pasek s.r.o.
+"""
+
+EXPECTED_CRL = """-----BEGIN X509 CRL-----
+MIIC5DCBzQIBATANBgkqhkiG9w0BAQUFADBVMQ4wDAYDVQQDDAVJQSBDQTETMBEG
+A1UECAwKU29tZS1TdGF0ZTELMAkGA1UEBhMCQVUxITAfBgNVBAoMGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZBcNMjEwNDIwMTc1ODUzWhcNMjEwNTIwMTc1ODUzWjBE
+MCACAQEXDTIxMDQwMTE1Mjc0OFowDDAKBgNVHRUEAwoBATAgAgECFw0yMTA0MDEx
+NTI3NDhaMAwwCgYDVR0VBAMKAQEwDQYJKoZIhvcNAQEFBQADggIBACY8D2BPpMf4
+nVvcdketKVxzKsaZ+nUdOB4kOwMA3/rbPq3axTVQ0HBuMyoTS5sTgZxfEnEd0U9L
+9sLk3464Mw6NH4Gmo1wiW3vIw0d4ml2hRytb64HIUAnRkZn3x/d2jA4mH0KRBO23
+aCAy4qTp8J61EIoqQxMM0eJBjVxZnwY/E3SZlVTPVLXe6loSjVzT3rJItKLqMzqV
+JUlMdMJSIvvCaIjihYa7eiH1iC9T3exyqV34sm/ktGOxxZshmGl0R3yJDgCJ/VG3
+MEzU8jTys1cwwudUdlcouL4j6XMUpsnJhj+I/JaRXJqozsuyYJy1XmSsR1nlOjct
+Mdx8chHiAFvQ3QmdfcGPxwtQXvWIh1nbNdoKHWH6CiTX3vhZiKsFwLYYcb+32DW7
+9BOIAgD+Y+pcBwADWIP5QsIoGHBqbH+KQy8sSSvXK+ZySSHrAdMr7hts+djqQjKE
+3pG2nH/AdiuyAneJu/YXvpYV9AV+IDVToB0kmwjaqYzGtNzgUMwyhBttR6+nfvkv
+SQLDUuorwSzPPaROzHpX0TtULHxedZr2gPwjbIOIoB+0ZulpHpBRH6dQY15LaKw6
+ZyjDy23+CVA/d3CvHuPaDkE0ucTJXNI/0c1giBGPJwdyYmeblZkzcaiYRboOI3kS
+XlFRZbGV9ivNg6kM4L/7LgNTgBA5T63f
+-----END X509 CRL-----
+""".strip().replace("\r", "").replace("\n", "")
+
+def test_generate_crl():
+    # only PEM data are relevant for the testcase
+    service = CryptographyService()
+    cert = Certificate(-1, "", "", "",
+                       CERTIFICATE_PEM, -1, -1, -1, {})
+    key = PrivateKey(-1, KEY_PEM)
+    # generate CRL
+    with TemporaryFile("index", INDEX) as index_file:
+        out = service.generate_crl(cert, key, index_file)
+
+    # check first 25 bytes - the rest can differ due to different issuing date
+    assert out.strip().replace("\r", "").replace("\n", "")[:CERT_TO_CHECK] == EXPECTED_CRL[:CERT_TO_CHECK]
