Projekt

Obecné

Profil

Stáhnout (24.4 KB) Statistiky
| Větev: | Tag: | Revize:
1 ef65f488 Stanislav Král
from typing import List
2
3 151e7604 Jan Pašek
from injector import inject
4
5 20b33bd4 Jan Pašek
from src.config.configuration import Configuration
6
from src.constants import ROOT_CA_ID, INTERMEDIATE_CA_ID, CA_ID, CERTIFICATE_ID, CERTIFICATE_STATES, \
7 c15357a9 Jan Pašek
    CERTIFICATE_REVOCATION_REASONS, SSL_ID, SIGNATURE_ID, AUTHENTICATION_ID, CERTIFICATE_VALID, CERTIFICATE_EXPIRED, \
8
    CERTIFICATE_REVOKED
9 4a40b0d2 Stanislav Král
from src.dao.certificate_repository import CertificateRepository
10 8ff50e4e Jan Pašek
from src.exceptions.certificate_not_found_exception import CertificateNotFoundException
11 485913d0 Captain_Trojan
from src.exceptions.database_exception import DatabaseException
12 9e6f791a Jan Pašek
from src.exceptions.unknown_exception import UnknownException
13 4a40b0d2 Stanislav Král
from src.model.certificate import Certificate
14 313b647b Stanislav Král
from src.model.private_key import PrivateKey
15 4a40b0d2 Stanislav Král
from src.model.subject import Subject
16
from src.services.cryptography import CryptographyService
17
18 313b647b Stanislav Král
import time
19
20 329216fe Stanislav Král
from src.utils.usages_to_extensions import usages_to_extension_lines, ExtensionFieldFlags, CRITICAL, KEY_CERT_SIGN, \
21
    CRL_SIGN, CA, DIGITAL_SIGNATURE, KEY_ENCIPHERMENT, KEY_AGREEMENT, SERVER_AUTH, NON_REPUDIATION, TIME_STAMPING, \
22
    CLIENT_AUTH
23
24 b3c80ccb David Friesecký
from src.utils.logger import Logger
25
26 7313994f Stanislav Král
VALID_FROM_TO_DATE_FORMAT = "%d.%m.%Y %H:%M:%S"
27 bbcb7c89 Stanislav Král
CA_EXTENSIONS = "basicConstraints=critical,CA:TRUE"
28 20b33bd4 Jan Pašek
CRL_EXTENSION = "crlDistributionPoints=URI:"
29
OCSP_EXTENSION = "authorityInfoAccess=OCSP;URI:"
30
STATUS_REVOKED = "revoked"
31
STATUS_VALID = "valid"
32 313b647b Stanislav Král
33 329216fe Stanislav Král
# define which flags are required for various usages
34
REQUIRED_USAGE_EXTENSION_FLAGS = {
35
    CA_ID: ExtensionFieldFlags({CRITICAL, KEY_CERT_SIGN, CRL_SIGN}, {}, {CRITICAL, CA}),
36
    SSL_ID: ExtensionFieldFlags({DIGITAL_SIGNATURE, KEY_ENCIPHERMENT, KEY_AGREEMENT}, {SERVER_AUTH}, {}),
37 52f2eca4 Jan Pašek
    SIGNATURE_ID: ExtensionFieldFlags({DIGITAL_SIGNATURE, NON_REPUDIATION}, {}, {}),
38 329216fe Stanislav Král
    AUTHENTICATION_ID: ExtensionFieldFlags({DIGITAL_SIGNATURE}, {CLIENT_AUTH}, {})}
39
40 4a40b0d2 Stanislav Král
41
class CertificateService:
42
43 151e7604 Jan Pašek
    @inject
44 20b33bd4 Jan Pašek
    def __init__(self, cryptography_service: CryptographyService,
45
                 certificate_repository: CertificateRepository,
46
                 configuration: Configuration):
47 4a40b0d2 Stanislav Král
        self.cryptography_service = cryptography_service
