Projekt

Obecné

Profil

Stáhnout (22 KB) Statistiky
| Větev: | Tag: | Revize:
1 4a40b0d2 Stanislav Král
import re
2 36409852 Stanislav Král
import subprocess
3
import time
4 9a55ea8a Stanislav Král
import random
5 c0aed2f5 Stanislav Král
6 64cfca84 Jan Pašek
from src.constants import CRL_CONFIG
7
from src.model.certificate import Certificate
8
from src.model.private_key import PrivateKey
9 cc51ca2c Stanislav Král
from src.model.subject import Subject
10 b3c80ccb David Friesecký
from src.utils.logger import Logger
11 181e1196 Jan Pašek
from src.utils.temporary_file import TemporaryFile
12 c0aed2f5 Stanislav Král
13 76648193 Captain_Trojan
# the prefix of an rsa key
14
KEY_PREFIX = b"-----BEGIN RSA PRIVATE KEY-----"
15
16
# what every encrypted key contains
17
GUARANTEED_SUBSTRING_OF_ENCRYPTED_KEYS = "ENCRYPTED"
18
19 36409852 Stanislav Král
# encryption method to be used when generating private keys
20 c0aed2f5 Stanislav Král
PRIVATE_KEY_ENCRYPTION_METHOD = "-aes256"
21
22
# openssl executable name
23
OPENSSL_EXECUTABLE = "openssl"
24
25 36409852 Stanislav Král
# format of NOT_BEFORE NOT_AFTER date fields
26
NOT_AFTER_BEFORE_DATE_FORMAT = "%b %d %H:%M:%S %Y %Z"
27
28 3e770afd Jan Pašek
# minimal configuration file to be used for openssl req command
29
# specifies distinguished_name that references empty section only
30
# openssl requires this option to be present
31
MINIMAL_CONFIG_FILE = "[req]\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n\n"
32
33
# section to be used to specify extensions when creating a SSCRT
34
SSCRT_SECTION = "sscrt_ext"
35
36
CA_EXTENSIONS = "basicConstraints=critical,CA:TRUE"
37
38 9a55ea8a Stanislav Král
# upper bound of the range of random serial numbers to be generated
39
MAX_SN = 4294967296
40
41 c0aed2f5 Stanislav Král
42
class CryptographyService:
43
44
    @staticmethod
45 57898b2f Stanislav Král
    def __subject_to_param_format(subject):
46
        """
47
        Converts the given subject to a dictionary containing openssl field names mapped to subject's fields
48
        :param subject: subject to be converted
49
        :return: a dictionary containing openssl field names mapped to subject's fields
50
        """
51 b3c80ccb David Friesecký
52
        Logger.debug("Function launched.")
53
54 6c098d6e Stanislav Král
        subj_dict = {}
55
        if subject.common_name is not None:
56
            subj_dict["CN"] = subject.common_name
57
        if subject.country is not None:
58
            subj_dict["C"] = subject.country
59
        if subject.locality is not None:
60
            subj_dict["L"] = subject.locality
61
        if subject.state is not None:
62
            subj_dict["ST"] = subject.state
63
        if subject.organization is not None:
64
            subj_dict["O"] = subject.organization
65
        if subject.organization_unit is not None:
66
            subj_dict["OU"] = subject.organization_unit
67
        if subject.email_address is not None:
68
            subj_dict["emailAddress"] = subject.email_address
69
70
        # merge the subject into a "subj" parameter format
71
        return "".join([f"/{key}={value}" for key, value in subj_dict.items()])
72
73
    @staticmethod
74 7444d4cb Stanislav Král
    def __run_for_output(args=None, proc_input=None, executable=OPENSSL_EXECUTABLE):
75 c0aed2f5 Stanislav Král
        """
76
        Launches a new process in which the given executable is run. STDIN and process arguments can be set.
77
        If the process ends with a non-zero then <CryptographyException> is raised.
78 4691a56f Stanislav Král
79 c0aed2f5 Stanislav Král
        :param args: Arguments to be passed to the program.
80 6c098d6e Stanislav Král
        :param proc_input: String input to be passed to the stdin of the created process.
81 c0aed2f5 Stanislav Král
        :param executable: Executable to be run (defaults to openssl)
82
        :return: If the process ends with a zero return code then the STDOUT of the process is returned as a byte array.
83
        """
