commit 19e5260da690934ca34c604e20fb0a01c3760712
Author: Stanislav Král <stanislav.kral3@gmail.com>
Date:   Tue Apr 13 01:00:19 2021 +0200

    Re #8573 - Implemented extract_public_key_from_certificate

diff --git a/src/services/cryptography.py b/src/services/cryptography.py
index 8b45194..5be787e 100644
--- a/src/services/cryptography.py
+++ b/src/services/cryptography.py
@@ -231,7 +231,7 @@ class CryptographyService:
             # the process failed because of some other reason (incorrect cert format)
             raise CryptographyException(OPENSSL_EXECUTABLE, args, err.decode())
 
-    def extract_public_key(self, private_key_pem: str, passphrase=None) -> str:
+    def extract_public_key_from_private_key(self, private_key_pem: str, passphrase=None) -> str:
         """
         Extracts a public key from the given private key passed in PEM format
         :param private_key_pem: PEM data representing the private key from which a public key should be extracted
@@ -243,6 +243,17 @@ class CryptographyService:
             args.extend(["-passin", f"pass:{passphrase}"])
         return self.__run_for_output(args, proc_input=bytes(private_key_pem, encoding="utf-8")).decode()
 
+    def extract_public_key_from_certificate(self, cert_pem: str) -> str:
+        """
+        Extracts a public key from the given certificate passed in PEM format
+        :param cert_pem: PEM data representing a certificate from which a public key should be extracted
+        :return: a string containing the extracted public key in PEM format
+        """
+        # extracting public key from a certificate does not seem to require a passphrase even when
+        # signed using an encrypted PK
+        args = ["x509", "-in", "-", "-noout", "-pubkey"]
+        return self.__run_for_output(args, proc_input=bytes(cert_pem, encoding="utf-8")).decode()
+
     def parse_cert_pem(self, cert_pem):
         """
         Parses the given certificate in PEM format and returns the subject of the certificate and it's NOT_BEFORE
diff --git a/src/services/key_service.py b/src/services/key_service.py
index 31224ad..820e44e 100644
--- a/src/services/key_service.py
+++ b/src/services/key_service.py
@@ -68,4 +68,4 @@ class KeyService:
         :param private_key: private key from which a public key should be extracted
         :return: a string containing the extracted public key in PEM format
         """
-        return self.cryptography_service.extract_public_key(private_key.private_key, private_key.password)
+        return self.cryptography_service.extract_public_key_from_private_key(private_key.private_key, private_key.password)
diff --git a/tests/unit_tests/services/cryptography/conftest.py b/tests/unit_tests/services/cryptography/conftest.py
index d63288e..f574832 100644
--- a/tests/unit_tests/services/cryptography/conftest.py
+++ b/tests/unit_tests/services/cryptography/conftest.py
@@ -76,3 +76,57 @@ b5fAiVPUfZUsI3xw17ZLCmVX9Jq2yzLS8Kyrlvd9rLyMeEVFb4R5ho3F+uLopVrC
 qRz7WctkmA9/SOxoF6EPaEhIyXNjjFOQbjsXVc+i0Bnw2DjY3s5SYJg6yycwdaHz
 -----END RSA PRIVATE KEY-----
     """, "pass"