48
        self.certificate_repository = certificate_repository
49 20b33bd4 Jan Pašek
        self.configuration = configuration
50 4a40b0d2 Stanislav Král
51 bbcb7c89 Stanislav Král
    # TODO usages present in method parameters but not in class diagram
52 ca3ac7c0 Stanislav Král
    def create_root_ca(self, key: PrivateKey, subject: Subject, extensions: str = "", config: str = "",
53 2f5101f1 Stanislav Král
                       usages=None, days=30):
54 a6727aa9 Stanislav Král
        """
55
        Creates a root CA certificate based on the given parameters.
56
        :param key: Private key to be used when generating the certificate
57
        :param subject: Subject to be used put into the certificate
58
        :param config: String containing the configuration to be used
59
        :param extensions: Name of the section in the configuration representing extensions
60
        :param usages: A dictionary containing usages of the certificate to be generated (see constants.py)
61 2f5101f1 Stanislav Král
        :param days: Number of days for which the generated cert. will be considered valid
62 a6727aa9 Stanislav Král
        :return: An instance of Certificate class representing the generated root CA cert
63
        """
64 b3c80ccb David Friesecký
65
        Logger.debug("Function launched.")
66
67 ca3ac7c0 Stanislav Král
        if usages is None:
68
            usages = {}
69
70 20b33bd4 Jan Pašek
        cert_id = self.certificate_repository.get_next_id()
71
72 329216fe Stanislav Král
        # specify CA usage
73
        usages[CA_ID] = True
74
75
        # generate extension configuration lines based on the specified usages
76
        extensions = extensions + "\n" + "\n".join(usages_to_extension_lines(usages, REQUIRED_USAGE_EXTENSION_FLAGS))
77
78 313b647b Stanislav Král
        # create a new self signed  certificate
79
        cert_pem = self.cryptography_service.create_sscrt(subject, key.private_key, key_pass=key.password,
80 87c56935 Stanislav Král
                                                          extensions=extensions, config=config, days=days, sn=cert_id)
81 ca3ac7c0 Stanislav Král
82 4c19a9b1 Stanislav Král
        # wrap into Certificate class
83 a6727aa9 Stanislav Král
        certificate = self.__create_wrapper(cert_pem, key.private_key_id, usages, 0,
84 4c19a9b1 Stanislav Král
                                            ROOT_CA_ID)
85 313b647b Stanislav Král
86
        # store the wrapper into the repository
87
        created_id = self.certificate_repository.create(certificate)
88
89
        # assign the generated ID to the inserted certificate
90
        certificate.certificate_id = created_id
91 4a40b0d2 Stanislav Král
92 313b647b Stanislav Král
        return certificate
93 10fab051 Stanislav Král
94 a6727aa9 Stanislav Král
    def __create_wrapper(self, cert_pem, private_key_id, usages, parent_id, cert_type):
95
        """
96 a4e818dc Jan Pašek
        Wraps the given parameters using the Certificate class. Uses CryptographyService to find out the notBefore and
97 a6727aa9 Stanislav Král
        notAfter fields.
98
        :param cert_pem: PEM of the cert. to be wrapped
99
        :param private_key_id: ID of the private key used to create the given certificate
100
        :param usages: A dictionary containing usages of the generated certificate generated (see constants.py)
101
        :param parent_id: ID of the CA that issued this certificate
102
        :param cert_type: Type of this certificate (see constants.py)
103
        :return: An instance of the Certificate class wrapping the values passed  via method parameters
104
        """
105 b3c80ccb David Friesecký
106
        Logger.debug("Function launched.")
107
108 4c19a9b1 Stanislav Král
        # parse the generated pem for subject and notBefore/notAfter fields
109 a6727aa9 Stanislav Král
        # TODO this could be improved in the future in such way that calling openssl is not required to parse the dates
110 4c19a9b1 Stanislav Král
        subj, not_before, not_after = self.cryptography_service.parse_cert_pem(cert_pem)
