commit 2c8b79114713cc44ff0920087adeac70f1e5b7f1
Author: Stanislav Král <stanislav.kral3@gmail.com>
Date:   Fri Apr 30 16:19:53 2021 +0200

    Re #8708 - Implemented a new method in the CryptographyService that allows the caller to create a PKCS12 identity of a certificate and it's chain of trust
    
    Covered the new method with unit tests.

diff --git a/src/services/cryptography.py b/src/services/cryptography.py
index 2bbef56..b1f7eae 100644
--- a/src/services/cryptography.py
+++ b/src/services/cryptography.py
@@ -2,6 +2,7 @@ import re
 import subprocess
 import time
 import random
+from typing import List
 
 from src.constants import CRL_CONFIG
 from src.model.certificate import Certificate
@@ -411,10 +412,9 @@ class CryptographyService:
 
         # openssl ca requires the .srl file to exists, therefore a dummy, unused file is created
         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:
-
+                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", "."]
 
             if key.password is not None and key.password != "":
@@ -437,9 +437,8 @@ class CryptographyService:
         Logger.debug("Function launched.")
 
         with TemporaryFile("certificate.pem", cert.pem_data) as ca_certificate, \
-             TemporaryFile("private_key.pem", key.private_key) as key_file, \
-             TemporaryFile("request.der", der_ocsp_request) as request_file:
-
+                TemporaryFile("private_key.pem", key.private_key) as key_file, \
+                TemporaryFile("request.der", der_ocsp_request) as request_file:
             args = ["ocsp", "-index", index_path, "-CA", ca_certificate, "-rsigner", ca_certificate, "-rkey", key_file,
                     "-reqin", request_file, "-respout", "-"]
 
@@ -448,6 +447,27 @@ class CryptographyService:
 
             return self.__run_for_output(args)
 
+    def generate_pkcs_identity(self, cert_pem: str, cert_key_pem: str, identity_name: str, identity_passphrase: str,
+                               chain_of_trust_pems: List[str], cert_key_passphrase: str = None):
+        """
+        Generates a PKCS12 identity of the given child certificate while including the given chain of trust.
+
+        :param cert_pem: PEM of the certificate whose identity should be created
+        :param cert_key_pem: PEM of the private key used to sign the certificate whose identity should be created
+        :param identity_name: the name to be given to the identity created
+        :param chain_of_trust_pems: list of PEMs representing certificates present in the chain of trust of the certificate
+        whose identity should be created
+        :param identity_passphrase: passphrase to be used when encrypting the identity
+        :param cert_key_passphrase: passphrase of the key used to sign the certificate whose identity should be created
+        :return: byte array containing the generated identity
+        """
+        with TemporaryFile("cert_key.pem", cert_key_pem) as cert_key_pem_file:
+            args = ["pkcs12", "-export", "-name", identity_name, "-in", "-", "-inkey", cert_key_pem_file, "-CAfile",
+                    "-", "-passout", f"pass:{identity_passphrase}", "-passin", f"pass:{cert_key_passphrase}"]
+            return self.__run_for_output(args,
+                                         proc_input=bytes(cert_pem + "".join(chain_of_trust_pems),
+                                                          encoding="utf-8"))
+
 
 class CryptographyException(Exception):
 