+
+
+@pytest.fixture
+def certificate_pem():
+    return """
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUTQwIafNfZdJmqZ+TvhtWT3dKLbMwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQ1oxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA0MTIyMjIxNTJaFw0yMTA1
+MTIyMjIxNTJaMEUxCzAJBgNVBAYTAkNaMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDh/OuanZE7Vg2CxvJUSAY+W3lDHCOJULqEaxcDRNfr
+3xd32yIv6BnwqO1qlchex2KeMN2PWuLQNFI0SQ7CfY0SihibPqrAKUW2XwNT2lqM
+B4OnOD0JGDe+WRAJEmVbabnAfYthPZ0g2BbCirC5cXpL5NTDr5czj5d2H/1XK8ue
+zNbHRrEd6W+P0X262ghKGebjD3mq5i92eZMjE8XranuTPtVn9G+lGjLW9MxgNC0Q
+OX71rMDkTIEVaj6r0kIVigu8dSIL3APX5rqdOiPEPDYf9k1AZ8mh313P3Ef1/4/W
+Fp2qqIaKfzjAdEZG3SuzQtE3XKuTevAm4Pf8jKXrScN1AgMBAAGjUzBRMB0GA1Ud
+DgQWBBS1TwdmMBbo2YBc1NY3bre+fkJUfzAfBgNVHSMEGDAWgBS1TwdmMBbo2YBc
+1NY3bre+fkJUfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCv
+684u4MRxN/h5yzxlYj4V0LfbifCLscrZIMquH7gavtmGZTSRlkVMcV+Lqt3Gr2x9
+1DdZ6+Uvewt1nmuWvKJSCRgc67Kj1JvYeFzctYR2J/xHPGmXTm6orEW3MPSQdTvh
+Fe1UXz7MF0HipLprIwxkH8DsGT5djSQ4jCLGnLL2h5DHW/9HY23OgAMSkpJbIo6k
+4sx+fjOtUhxkItDN8RY2LzlENRroW2bZA3cqVH5KO1ubj5Jy+Yrd8LvD0KxW37zi
+NyJXVOPghVAqaFbgaeBsXkL3QQYVR5wQl7delo0dn36crlckUPi+13INDhnLYgq7
+GO9Jbty2Qeomxyf+i+SB
+-----END CERTIFICATE-----
+    """
+
+
+@pytest.fixture
+def certificate_with_encrypted_pk_pem():
+    return """
+-----BEGIN CERTIFICATE-----
+MIIDazCCAlOgAwIBAgIUAWW0ebo18+AtvjzxLvIPEa4lIUkwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCcGExEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA0MTIyMjMxMDRaFw0yMTA1
+MTIyMjMxMDRaMEUxCzAJBgNVBAYTAnBhMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDRPcW6WGeMSvsW+p2xeuKXG5gfzf7b7i2r5hcac222
+hTxwV5nkwnCID/7rDG0JrnL0XoMwCtXKD8Noa6lAnxXs+1ENjdIbpufAIUYHkuBg
+jbqCRBs32ue87Tr6lK9DPgjbrs5f6/r08fJ2ZMX+g92Lq0bgn/Dej6+gtIN31n1M
+o72W3NpfrRl+PWMlwrTXzDM6tu3PLtK/8TCmrUrg2WgoTbUsRTFy9oUG5xm+qFfQ
+c+6o9gYk+5jO3FI7UP4z7C9n+5O6p25CYblTaENdJb/wjHLeLnY9f1iRkGZT4Lel
+6iQEC4iuiBvKL46txYl2r4hbNO5U7hDQ11nWp47GZJ8fAgMBAAGjUzBRMB0GA1Ud
+DgQWBBSCbDHOKEnOU27evnxYKxyF/gQzBTAfBgNVHSMEGDAWgBSCbDHOKEnOU27e
+vnxYKxyF/gQzBTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAe
+z8xDdKj/hmIozRxiOFEjjBW9HFQicmisD/lHH3XWQtW3coNXqa/6UOXor6JNCmYB
+MKV/N4BAHve/rRkMZ7ohInsanMBsR5Nl2lrYMYWcMNJ8vZm1FbeEqxL/VxQLWwlH
+Q2seDbNkrGPuCJzVPskqzV0x373IYKC1faTLpdPd7DChkKmUv1SWoYo3yQ+G4tUb
+IeHGOq+4gEYzF0YsGu2QaAPR3hRMcRtc7LDtuy+eRz4BqvRm3jF3W26Ha8cSojs7
+c2xJmcA3t2cFCHYKXDmlBJJ5Kvkuak9U+TZLl7+Y7+SmyJIv9nRN2THwDKpaRtff
+94wl65n2IoG1YP4XyjyX
+-----END CERTIFICATE-----
+    """, "pass"
\ No newline at end of file
diff --git a/tests/unit_tests/services/cryptography/extract_public_key_from_certificate_test.py b/tests/unit_tests/services/cryptography/extract_public_key_from_certificate_test.py
new file mode 100644
index 0000000..5a476da
--- /dev/null
+++ b/tests/unit_tests/services/cryptography/extract_public_key_from_certificate_test.py
@@ -0,0 +1,13 @@
+
+def test_extract_public_key_encrypted(service, certificate_with_encrypted_pk_pem):
+    public_key = service.extract_public_key_from_certificate(certificate_with_encrypted_pk_pem[0])
+
+    assert "-----BEGIN PUBLIC KEY-----" in public_key
+
+
+def test_extract_public_key(service, certificate_pem):
+    public_key = service.extract_public_key_from_certificate(certificate_pem)
+
+    # TODO test whether public key matches a private key
+    assert "-----BEGIN PUBLIC KEY-----" in public_key
+
diff --git a/tests/unit_tests/services/cryptography/extract_public_key_test.py b/tests/unit_tests/services/cryptography/extract_public_key_test.py
index e698a83..a37a103 100644
--- a/tests/unit_tests/services/cryptography/extract_public_key_test.py
+++ b/tests/unit_tests/services/cryptography/extract_public_key_test.py
@@ -4,13 +4,14 @@ from src.services.cryptography import CryptographyException
 
 
 def test_extract_public_key_encrypted(service, private_key_encrypted_pem):
-    public_key = service.extract_public_key(private_key_encrypted_pem[0], passphrase=private_key_encrypted_pem[1])
+    public_key = service.extract_public_key_from_private_key(private_key_encrypted_pem[0],
+                                                             passphrase=private_key_encrypted_pem[1])
 
     assert "-----BEGIN PUBLIC KEY-----" in public_key
 
 
-def test_extract_public_key_2(service, private_key_pem):
-    public_key = service.extract_public_key(private_key_pem)
+def test_extract_public_key(service, private_key_pem):
+    public_key = service.extract_public_key_from_private_key(private_key_pem)
 
     # TODO test whether public key matches a private key
     assert "-----BEGIN PUBLIC KEY-----" in public_key
@@ -19,18 +20,18 @@ def test_extract_public_key_2(service, private_key_pem):
 def test_extract_public_key_encrypted_fails(service, private_key_encrypted_pem):
     # try to extract it using no passphrase
     with pytest.raises(CryptographyException) as e:
-        service.extract_public_key(private_key_encrypted_pem[0])
+        service.extract_public_key_from_private_key(private_key_encrypted_pem[0])
 
     assert "bad decrypt" in e.value.message
 
     # try to extract it using an empty passphrase
     with pytest.raises(CryptographyException) as e:
-        service.extract_public_key(private_key_encrypted_pem[0], passphrase="")
+        service.extract_public_key_from_private_key(private_key_encrypted_pem[0], passphrase="")
 
     assert "bad decrypt" in e.value.message
 
     # try to extract it using a wrong passphrase
     with pytest.raises(CryptographyException) as e:
-        service.extract_public_key(private_key_encrypted_pem[0], passphrase="foo")
+        service.extract_public_key_from_private_key(private_key_encrypted_pem[0], passphrase="foo")
 
     assert "bad decrypt" in e.value.message