84 b3c80ccb David Friesecký
85
        Logger.debug("Function launched.")
86
87 c0aed2f5 Stanislav Král
        if args is None:
88
            args = []
89
        try:
90
            # prepend the name of the executable
91
            args.insert(0, executable)
92
93
            # create a new process
94 fe647b46 Stanislav Král
            proc = subprocess.Popen(args, stdin=subprocess.PIPE if proc_input is not None else None,
95
                                    stdout=subprocess.PIPE,
96 c0aed2f5 Stanislav Král
                                    stderr=subprocess.PIPE)
97
98 6c098d6e Stanislav Král
            out, err = proc.communicate(proc_input)
99 c0aed2f5 Stanislav Král
100
            if proc.returncode != 0:
101
                # if the process did not result in zero result code, then raise an exception
102 7d0aa304 Stanislav Král
                if err is not None and len(err) > 0:
103 b3c80ccb David Friesecký
                    Logger.error("CryptographyException")
104 c0aed2f5 Stanislav Král
                    raise CryptographyException(executable, args, err.decode())
105
                else:
106 b3c80ccb David Friesecký
                    Logger.error("CryptographyException")
107 c0aed2f5 Stanislav Král
                    raise CryptographyException(executable, args,
108
                                                f""""Execution resulted in non-zero argument""")
109
110
            return out
111
        except FileNotFoundError:
112 b3c80ccb David Friesecký
            Logger.error("CryptographyException")
113 c0aed2f5 Stanislav Král
            raise CryptographyException(executable, args, f""""{executable}" not found in the current PATH.""")
114
115
    def create_private_key(self, passphrase=None):
116
        """
117
        Creates a private key with the option to encrypt it using a passphrase.
118
        :param passphrase: A passphrase to be used when encrypting the key (if none is passed then the key is not
119
        encrypted at all). Empty passphrase ("") also results in a key that is not encrypted.
120 18588728 Stanislav Král
        :return: string containing the generated private key in PEM format
121 c0aed2f5 Stanislav Král
        """
122 b3c80ccb David Friesecký
123
        Logger.debug("Function launched.")
124
125 c0aed2f5 Stanislav Král
        if passphrase is None or len(passphrase) == 0:
126 7444d4cb Stanislav Král
            return self.__run_for_output(["genrsa", "2048"]).decode()
127 c0aed2f5 Stanislav Král
        else:
128 7444d4cb Stanislav Král
            return self.__run_for_output(
129 c0aed2f5 Stanislav Král
                ["genrsa", PRIVATE_KEY_ENCRYPTION_METHOD, "-passout", f"pass:{passphrase}", "2048"]).decode()
130
131 be2df9b7 Stanislav Král
    def create_sscrt(self, subject, key, config="", extensions="", key_pass=None, days=30, sn: int = None):
132 c0aed2f5 Stanislav Král
        """
133 329216fe Stanislav Král
        Creates a self signed certificate
134 c0aed2f5 Stanislav Král
135
        :param subject: an instance of <Subject> representing the subject to be added to the certificate
136 02f63b07 Stanislav Král
        :param key: private key of the CA to be used
137 c0aed2f5 Stanislav Král
        :param config: string containing the configuration to be used
138
        :param extensions: name of the section in the configuration representing extensions
139 18588728 Stanislav Král
        :param key_pass: passphrase of the private key
140 2f5101f1 Stanislav Král
        :param days: number of days for which the certificate will be valid
141 9a55ea8a Stanislav Král
        :param sn: serial number to be set, when "None" is set a random serial number is generated
142 c0aed2f5 Stanislav Král
143 18588728 Stanislav Král
        :return: string containing the generated certificate in PEM format
144 c0aed2f5 Stanislav Král
        """
145 b3c80ccb David Friesecký
146
        Logger.debug("Function launched.")
147
148 c0aed2f5 Stanislav Král
        assert key is not None
149
        assert subject is not None
150
151 57898b2f Stanislav Král
        subj = self.__subject_to_param_format(subject)
152 c0aed2f5 Stanislav Král
153 3e770afd Jan Pašek
        # To specify extension for creating a SSCRT, one has to use a configuration