111
        # format the parsed date
112 7313994f Stanislav Král
        not_before_formatted = time.strftime(VALID_FROM_TO_DATE_FORMAT, not_before)
113
        not_after_formatted = time.strftime(VALID_FROM_TO_DATE_FORMAT, not_after)
114 4c19a9b1 Stanislav Král
115
        # create a certificate wrapper
116 a6727aa9 Stanislav Král
        certificate = Certificate(-1, subj.common_name, not_before_formatted, not_after_formatted, cert_pem,
117 4c19a9b1 Stanislav Král
                                  private_key_id, cert_type, parent_id, usages)
118
119
        return certificate
120
121 bbcb7c89 Stanislav Král
    # TODO config parameter present in class diagram but not here (unused)
122
    def create_ca(self, subject_key: PrivateKey, subject: Subject, issuer_cert: Certificate, issuer_key: PrivateKey,
123 ca3ac7c0 Stanislav Král
                  extensions: str = "", days: int = 30, usages=None):
124 a6727aa9 Stanislav Král
        """
125
        Creates an intermediate CA certificate issued by the given parent CA.
126
        :param subject_key: Private key to be used when generating the certificate
127
        :param subject: Subject to be used put into the certificate
128
        :param issuer_cert: Issuer certificate that will sign the CSR required to create an intermediate CA
129
        :param issuer_key: PK used to generate the issuer certificate
130
        :param extensions: Extensions to be used when generating the certificate
131
        :param usages: A dictionary containing usages of the certificate to be generated (see constants.py)
132
        :param days: Number of days for which the generated cert. will be considered valid
133
        :return: An instance of Certificate class representing the generated intermediate CA cert
134
        """
135 b3c80ccb David Friesecký
136
        Logger.debug("Function launched.")
137
138 ca3ac7c0 Stanislav Král
        if usages is None:
139
            usages = {}
140
141 329216fe Stanislav Král
        # specify CA usage
142
        usages[CA_ID] = True
143
144
        # generate extension configuration lines based on the specified usages
145
        extensions = extensions + "\n" + "\n".join(usages_to_extension_lines(usages, REQUIRED_USAGE_EXTENSION_FLAGS))
146
147 20b33bd4 Jan Pašek
        # Add CRL and OCSP distribution point to certificate extensions
148
        cert_id = self.certificate_repository.get_next_id()
149 ea1229ee Jan Pašek
        extensions = extensions + "\n" + CRL_EXTENSION + " " + self.__get_crl_endpoint(issuer_cert.certificate_id)
150
        extensions = extensions + "\n" + OCSP_EXTENSION + " " + self.__get_ocsp_endpoint(issuer_cert.certificate_id)
151 20b33bd4 Jan Pašek
152 bbcb7c89 Stanislav Král
        # TODO implement AIA URI via extensions
153
        cert_pem = self.cryptography_service.create_crt(subject, subject_key.private_key, issuer_cert.pem_data,
154
                                                        issuer_key.private_key,
155
                                                        subject_key_pass=subject_key.password,
156
                                                        issuer_key_pass=issuer_key.password, extensions=extensions,
157 87c56935 Stanislav Král
                                                        days=days,
158
                                                        sn=cert_id)
159 bbcb7c89 Stanislav Král
160 4c19a9b1 Stanislav Král
        # wrap into Certificate class
161 a6727aa9 Stanislav Král
        self.__create_wrapper(cert_pem, subject_key.private_key_id, usages,
162 4c19a9b1 Stanislav Král
                              issuer_cert.certificate_id, INTERMEDIATE_CA_ID)
163
164 bbcb7c89 Stanislav Král
        # parse the generated pem for subject and notBefore/notAfter fields
165
        subj, not_before, not_after = self.cryptography_service.parse_cert_pem(cert_pem)
166
167
        # format the parsed date