diff --git a/tests/unit_tests/services/cryptography/generate_pkcs_identity_test.py b/tests/unit_tests/services/cryptography/generate_pkcs_identity_test.py
new file mode 100644
index 0000000..4d23fe3
--- /dev/null
+++ b/tests/unit_tests/services/cryptography/generate_pkcs_identity_test.py
@@ -0,0 +1,95 @@
+import subprocess
+
+import pytest
+
+from src.model.subject import Subject
+from src.services.cryptography import CryptographyException
+
+
+def test_generate_pkcs_identity(service):
+    root_key = service.create_private_key()
+    root_cert = service.create_sscrt(Subject(common_name="Foo"), root_key)
+
+    inter_key = service.create_private_key()
+    inter_cert = service.create_crt(Subject(common_name="Bar"), inter_key, root_cert, root_key)
+
+    child_key = service.create_private_key()
+    child_cert = service.create_crt(Subject(common_name="Baz"), child_key, inter_cert, inter_key)
+
+    pkcs = service.generate_pkcs_identity(child_cert, child_key, "Baz Pkcs", "secret_pass",
+                                          "".join([root_cert, inter_cert]))
+
+    # print out the pkcs store in order to be able to check it
+    pkcs_info = subprocess.check_output(
+        ["openssl", "pkcs12", "-info", "-in", "-", "-nodes", "-passin", "pass:secret_pass"],
+        input=pkcs,
+        stderr=subprocess.STDOUT).decode()
+
+    assert child_cert in pkcs_info
+
+    assert "-----BEGIN PRIVATE KEY-----" in pkcs_info
+    assert root_cert in pkcs_info
+    assert inter_cert in pkcs_info
+
+
+def test_generate_pkcs_identity_encrypted_key(service):
+    root_key = service.create_private_key()
+    root_cert = service.create_sscrt(Subject(common_name="Foo"), root_key)
+
+    inter_key = service.create_private_key()
+    inter_cert = service.create_crt(Subject(common_name="Bar"), inter_key, root_cert, root_key)
+
+    # protect the child key with a passphrase
+    child_key_passphrase = "keypass"
+    child_key = service.create_private_key(passphrase=child_key_passphrase)
+    child_cert = service.create_crt(Subject(common_name="Baz"), child_key, inter_cert, inter_key, child_key_passphrase)
+
+    pkcs = service.generate_pkcs_identity(child_cert, child_key, "Baz Pkcs", "secret_pass",
+                                          "".join([root_cert, inter_cert]),
+                                          cert_key_passphrase=child_key_passphrase)
+
+    # print out the pkcs store in order to be able to check it
+    pkcs_info = subprocess.check_output(
+        ["openssl", "pkcs12", "-info", "-in", "-", "-nodes", "-passin", "pass:secret_pass"],
+        input=pkcs,
+        stderr=subprocess.STDOUT).decode()
+
+    assert child_cert in pkcs_info
+
+    assert "-----BEGIN PRIVATE KEY-----" in pkcs_info
+    assert root_cert in pkcs_info
+    assert inter_cert in pkcs_info
+
+
+def test_generate_pkcs_identity_encrypted_key_passphrase_not_provided(service):
+    root_key = service.create_private_key()
+    root_cert = service.create_sscrt(Subject(common_name="Foo"), root_key)
+
+    inter_key = service.create_private_key()
+    inter_cert = service.create_crt(Subject(common_name="Bar"), inter_key, root_cert, root_key)
+
+    # protect the child key with a passphrase
+    child_key_passphrase = "keypass"
+    child_key = service.create_private_key(passphrase=child_key_passphrase)
+    child_cert = service.create_crt(Subject(common_name="Baz"), child_key, inter_cert, inter_key, child_key_passphrase)
+
+    with pytest.raises(CryptographyException):
+        service.generate_pkcs_identity(child_cert, child_key, "Baz Pkcs", "secret_pass",
+                                       "".join([root_cert, inter_cert]))
+
+
+def test_generate_pkcs_identity_encrypted_key_incorrect_passphrase(service):
+    root_key = service.create_private_key()
+    root_cert = service.create_sscrt(Subject(common_name="Foo"), root_key)
+
+    inter_key = service.create_private_key()
+    inter_cert = service.create_crt(Subject(common_name="Bar"), inter_key, root_cert, root_key)
+
+    # protect the child key with a passphrase
+    child_key_passphrase = "keypass"
+    child_key = service.create_private_key(passphrase=child_key_passphrase)
+    child_cert = service.create_crt(Subject(common_name="Baz"), child_key, inter_cert, inter_key, child_key_passphrase)
+
+    with pytest.raises(CryptographyException):
+        service.generate_pkcs_identity(child_cert, child_key, "Baz Pkcs", "secret_pass",
+                                       "".join([root_cert, inter_cert]), cert_key_passphrase="passkey")
