commit 9a55ea8abb7eff91287f62acc3e52d101e967c72
Author: Stanislav Král <stanislav.kral3@gmail.com>
Date:   Mon Apr 19 07:28:11 2021 +0200

    Re #8575 - Added a "sn" parameter to some CryptographyService

diff --git a/src/services/cryptography.py b/src/services/cryptography.py
index 886577e..ee94bad 100644
--- a/src/services/cryptography.py
+++ b/src/services/cryptography.py
@@ -1,6 +1,7 @@
 import re
 import subprocess
 import time
+import random
 
 from src.model.subject import Subject
 from src.utils.temporary_file import TemporaryFile
@@ -24,6 +25,9 @@ SSCRT_SECTION = "sscrt_ext"
 
 CA_EXTENSIONS = "basicConstraints=critical,CA:TRUE"
 
+# upper bound of the range of random serial numbers to be generated
+MAX_SN = 4294967296
+
 
 class CryptographyService:
 
@@ -102,7 +106,7 @@ class CryptographyService:
             return self.__run_for_output(
                 ["genrsa", PRIVATE_KEY_ENCRYPTION_METHOD, "-passout", f"pass:{passphrase}", "2048"]).decode()
 
-    def create_sscrt(self, subject, key, config="", extensions="", key_pass=None, days=30):
+    def create_sscrt(self, subject, key, config="", extensions="", key_pass=None, days=30, sn: int =None):
         """
         Creates a root CA
 
@@ -112,6 +116,7 @@ class CryptographyService:
         :param extensions: name of the section in the configuration representing extensions
         :param key_pass: passphrase of the private key
         :param days: number of days for which the certificate will be valid
+        :param sn: serial number to be set, when "None" is set a random serial number is generated
 
         :return: string containing the generated certificate in PEM format
         """
@@ -133,6 +138,11 @@ class CryptographyService:
             args = ["req", "-x509", "-new", "-subj", subj, "-days", f"{days}",
                     "-key", "-"]
 
+            # serial number passed, use it when generating the certificate,
+            # without passing it openssl generates a random one
+            if sn is not None:
+                args.extend(["-set_serial", str(sn)])
+
             if len(config) > 0:
                 args.extend(["-config", conf_path])
             if len(extensions) > 0:
@@ -167,7 +177,7 @@ class CryptographyService:
 
         return self.__run_for_output(args, proc_input=bytes(key, encoding="utf-8")).decode()
 
-    def __sign_csr(self, csr, issuer_pem, issuer_key, issuer_key_pass=None, extensions="", days=30):
+    def __sign_csr(self, csr, issuer_pem, issuer_key, issuer_key_pass=None, extensions="", days=30, sn: int = None):
         """
         Signs the given CSR by the given issuer CA
 
@@ -178,12 +188,18 @@ class CryptographyService:
         format
         :param extensions: extensions to be applied when signing the CSR
         :param days: number of days for which the certificate will be valid
+        :param sn: serial number to be set, when "None" is set a random serial number is generated
         :return: string containing the generated and signed certificate in PEM format
         """
 
         # concatenate CSR, issuer certificate and issuer's key (will be used in the openssl call)
         proc_input = csr + issuer_pem + issuer_key
 
+        # TODO find a better way to generate a random serial number or let openssl generate a .srl file
+        # when serial number is not passed generate a random one
+        if sn is None:
+            sn = random.randint(0, MAX_SN)
+
         # prepare openssl parameters...
         # CSR, CA and CA's private key will be passed via stdin (that's the meaning of the '-' symbol)
         params = ["x509", "-req", "-in", "-", "-CA", "-", "-CAkey", "-", "-CAcreateserial", "-days", str(days)]
@@ -202,7 +218,8 @@ class CryptographyService:
 
     def create_crt(self, subject, subject_key, issuer_pem, issuer_key, subject_key_pass=None, issuer_key_pass=None,
                    extensions="",
-                   days=30):
+                   days=30,
+                   sn: int = None):
         """
         Creates a certificate by using the given subject, subject's key, issuer and its key.
 
@@ -216,11 +233,12 @@ class CryptographyService:
         format
         :param extensions: extensions to be applied when creating the certificate
         :param days: number of days for which the certificate will be valid
+        :param sn: serial number to be set, when "None" is set a random serial number is generated
         :return: string containing the generated certificate in PEM format
         """
         csr = self.__create_csr(subject, subject_key, key_pass=subject_key_pass)
         return self.__sign_csr(csr, issuer_pem, issuer_key, issuer_key_pass=issuer_key_pass, extensions=extensions,
-                               days=days)
+                               days=days, sn=sn)
 
     @staticmethod
     def verify_cert(certificate):