168 7313994f Stanislav Král
        not_before_formatted = time.strftime(VALID_FROM_TO_DATE_FORMAT, not_before)
169
        not_after_formatted = time.strftime(VALID_FROM_TO_DATE_FORMAT, not_after)
170 bbcb7c89 Stanislav Král
171
        # create a certificate wrapper
172
        certificate = Certificate(-1, subject.common_name, not_before_formatted, not_after_formatted, cert_pem,
173 ca3ac7c0 Stanislav Král
                                  subject_key.private_key_id, INTERMEDIATE_CA_ID, issuer_cert.certificate_id, usages)
174 bbcb7c89 Stanislav Král
175
        # store the wrapper into the repository
176
        created_id = self.certificate_repository.create(certificate)
177
178
        # assign the generated ID to the inserted certificate
179
        certificate.certificate_id = created_id
180
181
        return certificate
182
183 4c19a9b1 Stanislav Král
    def create_end_cert(self, subject_key: PrivateKey, subject: Subject, issuer_cert: Certificate,
184
                        issuer_key: PrivateKey,
185
                        extensions: str = "", days: int = 30, usages=None):
186 a6727aa9 Stanislav Král
        """
187
        Creates an end certificate issued by the given parent CA.
188
        :param subject_key: Private key to be used when generating the certificate
189
        :param subject: Subject to be used put into the certificate
190
        :param issuer_cert: Issuer certificate that will sign the CSR required to create an intermediate CA
191
        :param issuer_key: PK used to generate the issuer certificate
192
        :param extensions: Extensions to be used when generating the certificate
193
        :param usages: A dictionary containing usages of the certificate to be generated (see constants.py)
194
        :param days: Number of days for which the generated cert. will be considered valid
195
        :return: An instance of Certificate class representing the generated cert
196
        """
197 b3c80ccb David Friesecký
198
        Logger.debug("Function launched.")
199
200 4c19a9b1 Stanislav Král
        if usages is None:
201
            usages = {}
202
203 87c56935 Stanislav Král
        # get the next certificate ID in order to be able to specify the serial number
204
        cert_id = self.certificate_repository.get_next_id()
205
206 329216fe Stanislav Král
        # generate extension configuration lines based on the specified usages
207
        extensions = extensions + "\n" + "\n".join(usages_to_extension_lines(usages, REQUIRED_USAGE_EXTENSION_FLAGS))
208
209 ea1229ee Jan Pašek
        # Add CRL and OCSP distribution point to certificate extensions
210
        extensions = extensions + "\n" + CRL_EXTENSION + " " + self.__get_crl_endpoint(issuer_cert.certificate_id)
211
        extensions = extensions + "\n" + OCSP_EXTENSION + " " + self.__get_ocsp_endpoint(issuer_cert.certificate_id)
212
213 4c19a9b1 Stanislav Král
        # generate a new certificate
214
        cert_pem = self.cryptography_service.create_crt(subject, subject_key.private_key, issuer_cert.pem_data,
215
                                                        issuer_key.private_key,
216
                                                        subject_key_pass=subject_key.password,
217
                                                        issuer_key_pass=issuer_key.password, extensions=extensions,
218 87c56935 Stanislav Král
                                                        days=days,
219
                                                        sn=cert_id
220
                                                        )
221 4c19a9b1 Stanislav Král
222
        # wrap the generated certificate using Certificate class
223 a6727aa9 Stanislav Král
        certificate = self.__create_wrapper(cert_pem, subject_key.private_key_id, usages,
224 4c19a9b1 Stanislav Král
                                            issuer_cert.certificate_id, CERTIFICATE_ID)
225
226
        created_id = self.certificate_repository.create(certificate)
227
228
        certificate.certificate_id = created_id
229
230
        return certificate
231
232 10fab051 Stanislav Král
    def get_certificate(self, unique_id: int) -> Certificate:
233 a6727aa9 Stanislav Král
        """
234
        Tries to fetch a certificate from the certificate repository using a given id.
235
        :param unique_id: ID of the certificate to be fetched
236
        :return: Instance of the Certificate class containing a certificate with the given id or `None` if such
237
        certificate is not found
238
        """
239 b3c80ccb David Friesecký
240
        Logger.debug("Function launched.")
241
242 10fab051 Stanislav Král
        return self.certificate_repository.read(unique_id)
243 2a90f4fd Stanislav Král
244 ef65f488 Stanislav Král
    def get_certificates(self, cert_type=None) -> List[Certificate]:
245 a6727aa9 Stanislav Král
        """
246
        Tries to fetch a list of all certificates from the certificate repository. Using the `cert_type` parameter only
247
        certificates of the given type can be returned.
248
        :param cert_type: Type of certificates to be returned
249
        :return: List of instances of the Certificate class representing all certificates present in the certificate
250
        repository. An empty list is returned when no certificates are found.
251
        """
252 b3c80ccb David Friesecký
253
        Logger.debug("Function launched.")
254
255 2a90f4fd Stanislav Král
        return self.certificate_repository.read_all(cert_type)
256 ef65f488 Stanislav Král
257
    def get_chain_of_trust(self, from_id: int, to_id: int = -1, exclude_root=True) -> List[Certificate]:
258 4e70d22a Stanislav Král
        """
259
        Traverses the certificate hierarchy tree upwards till a certificate with the `to_id` ID is found or till a
260
        root CA certificate is found. Root certificates are excluded from the chain by default.
261
        :param from_id: ID of the first certificate to be included in the chain of trust
262
        :param to_id: ID of the last certificate to be included in the chain of trust
263
        :param exclude_root: a flag indicating whether root CA certificate should be excluded
264
        :return: a list of certificates representing the chain of trust starting with the certificate given by `from_id`
265
        ID
266
        """
267 b3c80ccb David Friesecký
268
        Logger.debug("Function launched.")
269
270 4e70d22a Stanislav Král
        # read the first certificate of the chain
271 ef65f488 Stanislav Král
        start_cert = self.certificate_repository.read(from_id)
272
273 4e70d22a Stanislav Král
        # if no cert is found or the current cert is root CA and root CAs should be excluded, then return an empty list
274 ef65f488 Stanislav Král
        if start_cert is None or (start_cert.type_id == ROOT_CA_ID and exclude_root):
275
            return []
276
277
        current_cert = start_cert
278
        chain_of_trust = [current_cert]
279
280
        # TODO could possibly be simplified
281
        if start_cert.type_id == ROOT_CA_ID:
282 4e70d22a Stanislav Král
            # the first cert found is a root ca
283 ef65f488 Stanislav Král
            return chain_of_trust
284
285
        while True:
286
            parent_cert = self.certificate_repository.read(current_cert.parent_id)
287
288 4e70d22a Stanislav Král
            # check whether parent certificate exists
289
            if parent_cert is None:
290
                break
291
292
            # check whether the found certificate is a root certificate
293
            if parent_cert.type_id == ROOT_CA_ID:
294 ef65f488 Stanislav Král
                if not exclude_root:
295 4e70d22a Stanislav Král
                    # append the found root cert only if root certificates should not be excluded from the CoT
296 ef65f488 Stanislav Král
                    chain_of_trust.append(parent_cert)
297
                break
298
299 4e70d22a Stanislav Král
            # append the certificate
300 ef65f488 Stanislav Král
            chain_of_trust.append(parent_cert)
301
302 4e70d22a Stanislav Král
            # stop iterating over certificates if the id of the found certificate matches `to_id` method parameter
303 ef65f488 Stanislav Král
            if parent_cert.certificate_id == to_id:
304
                break
305
306
            current_cert = parent_cert
307
308
        return chain_of_trust
309 3d639744 Stanislav Král
310 5f8a2c07 Captain_Trojan
    def delete_certificate(self, unique_id):
