Projekt

Obecné

Profil

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