154
        # file instead of an extension file. Therefore the following code creates
155
        # the most basic configuration file with sscrt_ext section, that is later
156
        # reference in openssl req command using -extensions option.
157
        if len(config) == 0:
158 9e6f791a Jan Pašek
            config += MINIMAL_CONFIG_FILE
159 3e770afd Jan Pašek
        config += "\n[ " + SSCRT_SECTION + " ]" + "\n" + extensions
160
161 c0aed2f5 Stanislav Král
        with TemporaryFile("openssl.conf", config) as conf_path:
162 2f5101f1 Stanislav Král
            args = ["req", "-x509", "-new", "-subj", subj, "-days", f"{days}",
163 c0aed2f5 Stanislav Král
                    "-key", "-"]
164 3e770afd Jan Pašek
165 9a55ea8a Stanislav Král
            # serial number passed, use it when generating the certificate,
166
            # without passing it openssl generates a random one
167
            if sn is not None:
168
                args.extend(["-set_serial", str(sn)])
169
170 c0aed2f5 Stanislav Král
            if len(config) > 0:
171
                args.extend(["-config", conf_path])
172
            if len(extensions) > 0:
173 64cfca84 Jan Pašek
                args.extend(["-extensions", SSCRT_SECTION])  # when creating SSCRT, section references section in config
174 c0aed2f5 Stanislav Král
175
            # it would be best to not send the pass phrase at all, but for some reason pytest then prompts for
176
            # the pass phrase (this does not happen when run from pycharm)
177
178 fe647b46 Stanislav Král
            #  add the passphrase even when None is passed. Otherwise when running tests with pytest some tests freeze
179
            # waiting for the passphrase to be typed in
180 18588728 Stanislav Král
            args.extend(["-passin", f"pass:{key_pass}"])
181 c0aed2f5 Stanislav Král
182 7444d4cb Stanislav Král
            return self.__run_for_output(args, proc_input=bytes(key, encoding="utf-8")).decode()
183 6c098d6e Stanislav Král
184 87fd5afc Stanislav Král
    def __create_csr(self, subject, key, key_pass=""):
185 6c098d6e Stanislav Král
        """
186 bdf9a46c Stanislav Král
        Creates a CSR (Certificate Signing Request)
187 6c098d6e Stanislav Král
188
        :param subject: an instance of <Subject> representing the subject to be added to the CSR
189 87fd5afc Stanislav Král
        :param key: the private key of the subject to be used to generate the CSR
190
        :param key_pass: passphrase of the subject's private key
191 18588728 Stanislav Král
        :return: string containing the generated certificate signing request in PEM format
192 6c098d6e Stanislav Král
        """
193
194 b3c80ccb David Friesecký
        Logger.debug("Function launched.")
195
196 57898b2f Stanislav Král
        subj_param = self.__subject_to_param_format(subject)
197 6c098d6e Stanislav Král
198
        args = ["req", "-new", "-subj", subj_param, "-key", "-"]
199
200 fe647b46 Stanislav Král
        # add the passphrase even when None is passed. Otherwise when running tests with pytest some tests freeze
201
        # waiting for the passphrase to be typed in
202 87fd5afc Stanislav Král
        args.extend(["-passin", f"pass:{key_pass}"])
203 6c098d6e Stanislav Král
204 87fd5afc Stanislav Král
        return self.__run_for_output(args, proc_input=bytes(key, encoding="utf-8")).decode()
205 c0aed2f5 Stanislav Král
206 9a55ea8a Stanislav Král
    def __sign_csr(self, csr, issuer_pem, issuer_key, issuer_key_pass=None, extensions="", days=30, sn: int = None):
207 fe647b46 Stanislav Král
        """
208
        Signs the given CSR by the given issuer CA
209 ad068f9d Stanislav Král
210 fe647b46 Stanislav Král
        :param csr: a string containing the CSR to be signed
211
        :param issuer_pem: string containing the certificate of the issuer that will sign this CSR in PEM format
212
        :param issuer_key: string containing the private key of the issuer's certificate in PEM format
213 9dbbcdae Stanislav Král
        :param issuer_key_pass: string containing the passphrase of the private key of the issuer's certificate in PEM
214
        format
215 fe647b46 Stanislav Král
        :param extensions: extensions to be applied when signing the CSR
216 c4b2f4d2 Stanislav Král
        :param days: number of days for which the certificate will be valid
217 9a55ea8a Stanislav Král
        :param sn: serial number to be set, when "None" is set a random serial number is generated
218 fe647b46 Stanislav Král
        :return: string containing the generated and signed certificate in PEM format
219
        """