311 3d639744 Stanislav Král
        """
312 5f8a2c07 Captain_Trojan
        Deletes a certificate. Raises an Exception should any unexpected behavior occur.
313 3d639744 Stanislav Král
314
        :param unique_id: ID of specific certificate
315
        """
316 5f8a2c07 Captain_Trojan
317 b3c80ccb David Friesecký
        Logger.debug("Function launched.")
318
319 5f8a2c07 Captain_Trojan
        to_delete = self.certificate_repository.get_all_descendants_of(unique_id)
320
        if to_delete is None:
321 b3c80ccb David Friesecký
            Logger.error(f"No such certificate found 'ID = {unique_id}'.")
322 5f8a2c07 Captain_Trojan
            raise CertificateNotFoundException(unique_id)
323
324
        for cert in to_delete:
325
            try:
326
                self.set_certificate_revocation_status(cert.certificate_id, STATUS_REVOKED)
327
            except CertificateAlreadyRevokedException:
328 b3c80ccb David Friesecký
                Logger.info(f"Certificate already revoked 'ID = {unique_id}'.")
329 5f8a2c07 Captain_Trojan
                # TODO log as an info/debug, not warning or above <-- perfectly legal
330 02954c9d Jan Pašek
                pass
331 5f8a2c07 Captain_Trojan
332 b3c80ccb David Friesecký
            if not self.certificate_repository.delete(cert.certificate_id):
333
                Logger.error(f"The certificate has not been deleted 'ID = {cert.certificate_id}'.")
334
335 c4ba6bb7 Jan Pašek
336 485913d0 Captain_Trojan
    def get_certificates_issued_by(self, unique_id):
337
        """
338
        Returns a list of all children of a certificate identified by an unique_id.
339
        Raises a DatabaseException should any unexpected behavior occur.
340
        :param unique_id: target certificate ID
341
        :return: children of unique_id
342
        """
343 b3c80ccb David Friesecký
344
        Logger.debug("Function launched.")
345
346 485913d0 Captain_Trojan
        try:
347
            if self.certificate_repository.read(unique_id) is None:
348 b3c80ccb David Friesecký
                Logger.error(f"No such certificate found 'ID = {unique_id}'.")
349 485913d0 Captain_Trojan
                raise CertificateNotFoundException(unique_id)
350
        except DatabaseException:
351 b3c80ccb David Friesecký
            Logger.error(f"No such certificate found 'ID = {unique_id}'.")
352 485913d0 Captain_Trojan
            raise CertificateNotFoundException(unique_id)
353
354
        return self.certificate_repository.get_all_issued_by(unique_id)
355
356 20b33bd4 Jan Pašek
    def set_certificate_revocation_status(self, id, status, reason="unspecified"):
357
        """
358
        Set certificate status to 'valid' or 'revoked'.
359
        If the new status is revoked a reason can be provided -> default is unspecified
360
        :param reason: reason for revocation
361
        :param id: identifier of the certificate whose status is to be changed
362
        :param status: new status of the certificate
363
        """
364 b3c80ccb David Friesecký
365
        Logger.debug("Function launched.")
366
367 20b33bd4 Jan Pašek
        if status not in CERTIFICATE_STATES:
368 b3c80ccb David Friesecký
            Logger.error(f"Wrong parameter, invalid status '{status}'.")
369 20b33bd4 Jan Pašek
            raise CertificateStatusInvalidException(status)
370
        if reason not in CERTIFICATE_REVOCATION_REASONS:
371 b3c80ccb David Friesecký
            Logger.error(f"Wrong parameter, invalid reason '{reason}'.")
372 20b33bd4 Jan Pašek
            raise RevocationReasonInvalidException(reason)
373
374 9e6f791a Jan Pašek
        # check whether the certificate exists
375
        certificate = self.certificate_repository.read(id)
376
        if certificate is None:
377 b3c80ccb David Friesecký
            Logger.error(f"No such certificate found 'ID = {id}'.")
