commit 36409852ecd02f1c54d08b2c804494f86658d882
Author: Stanislav Král <stanislav.kral3@gmail.com>
Date:   Sun Apr 4 12:17:40 2021 +0200

    Re #8472 - Improved parse_cert_pem method in such way that it also parses notBefore and notAfter fields now

diff --git a/src/services/cryptography.py b/src/services/cryptography.py
index f286e71..dd95f78 100644
--- a/src/services/cryptography.py
+++ b/src/services/cryptography.py
@@ -1,15 +1,19 @@
-import subprocess
 import re
+import subprocess
+import time
 
-# encryption method to be used when generating private keys
 from src.model.subject import Subject
 from src.utils.temporary_file import TemporaryFile
 
+# encryption method to be used when generating private keys
 PRIVATE_KEY_ENCRYPTION_METHOD = "-aes256"
 
 # openssl executable name
 OPENSSL_EXECUTABLE = "openssl"
 
+# format of NOT_BEFORE NOT_AFTER date fields
+NOT_AFTER_BEFORE_DATE_FORMAT = "%b %d %H:%M:%S %Y %Z"
+
 
 class CryptographyService:
 
@@ -187,8 +191,8 @@ class CryptographyService:
         :param issuer_key: string containing the private key of the issuer's certificate in PEM format
         :param issuer_pem: string containing the certificate of the issuer that will sign this CSR in PEM format
         :param issuer_key: string containing the private key of the issuer's certificate in PEM format
-        :param subject_key_pass: string containing the passphrase of the private key used when creating the certificate in PEM
-        format
+        :param subject_key_pass: string containing the passphrase of the private key used when creating the certificate
+        in PEM format
         :param issuer_key_pass: string containing the passphrase of the private key of the issuer's certificate in PEM
         format
         :param extensions: extensions to be applied when creating the certificate
@@ -229,25 +233,34 @@ class CryptographyService:
 
     def parse_cert_pem(self, cert_pem):
         """
-        Parses the given certificate in PEM format and returns the subject of the certificate
+        Parses the given certificate in PEM format and returns the subject of the certificate and it's NOT_BEFORE
+        and NOT_AFTER field
         :param cert_pem: a certificated in a PEM format to be parsed
-        :return: a subject stored within the supplied certificate
+        :return: a tuple containing a subject, NOT_BEFORE and NOT_AFTER dates
         """
         # run openssl x509 to view certificate content
-        args = ["x509", "-noout", "-text", "-in", "-"]
-        result = self.__run_for_output(args, proc_input=bytes(cert_pem, encoding="utf-8")).decode()
+        args = ["x509", "-noout", "-subject", "-startdate", "-enddate", "-in", "-"]
 
-        # find the line containing the subject
-        match = re.search(r"Subject:\s(.*)", result)
+        cert_info_raw = self.__run_for_output(args, proc_input=bytes(cert_pem, encoding="utf-8")).decode()
+
+        # split lines
+        results = re.split("\n", cert_info_raw)
+        subj_line = results[0]
+        not_before_line = results[1]
+        not_after_line = results[2]
+
+        # attempt to extract subject via regex
+        match = re.search(r"subject=(.*)", subj_line)
         if match is None:
             # TODO use logger
-            print(f"Could not find subject to parse: {result}")
+            print(f"Could not find subject to parse: {subj_line}")
             return None
         else:
-            # find all attributes (key = value
-            found = re.findall(r"\s?([^c=\s]+)\s?=\s?([^,\n]+)", match.group())
+            # find all attributes (key = value)
+            found = re.findall(r"\s?([^c=\s]+)\s?=\s?([^,\n]+)", match.group(1))
             subj = Subject()
             for key, value in found:
+                print(f"{key}:{value}")
                 if key == "C":
                     subj.country = value
                 elif key == "ST":
@@ -262,7 +275,20 @@ class CryptographyService:
                     subj.common_name = value
                 elif key == "emailAddress":
                     subj.email_address = value