220
221 b3c80ccb David Friesecký
        Logger.debug("Function launched.")
222
223 fe647b46 Stanislav Král
        # concatenate CSR, issuer certificate and issuer's key (will be used in the openssl call)
224
        proc_input = csr + issuer_pem + issuer_key
225
226 9a55ea8a Stanislav Král
        # TODO find a better way to generate a random serial number or let openssl generate a .srl file
227
        # when serial number is not passed generate a random one
228
        if sn is None:
229
            sn = random.randint(0, MAX_SN)
230
231 fe647b46 Stanislav Král
        # prepare openssl parameters...
232
        # CSR, CA and CA's private key will be passed via stdin (that's the meaning of the '-' symbol)
233 2510f01a Stanislav Král
        params = ["x509", "-req", "-in", "-", "-CA", "-", "-CAkey", "-", "-CAcreateserial", "-days", str(days),
234
                  "-set_serial", str(sn)]
235 fe647b46 Stanislav Král
236
        with TemporaryFile("extensions.conf", extensions) as ext_path:
237
            # add the passphrase even when None is passed. Otherwise when running tests with pytest some tests freeze
238
            # waiting for the passphrase to be typed in
239
            params.extend(["-passin", f"pass:{issuer_key_pass}"])
240
241
            if len(extensions) > 0:
242
                params.extend(["-extfile", ext_path])
243
244 7444d4cb Stanislav Král
            return self.__run_for_output(params, proc_input=(bytes(proc_input, encoding="utf-8"))).decode()
245 fe647b46 Stanislav Král
246 18588728 Stanislav Král
    def create_crt(self, subject, subject_key, issuer_pem, issuer_key, subject_key_pass=None, issuer_key_pass=None,
247 5fdd01a6 Stanislav Král
                   extensions="",
248 9a55ea8a Stanislav Král
                   days=30,
249
                   sn: int = None):
250 9dbbcdae Stanislav Král
        """
251 61a42455 Stanislav Král
        Creates a certificate by using the given subject, subject's key, issuer and its key.
252 ad068f9d Stanislav Král
253 9dbbcdae Stanislav Král
        :param subject: subject to be added to the created certificate
254 18588728 Stanislav Král
        :param subject_key: string containing the private key to be used when creating the certificate in PEM format
255 9dbbcdae Stanislav Král
        :param issuer_key: string containing the private key of the issuer's certificate in PEM format
256
        :param issuer_pem: string containing the certificate of the issuer that will sign this CSR in PEM format
257 36409852 Stanislav Král
        :param subject_key_pass: string containing the passphrase of the private key used when creating the certificate
258
        in PEM format
259 9dbbcdae Stanislav Král
        :param issuer_key_pass: string containing the passphrase of the private key of the issuer's certificate in PEM
260
        format
261
        :param extensions: extensions to be applied when creating the certificate
262 5fdd01a6 Stanislav Král
        :param days: number of days for which the certificate will be valid
263 9a55ea8a Stanislav Král
        :param sn: serial number to be set, when "None" is set a random serial number is generated
264 18588728 Stanislav Král
        :return: string containing the generated certificate in PEM format
265 9dbbcdae Stanislav Král
        """
266 b3c80ccb David Friesecký
267
        Logger.debug("Function launched.")
268
269 87fd5afc Stanislav Král
        csr = self.__create_csr(subject, subject_key, key_pass=subject_key_pass)
270 87a7a4a5 Stanislav Král
        return self.__sign_csr(csr, issuer_pem, issuer_key, issuer_key_pass=issuer_key_pass, extensions=extensions,
271 9a55ea8a Stanislav Král
                               days=days, sn=sn)
272 5fdd01a6 Stanislav Král
273
    @staticmethod