378 9e6f791a Jan Pašek
            raise CertificateNotFoundException(id)
379
380 9c704fb1 Jan Pašek
        updated = False
381 20b33bd4 Jan Pašek
        if status == STATUS_VALID:
382 9c704fb1 Jan Pašek
            updated = self.certificate_repository.clear_certificate_revocation(id)
383 20b33bd4 Jan Pašek
        elif status == STATUS_REVOKED:
384 9e6f791a Jan Pašek
            # check if the certificate is not revoked already
385
            revoked = self.certificate_repository.get_all_revoked_by(certificate.parent_id)
386
            if certificate.certificate_id in [x.certificate_id for x in revoked]:
387 b3c80ccb David Friesecký
                Logger.error(f"Certificate already revoked 'ID = {id}'.")
388 9e6f791a Jan Pašek
                raise CertificateAlreadyRevokedException(id)
389
390 20b33bd4 Jan Pašek
            revocation_timestamp = int(time.time())
391 9c704fb1 Jan Pašek
            updated = self.certificate_repository.set_certificate_revoked(id, str(revocation_timestamp), reason)
392
393
        if not updated:
394 b3c80ccb David Friesecký
            Logger.error(f"Repository returned 'false' from clear_certificate_revocation() "
395
                         f"or set_certificate_revoked().")
396 9e6f791a Jan Pašek
            raise UnknownException("Repository returned 'false' from clear_certificate_revocation() "
397
                                   "or set_certificate_revoked().")
398 20b33bd4 Jan Pašek
399 c4ba6bb7 Jan Pašek
    def get_subject_from_certificate(self, certificate: Certificate) -> Subject:
400
        """
401
        Get Subject distinguished name from a Certificate
402
        :param certificate: certificate instance whose Subject shall be parsed
403
        :return: instance of Subject class containing resulting distinguished name
404
        """
405 b3c80ccb David Friesecký
406
        Logger.debug("Function launched.")
407
408 c4ba6bb7 Jan Pašek
        (subject, _, _) = self.cryptography_service.parse_cert_pem(certificate.pem_data)
409
        return subject
410 d3bfacfc Stanislav Král
411
    def get_public_key_from_certificate(self, certificate: Certificate):
412
        """
413
        Extracts a public key from the given certificate
414
        :param certificate: an instance of the Certificate class containing the certificate from which a public key
415
        should be extracted.
416
        :return: a string containing the extracted public key in PEM format
417
        """
418 b3c80ccb David Friesecký
419
        Logger.debug("Function launched.")
420
421 d3bfacfc Stanislav Král
        return self.cryptography_service.extract_public_key_from_certificate(certificate.pem_data)
422 20b33bd4 Jan Pašek
423 a53e5aef Jan Pašek
    def get_certificate_state(self, id: int) -> str:
424
        """
425
        Check whether the certificate is expired, valid or revoked.
426
            - in case it's revoked and expired, revoked is returned
427
        :param id: identifier of the certificate
428
        :return: certificates state from {valid, revoked, expired}
429
        :raises CertificateNotFoundException: in case id of non-existing certificate is entered
430
        """
431 4ff15a44 Jan Pašek
        Logger.debug("Function launched.")
432 c15357a9 Jan Pašek
        status = CERTIFICATE_VALID
433
434
        # Read the selected certificate from the repository
435
        certificate = self.certificate_repository.read(id)
436
        if certificate is None:
437 1fa20e93 Stanislav Král
            Logger.error("Certificate whose details were requested does not exist.")
438 c15357a9 Jan Pašek
            raise CertificateNotFoundException(id)
439
440
        # check the expiration date using OpenSSL
441
        if not self.cryptography_service.verify_cert(certificate.pem_data):
442
            status = CERTIFICATE_EXPIRED
443
444
        # check certificate revocation
445
        all_revoked_by_parent = self.certificate_repository.get_all_revoked_by(certificate.parent_id)