-            return subj
+
+        # extract notBefore and notAfter date fields
+        not_before = re.search(r"notBefore=(.*)", not_before_line)
+        not_after = re.search(r"notAfter=(.*)", not_after_line)
+
+        # if date fields are found parse them into date objects
+        if not_before is not None:
+            not_before = time.strptime(not_before.group(1), NOT_AFTER_BEFORE_DATE_FORMAT)
+        if not_after is not None:
+            not_after = time.strptime(not_after.group(1), NOT_AFTER_BEFORE_DATE_FORMAT)
+
+        # TODO wrapper class?
+        # return it as a tuple
+        return subj, not_before, not_after
 
 
 class CryptographyException(Exception):
diff --git a/tests/services/cryptography/parse_cert_pem_test.py b/tests/services/cryptography/parse_cert_pem_test.py
index bbb3ef7..eb5e2bf 100644
--- a/tests/services/cryptography/parse_cert_pem_test.py
+++ b/tests/services/cryptography/parse_cert_pem_test.py
@@ -42,7 +42,21 @@ XXwUYr8GuSFQZb1Q4BpCayCYvTLj+7q3z72BCqAA+jMJYV/qU0EpsuFjPvzU8apg
     """
 
     # parse a certificate supplied in a PEM format
-    subj = service.parse_cert_pem(cert_pem)
+    subj, n_before, n_after = service.parse_cert_pem(cert_pem)
+
+    assert 3 == n_before.tm_mon
+    assert 21 == n_before.tm_mday
+    assert 10 == n_before.tm_hour
+    assert 1 == n_before.tm_min
+    assert 52 == n_before.tm_sec
+    assert 2021 == n_before.tm_year
+
+    assert 3 == n_after.tm_mon
+    assert 21 == n_after.tm_mday
+    assert 10 == n_after.tm_hour
+    assert 1 == n_after.tm_min
+    assert 52 == n_after.tm_sec
+    assert 2026 == n_after.tm_year
 
     assert "CZ" == subj.country
     assert "Pilsen Region" == subj.state
@@ -90,7 +104,21 @@ gug7Na3ya2fwlerj6YM9w+i8Csf8lUFe0gww7NLkbv54
     """
 
     # parse a certificate supplied in a PEM format
-    subj = service.parse_cert_pem(cert_pem)
+    subj, n_before, n_after = service.parse_cert_pem(cert_pem)
+
+    assert 3 == n_before.tm_mon
+    assert 23 == n_before.tm_mday
+    assert 21 == n_before.tm_hour
+    assert 32 == n_before.tm_min
+    assert 59 == n_before.tm_sec
+    assert 2021 == n_before.tm_year
+
+    assert 3 == n_after.tm_mon
+    assert 23 == n_after.tm_mday
+    assert 21 == n_after.tm_hour
+    assert 32 == n_after.tm_min
+    assert 59 == n_after.tm_sec
+    assert 2024 == n_after.tm_year
 
     assert "AU" == subj.country
     assert "Some-State" == subj.state
@@ -127,7 +155,21 @@ khprZhxRGRR6cHhyVmMexSWucWb7zlJZNcO9F0/FIgoqcKODtdNczTJyrC9raeuf
     """
 
     # parse a certificate supplied in a PEM format
-    subj = service.parse_cert_pem(cert_pem)
+    subj, n_before, n_after = service.parse_cert_pem(cert_pem)
+
+    assert 4 == n_before.tm_mon
+    assert 3 == n_before.tm_mday
+    assert 23 == n_before.tm_hour
+    assert 30 == n_before.tm_min
+    assert 10 == n_before.tm_sec
+    assert 2021 == n_before.tm_year
+
+    assert 5 == n_after.tm_mon
+    assert 3 == n_after.tm_mday
+    assert 23 == n_after.tm_hour
+    assert 30 == n_after.tm_min
+    assert 10 == n_after.tm_sec
+    assert 2021 == n_after.tm_year
 
     # TODO improve parsing of fields within quotes
     assert "\"  \"" == subj.country
@@ -147,7 +189,7 @@ def test_create_and_parse_cert(service):
     cert = service.create_sscrt(Subject(common_name="Foo CN", email_address="foo@bar.cz"), key, key_pass="foobar")
 
     # parse the subject
-    parsed_subj = service.parse_cert_pem(cert)
+    parsed_subj, n_before, n_after = service.parse_cert_pem(cert)
 
     assert "Foo CN" == parsed_subj.common_name
     assert "foo@bar.cz" == parsed_subj.email_address