274 61a42455 Stanislav Král
    def verify_cert(certificate):
275
        """
276
        Verifies whether the given certificate is not expired.
277
278
        :param certificate: certificate to be verified in PEM format
279
        :return: Returns `true` if the certificate is not expired, `false` when expired.
280
        """
281 b3c80ccb David Friesecký
282
        Logger.debug("Function launched.")
283
284 5fdd01a6 Stanislav Král
        # call openssl to check whether the certificate is valid to this date
285
        args = [OPENSSL_EXECUTABLE, "x509", "-checkend", "0", "-noout", "-text", "-in", "-"]
286
287
        # create a new process
288
        proc = subprocess.Popen(args, stdin=subprocess.PIPE,
289
                                stdout=subprocess.PIPE,
290
                                stderr=subprocess.PIPE)
291
292
        out, err = proc.communicate(bytes(certificate, encoding="utf-8"))
293
294
        # zero return code means that the certificate is valid
295
        if proc.returncode == 0:
296
            return True
297
        elif proc.returncode == 1 and "Certificate will expire" in out.decode():
298
            # 1 return code means that the certificate is invalid but such message has to be present in the proc output
299
            return False
300
        else:
301
            # the process failed because of some other reason (incorrect cert format)
302 b3c80ccb David Friesecký
            Logger.error("CryptographyException")
303 5fdd01a6 Stanislav Král
            raise CryptographyException(OPENSSL_EXECUTABLE, args, err.decode())
304 9dbbcdae Stanislav Král
305 19e5260d Stanislav Král
    def extract_public_key_from_private_key(self, private_key_pem: str, passphrase=None) -> str:
306 5c748d51 Stanislav Král
        """
307 e8face67 Stanislav Král
        Extracts a public key from the given private key passed in PEM format
308
        :param private_key_pem: PEM data representing the private key from which a public key should be extracted
309
        :param passphrase: passphrase to be provided when the supplied private key is encrypted
310 5c748d51 Stanislav Král
        :return: a string containing the extracted public key in PEM format
311
        """
312 b3c80ccb David Friesecký
313
        Logger.debug("Function launched.")
314
315 e8face67 Stanislav Král
        args = ["rsa", "-in", "-", "-pubout"]
316
        if passphrase is not None:
317
            args.extend(["-passin", f"pass:{passphrase}"])
318
        return self.__run_for_output(args, proc_input=bytes(private_key_pem, encoding="utf-8")).decode()
319 5c748d51 Stanislav Král
320 19e5260d Stanislav Král
    def extract_public_key_from_certificate(self, cert_pem: str) -> str:
321
        """
322
        Extracts a public key from the given certificate passed in PEM format
323
        :param cert_pem: PEM data representing a certificate from which a public key should be extracted
324
        :return: a string containing the extracted public key in PEM format
325
        """
326 b3c80ccb David Friesecký
327
        Logger.debug("Function launched.")
328
329 19e5260d Stanislav Král
        # extracting public key from a certificate does not seem to require a passphrase even when
330
        # signed using an encrypted PK
331
        args = ["x509", "-in", "-", "-noout", "-pubkey"]
332
        return self.__run_for_output(args, proc_input=bytes(cert_pem, encoding="utf-8")).decode()
333
334 4a40b0d2 Stanislav Král
    def parse_cert_pem(self, cert_pem):
335 cc51ca2c Stanislav Král
        """
336 36409852 Stanislav Král
        Parses the given certificate in PEM format and returns the subject of the certificate and it's NOT_BEFORE
337
        and NOT_AFTER field
338 cc51ca2c Stanislav Král
        :param cert_pem: a certificated in a PEM format to be parsed
339 36409852 Stanislav Král
        :return: a tuple containing a subject, NOT_BEFORE and NOT_AFTER dates
340 cc51ca2c Stanislav Král
        """
341 b3c80ccb David Friesecký
342
        Logger.debug("Function launched.")
343
344 cc51ca2c Stanislav Král
        # run openssl x509 to view certificate content
345 36409852 Stanislav Král
        args = ["x509", "-noout", "-subject", "-startdate", "-enddate", "-in", "-"]
346 cc51ca2c Stanislav Král
347 36409852 Stanislav Král
        cert_info_raw = self.__run_for_output(args, proc_input=bytes(cert_pem, encoding="utf-8")).decode()