446
        all_revoked_by_parent_ids = [cert.certificate_id for cert in all_revoked_by_parent]
447
448
        if id in all_revoked_by_parent_ids:
449
            status = CERTIFICATE_REVOKED
450
451
        return status
452
453 a53e5aef Jan Pašek
454 20b33bd4 Jan Pašek
    def __get_crl_endpoint(self, ca_identifier: int) -> str:
455
        """
456
        Get URL address of CRL distribution endpoint based on
457
        issuer's ID
458
459
        :param ca_identifier: ID of issuing authority
460
        :return: CRL endpoint for the given CA
461
        """
462 b3c80ccb David Friesecký
463
        Logger.debug("Function launched.")
464
465 20b33bd4 Jan Pašek
        return self.configuration.base_server_url + "/api/crl/" + str(ca_identifier)
466
467
    def __get_ocsp_endpoint(self, ca_identifier: int) -> str:
468
        """
469
        Get URL address of OCSP distribution endpoint based on
470
        issuer's ID
471
472
        :param ca_identifier: ID of issuing authority
473
        :return: OCSP endpoint for the given CA
474
        """
475 b3c80ccb David Friesecký
476
        Logger.debug("Function launched.")
477
478 20b33bd4 Jan Pašek
        return self.configuration.base_server_url + "/api/ocsp/" + str(ca_identifier)
479
480 04805a41 Stanislav Král
    def generate_pkcs_identity(self, certificate: Certificate, cert_key: PrivateKey, identity_name: str, identity_passphrase: str):
481 1fa20e93 Stanislav Král
        """
482
        Generates a PKCS identity of the certificate given by the specified ID while using the private key passed.
483
        A name of the identity to be used and certificate's passphrase have to be specified as well as the passphrase
484
        of certificate's private key (if encrypted).
485 04805a41 Stanislav Král
        :param certificate: certificate to be put into the PKCS identity store
486 1fa20e93 Stanislav Král
        :param cert_key: key used to sign the given certificate
487
        :param identity_name: name to be given to the identity to be created
488
        :param identity_passphrase: passphrase to be used to encrypt the identity
489
        :return: byte array containing the generated identity (PKCS12 store)
490
        """
491
        Logger.debug("Function launched.")
492
493
        # get the chain of trust of the certificate whose identity should be generated and exclude the certificate
494
        # whose chain of trust we are querying
495 04805a41 Stanislav Král
        cot_pem_list = [cert.pem_data for cert in self.get_chain_of_trust(certificate.certificate_id, exclude_root=False)[1:]]
496 1fa20e93 Stanislav Král
497
        return self.cryptography_service.generate_pkcs_identity(certificate.pem_data, cert_key.private_key,
498
                                                                identity_name,
499
                                                                identity_passphrase, cot_pem_list, cert_key.password)
500
501 20b33bd4 Jan Pašek
502
class RevocationReasonInvalidException(Exception):
503
    """
504
    Exception that denotes that the caller was trying to revoke
505
    a certificate using an invalid revocation reason
506
    """
507
508
    def __init__(self, reason):
509
        self.reason = reason
510
511
    def __str__(self):
512
        return f"Revocation reason '{self.reason}' is not valid."
513
514
515
class CertificateStatusInvalidException(Exception):
516
    """
517
    Exception that denotes that the caller was trying to set
518
    a certificate to an invalid state
519
    """
520
521
    def __init__(self, status):
522
        self.status = status
523
524
    def __str__(self):
525
        return f"Certificate status '{self.status}' is not valid."
526 9c704fb1 Jan Pašek
527
528 9e6f791a Jan Pašek
class CertificateAlreadyRevokedException(Exception):
529
    """
530
    Exception that denotes that the caller was trying to revoke
531
    a certificate that is already revoked
532
    """
533
534
    def __init__(self, id):
535
        self.id = id
536
537
    def __str__(self):
538
        return f"Certificate id '{self.id}' is already revoked."