348
349
        # split lines
350
        results = re.split("\n", cert_info_raw)
351 3e770afd Jan Pašek
        subj_line = results[0].strip()
352
        not_before_line = results[1].strip()
353
        not_after_line = results[2].strip()
354 36409852 Stanislav Král
355
        # attempt to extract subject via regex
356
        match = re.search(r"subject=(.*)", subj_line)
357 4a40b0d2 Stanislav Král
        if match is None:
358 cc51ca2c Stanislav Král
            # TODO use logger
359 36409852 Stanislav Král
            print(f"Could not find subject to parse: {subj_line}")
360 cc51ca2c Stanislav Král
            return None
361 4a40b0d2 Stanislav Král
        else:
362 36409852 Stanislav Král
            # find all attributes (key = value)
363
            found = re.findall(r"\s?([^c=\s]+)\s?=\s?([^,\n]+)", match.group(1))
364 cc51ca2c Stanislav Král
            subj = Subject()
365
            for key, value in found:
366
                if key == "C":
367 3e770afd Jan Pašek
                    subj.country = value.strip()
368 cc51ca2c Stanislav Král
                elif key == "ST":
369 3e770afd Jan Pašek
                    subj.state = value.strip()
370 cc51ca2c Stanislav Král
                elif key == "L":
371 3e770afd Jan Pašek
                    subj.locality = value.strip()
372 cc51ca2c Stanislav Král
                elif key == "O":
373 3e770afd Jan Pašek
                    subj.organization = value.strip()
374 cc51ca2c Stanislav Král
                elif key == "OU":
375 3e770afd Jan Pašek
                    subj.organization_unit = value.strip()
376 cc51ca2c Stanislav Král
                elif key == "CN":
377 3e770afd Jan Pašek
                    subj.common_name = value.strip()
378 cc51ca2c Stanislav Král
                elif key == "emailAddress":
379 3e770afd Jan Pašek
                    subj.email_address = value.strip()
380 36409852 Stanislav Král
381
        # extract notBefore and notAfter date fields
382
        not_before = re.search(r"notBefore=(.*)", not_before_line)
383
        not_after = re.search(r"notAfter=(.*)", not_after_line)
384
385
        # if date fields are found parse them into date objects
386
        if not_before is not None:
387 4faab824 Jan Pašek
            not_before = time.strptime(not_before.group(1).strip(), NOT_AFTER_BEFORE_DATE_FORMAT)
388 36409852 Stanislav Král
        if not_after is not None:
389 4faab824 Jan Pašek
            not_after = time.strptime(not_after.group(1).strip(), NOT_AFTER_BEFORE_DATE_FORMAT)
390 36409852 Stanislav Král
391
        # TODO wrapper class?
392
        # return it as a tuple
393
        return subj, not_before, not_after
394 4a40b0d2 Stanislav Král
395 81dbb479 Jan Pašek
    def get_openssl_version(self) -> str:
396
        """
397
        Get version of the OpenSSL installed on the system
398
        :return: version of the OpenSSL as returned from the process
399
        """
400 b3c80ccb David Friesecký
401
        Logger.debug("Function launched.")
402
403 81dbb479 Jan Pašek
        return self.__run_for_output(["version"]).decode("utf-8")
404
405 64cfca84 Jan Pašek
    def generate_crl(self, cert: Certificate, key: PrivateKey, index_file_path: str) -> str:
406 0fd6d825 Jan Pašek
        """
407
        Generate a CertificateRevocationList for a specified
408
        certificate authority.
409
410 64cfca84 Jan Pašek
        :param key: key that is used to sign the CRL (must belong to the given certificate)
411
        :param cert: Certificate of the certificate authority that issue the CRL
412 0fd6d825 Jan Pašek
        :param index_file_path: path to a file that contains the openssl index with all revoked certificates
413
        :return: CRL encoded in PEM format string
414
        """
415 b3c80ccb David Friesecký
416
        Logger.debug("Function launched.")
417
418 dd91fb7a Jan Pašek
        # openssl ca requires the .srl file to exists, therefore a dummy, unused file is created
419 64cfca84 Jan Pašek
        with TemporaryFile("serial.srl", "0") as serial_file, \
420 76648193 Captain_Trojan
                TemporaryFile("crl.conf", CRL_CONFIG % (index_file_path, serial_file)) as config_file, \
421
                TemporaryFile("certificate.pem", cert.pem_data) as cert_file, \
422
                TemporaryFile("private_key.pem", key.private_key) as key_file:
423 64cfca84 Jan Pašek
            args = ["ca", "-config", config_file, "-gencrl", "-keyfile", key_file, "-cert", cert_file, "-outdir", "."]
424 94e89bb1 Jan Pašek
425
            if key.password is not None and key.password != "":
426
                args.extend(["-passin", f"pass:{key.password}"])
427
428 64cfca84 Jan Pašek
            return self.__run_for_output(args).decode("utf-8")
429
430 7f9b2c58 Captain_Trojan
    def generate_ocsp(self, cert, key, index_path, der_ocsp_request):
431
        """
432
        Generate an OCSP Response from an OCSP Request given the issuer cert, issuer cert key and the index file.
433
        The OSCP Response is signed by the CA itself (recommended way according to multiple sources).
434
435
        :param cert: issuer certificate
436
        :param key: corresponding key
437
        :param index_path: path/to/the/generated/index/file
438
        :param der_ocsp_request: DER encoded OCSP Request
439
        :return: DER encoded OCSP Response
440
        """
441 b3c80ccb David Friesecký
442
        Logger.debug("Function launched.")
443
444 7f9b2c58 Captain_Trojan
        with TemporaryFile("certificate.pem", cert.pem_data) as ca_certificate, \
445 76648193 Captain_Trojan
                TemporaryFile("private_key.pem", key.private_key) as key_file, \
446
                TemporaryFile("request.der", der_ocsp_request) as request_file:
447 7f9b2c58 Captain_Trojan
            args = ["ocsp", "-index", index_path, "-CA", ca_certificate, "-rsigner", ca_certificate, "-rkey", key_file,
448
                    "-reqin", request_file, "-respout", "-"]
449
450
            if key.password is not None and key.password != "":
451
                args.extend(["-passin", f"pass:{key.password}"])
452
453
            return self.__run_for_output(args)
454
455 76648193 Captain_Trojan
    def verify_key(self, key, passphrase):
456
        """
457
        Verifies whether the provided key is encrypted by the provided passphrase. If passphrase is none, verifies
458
        that the provided key is unencrypted.
459
        :param key: target key
460
        :param passphrase: target passphrase or None
461
        :return: True if the condition is fulfilled, else False
462
        """
463
        if passphrase is None:
464 9a466331 Captain_Trojan
            if re.search(GUARANTEED_SUBSTRING_OF_ENCRYPTED_KEYS, key) is not None:
465
                return False
466
            else:
467
                try:
468
                    with TemporaryFile("tested_key.pem", key) as f:
469
                        ret = self.__run_for_output(["rsa", "-in", f, "-inform", "PEM"])
470
                    return ret.startswith(KEY_PREFIX)
471
                except CryptographyException:
472
                    return False
473 76648193 Captain_Trojan
        else:
474
            if re.search(GUARANTEED_SUBSTRING_OF_ENCRYPTED_KEYS, key) is None:
475
                return False
476
            else:
477
                try:
478
                    with TemporaryFile("tested_key.pem", key) as f:
479
                        ret = self.__run_for_output(["rsa", "-in", f, "-passin", f"pass:{passphrase}"])
480
                    return ret.startswith(KEY_PREFIX)
481
                except CryptographyException:
482
                    return False
483
484 c0aed2f5 Stanislav Král
485
class CryptographyException(Exception):
486
487
    def __init__(self, executable, args, message):
488
        self.executable = executable
489
        self.args = args
490
        self.message = message
491
492
    def __str__(self):
493 b3c80ccb David Friesecký
        # TODO check log is valid here
494
        msg = f"""
495 c0aed2f5 Stanislav Král
        EXECUTABLE: {self.executable}
496
        ARGS: {self.args}
497
        MESSAGE: {self.message}
498
        """
499 b3c80ccb David Friesecký
500
        Logger.error(msg)
501
        return msg