Projekt

Obecné

Profil

Stáhnout (28 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 894cc2cd David Friesecký
        certificate = Certificate(-1, not_before_formatted, not_after_formatted, cert_pem, cert_type, parent_id,
118
                                  private_key_id, usages, subj.common_name, subj.country, subj.locality, subj.state,
119
                                  subj.organization, subj.organization_unit, subj.email_address)
120 4c19a9b1 Stanislav Král
121
        return certificate
122
123 bbcb7c89 Stanislav Král
    # TODO config parameter present in class diagram but not here (unused)
124
    def create_ca(self, subject_key: PrivateKey, subject: Subject, issuer_cert: Certificate, issuer_key: PrivateKey,
125 ca3ac7c0 Stanislav Král
                  extensions: str = "", days: int = 30, usages=None):
126 a6727aa9 Stanislav Král
        """
127
        Creates an intermediate CA certificate issued by the given parent CA.
128
        :param subject_key: Private key to be used when generating the certificate
129
        :param subject: Subject to be used put into the certificate
130
        :param issuer_cert: Issuer certificate that will sign the CSR required to create an intermediate CA
131
        :param issuer_key: PK used to generate the issuer certificate
132
        :param extensions: Extensions to be used when generating the certificate
133
        :param usages: A dictionary containing usages of the certificate to be generated (see constants.py)
134
        :param days: Number of days for which the generated cert. will be considered valid
135
        :return: An instance of Certificate class representing the generated intermediate CA cert
136
        """
137 b3c80ccb David Friesecký
138
        Logger.debug("Function launched.")
139
140 ca3ac7c0 Stanislav Král
        if usages is None:
141
            usages = {}
142
143 329216fe Stanislav Král
        # specify CA usage
144
        usages[CA_ID] = True
145
146
        # generate extension configuration lines based on the specified usages
147
        extensions = extensions + "\n" + "\n".join(usages_to_extension_lines(usages, REQUIRED_USAGE_EXTENSION_FLAGS))
148
149 20b33bd4 Jan Pašek
        # Add CRL and OCSP distribution point to certificate extensions
150
        cert_id = self.certificate_repository.get_next_id()
151 ea1229ee Jan Pašek
        extensions = extensions + "\n" + CRL_EXTENSION + " " + self.__get_crl_endpoint(issuer_cert.certificate_id)
152
        extensions = extensions + "\n" + OCSP_EXTENSION + " " + self.__get_ocsp_endpoint(issuer_cert.certificate_id)
153 20b33bd4 Jan Pašek
154 bbcb7c89 Stanislav Král
        # TODO implement AIA URI via extensions
155
        cert_pem = self.cryptography_service.create_crt(subject, subject_key.private_key, issuer_cert.pem_data,
156
                                                        issuer_key.private_key,
157
                                                        subject_key_pass=subject_key.password,
158
                                                        issuer_key_pass=issuer_key.password, extensions=extensions,
159 87c56935 Stanislav Král
                                                        days=days,
160
                                                        sn=cert_id)
161 bbcb7c89 Stanislav Král
162 4c19a9b1 Stanislav Král
        # wrap into Certificate class
163 894cc2cd David Friesecký
        certificate = self.__create_wrapper(cert_pem, subject_key.private_key_id, usages,
164
                                            issuer_cert.certificate_id, INTERMEDIATE_CA_ID)
165 bbcb7c89 Stanislav Král
166
        # store the wrapper into the repository
167
        created_id = self.certificate_repository.create(certificate)
168
169
        # assign the generated ID to the inserted certificate
170
        certificate.certificate_id = created_id
171
172
        return certificate
173
174 4c19a9b1 Stanislav Král
    def create_end_cert(self, subject_key: PrivateKey, subject: Subject, issuer_cert: Certificate,
175
                        issuer_key: PrivateKey,
176
                        extensions: str = "", days: int = 30, usages=None):
177 a6727aa9 Stanislav Král
        """
178
        Creates an end certificate issued by the given parent CA.
179
        :param subject_key: Private key to be used when generating the certificate
180
        :param subject: Subject to be used put into the certificate
181
        :param issuer_cert: Issuer certificate that will sign the CSR required to create an intermediate CA
182
        :param issuer_key: PK used to generate the issuer certificate
183
        :param extensions: Extensions to be used when generating the certificate
184
        :param usages: A dictionary containing usages of the certificate to be generated (see constants.py)
185
        :param days: Number of days for which the generated cert. will be considered valid
186
        :return: An instance of Certificate class representing the generated cert
187
        """
188 b3c80ccb David Friesecký
189
        Logger.debug("Function launched.")
190
191 4c19a9b1 Stanislav Král
        if usages is None:
192
            usages = {}
193
194 87c56935 Stanislav Král
        # get the next certificate ID in order to be able to specify the serial number
195
        cert_id = self.certificate_repository.get_next_id()
196
197 329216fe Stanislav Král
        # generate extension configuration lines based on the specified usages
198
        extensions = extensions + "\n" + "\n".join(usages_to_extension_lines(usages, REQUIRED_USAGE_EXTENSION_FLAGS))
199
200 ea1229ee Jan Pašek
        # Add CRL and OCSP distribution point to certificate extensions
201
        extensions = extensions + "\n" + CRL_EXTENSION + " " + self.__get_crl_endpoint(issuer_cert.certificate_id)
202
        extensions = extensions + "\n" + OCSP_EXTENSION + " " + self.__get_ocsp_endpoint(issuer_cert.certificate_id)
203
204 4c19a9b1 Stanislav Král
        # generate a new certificate
205
        cert_pem = self.cryptography_service.create_crt(subject, subject_key.private_key, issuer_cert.pem_data,
206
                                                        issuer_key.private_key,
207
                                                        subject_key_pass=subject_key.password,
208
                                                        issuer_key_pass=issuer_key.password, extensions=extensions,
209 87c56935 Stanislav Král
                                                        days=days,
210
                                                        sn=cert_id
211
                                                        )
212 4c19a9b1 Stanislav Král
213
        # wrap the generated certificate using Certificate class
214 a6727aa9 Stanislav Král
        certificate = self.__create_wrapper(cert_pem, subject_key.private_key_id, usages,
215 4c19a9b1 Stanislav Král
                                            issuer_cert.certificate_id, CERTIFICATE_ID)
216
217
        created_id = self.certificate_repository.create(certificate)
218
219
        certificate.certificate_id = created_id
220
221
        return certificate
222
223 10fab051 Stanislav Král
    def get_certificate(self, unique_id: int) -> Certificate:
224 a6727aa9 Stanislav Král
        """
225
        Tries to fetch a certificate from the certificate repository using a given id.
226
        :param unique_id: ID of the certificate to be fetched
227
        :return: Instance of the Certificate class containing a certificate with the given id or `None` if such
228
        certificate is not found
229
        """
230 b3c80ccb David Friesecký
231
        Logger.debug("Function launched.")
232
233 10fab051 Stanislav Král
        return self.certificate_repository.read(unique_id)
234 2a90f4fd Stanislav Král
235 ef65f488 Stanislav Král
    def get_certificates(self, cert_type=None) -> List[Certificate]:
236 a6727aa9 Stanislav Král
        """
237
        Tries to fetch a list of all certificates from the certificate repository. Using the `cert_type` parameter only
238
        certificates of the given type can be returned.
239
        :param cert_type: Type of certificates to be returned
240
        :return: List of instances of the Certificate class representing all certificates present in the certificate
241
        repository. An empty list is returned when no certificates are found.
242
        """
243 b3c80ccb David Friesecký
244
        Logger.debug("Function launched.")
245
246 2a90f4fd Stanislav Král
        return self.certificate_repository.read_all(cert_type)
247 ef65f488 Stanislav Král
248 67723931 Captain_Trojan
    def get_certificates_filter(self, target_types, target_usages, target_cn_substring, page, per_page):
249
        """
250
        Tries to fetch a filtered list of all certificates from the certificate repository.
251
        :param target_types: certificate types (filter)
252
        :param target_usages: certificate usages (filter)
253
        :param target_cn_substring: certificate CN substring (filter)
254
        :param page: target page
255
        :param per_page: target page size
256
        :return: List of instances of the Certificate class representing filtered certificates
257
                    present in the certificate repository. An empty list is returned when no certificates are found.
258
        """
259
        return self.certificate_repository.read_all_filter(target_types, target_usages, target_cn_substring,
260
                                                           page, per_page)
261
262 ef65f488 Stanislav Král
    def get_chain_of_trust(self, from_id: int, to_id: int = -1, exclude_root=True) -> List[Certificate]:
263 4e70d22a Stanislav Král
        """
264
        Traverses the certificate hierarchy tree upwards till a certificate with the `to_id` ID is found or till a
265
        root CA certificate is found. Root certificates are excluded from the chain by default.
266
        :param from_id: ID of the first certificate to be included in the chain of trust
267
        :param to_id: ID of the last certificate to be included in the chain of trust
268
        :param exclude_root: a flag indicating whether root CA certificate should be excluded
269
        :return: a list of certificates representing the chain of trust starting with the certificate given by `from_id`
270
        ID
271
        """
272 b3c80ccb David Friesecký
273
        Logger.debug("Function launched.")
274
275 4e70d22a Stanislav Král
        # read the first certificate of the chain
276 ef65f488 Stanislav Král
        start_cert = self.certificate_repository.read(from_id)
277
278 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
279 ef65f488 Stanislav Král
        if start_cert is None or (start_cert.type_id == ROOT_CA_ID and exclude_root):
280
            return []
281
282
        current_cert = start_cert
283
        chain_of_trust = [current_cert]
284
285
        # TODO could possibly be simplified
286
        if start_cert.type_id == ROOT_CA_ID:
287 4e70d22a Stanislav Král
            # the first cert found is a root ca
288 ef65f488 Stanislav Král
            return chain_of_trust
289
290
        while True:
291
            parent_cert = self.certificate_repository.read(current_cert.parent_id)
292
293 4e70d22a Stanislav Král
            # check whether parent certificate exists
294
            if parent_cert is None:
295
                break
296
297
            # check whether the found certificate is a root certificate
298
            if parent_cert.type_id == ROOT_CA_ID:
299 ef65f488 Stanislav Král
                if not exclude_root:
300 4e70d22a Stanislav Král
                    # append the found root cert only if root certificates should not be excluded from the CoT
301 ef65f488 Stanislav Král
                    chain_of_trust.append(parent_cert)
302
                break
303
304 4e70d22a Stanislav Král
            # append the certificate
305 ef65f488 Stanislav Král
            chain_of_trust.append(parent_cert)
306
307 4e70d22a Stanislav Král
            # stop iterating over certificates if the id of the found certificate matches `to_id` method parameter
308 ef65f488 Stanislav Král
            if parent_cert.certificate_id == to_id:
309
                break
310
311
            current_cert = parent_cert
312
313
        return chain_of_trust
314 3d639744 Stanislav Král
315 5f8a2c07 Captain_Trojan
    def delete_certificate(self, unique_id):
316 3d639744 Stanislav Král
        """
317 5f8a2c07 Captain_Trojan
        Deletes a certificate. Raises an Exception should any unexpected behavior occur.
318 3d639744 Stanislav Král
319
        :param unique_id: ID of specific certificate
320
        """
321 5f8a2c07 Captain_Trojan
322 b3c80ccb David Friesecký
        Logger.debug("Function launched.")
323
324 5f8a2c07 Captain_Trojan
        to_delete = self.certificate_repository.get_all_descendants_of(unique_id)
325
        if to_delete is None:
326 b3c80ccb David Friesecký
            Logger.error(f"No such certificate found 'ID = {unique_id}'.")
327 5f8a2c07 Captain_Trojan
            raise CertificateNotFoundException(unique_id)
328
329
        for cert in to_delete:
330
            try:
331
                self.set_certificate_revocation_status(cert.certificate_id, STATUS_REVOKED)
332
            except CertificateAlreadyRevokedException:
333 b3c80ccb David Friesecký
                Logger.info(f"Certificate already revoked 'ID = {unique_id}'.")
334 5f8a2c07 Captain_Trojan
                # TODO log as an info/debug, not warning or above <-- perfectly legal
335 02954c9d Jan Pašek
                pass
336 5f8a2c07 Captain_Trojan
337 b3c80ccb David Friesecký
            if not self.certificate_repository.delete(cert.certificate_id):
338
                Logger.error(f"The certificate has not been deleted 'ID = {cert.certificate_id}'.")
339
340 c839facb Captain_Trojan
    def get_certificates_issued_by(self, issuer_id):
341 485913d0 Captain_Trojan
        """
342
        Returns a list of all children of a certificate identified by an unique_id.
343
        Raises a DatabaseException should any unexpected behavior occur.
344 c839facb Captain_Trojan
        :param issuer_id: target certificate ID
345 485913d0 Captain_Trojan
        :return: children of unique_id
346
        """
347 b3c80ccb David Friesecký
348
        Logger.debug("Function launched.")
349
350 485913d0 Captain_Trojan
        try:
351 c839facb Captain_Trojan
            if self.certificate_repository.read(issuer_id) is None:
352
                Logger.error(f"No such certificate found 'ID = {issuer_id}'.")
353
                raise CertificateNotFoundException(issuer_id)
354 485913d0 Captain_Trojan
        except DatabaseException:
355 c839facb Captain_Trojan
            Logger.error(f"No such certificate found 'ID = {issuer_id}'.")
356
            raise CertificateNotFoundException(issuer_id)
357
358
        return self.certificate_repository.get_all_issued_by(issuer_id)
359 485913d0 Captain_Trojan
360 c839facb Captain_Trojan
    def get_certificates_issued_by_filter(self, issuer_id, target_types, target_usages, target_cn_substring, page,
361
                                          per_page):
362
        """
363
        Returns a list of all children of a certificate identified by an unique_id.
364
        Filters the results according to target types, usages, cn substring and pagination.
365
        Raises a DatabaseException should any unexpected behavior occur.
366
        :param issuer_id: target certificate ID
367
        :param target_types: filter of types
368
        :param target_usages: filter of usages
369
        :param target_cn_substring: CN substring
370
        :param page: target page or None
371
        :param per_page: certs per page or None
372
        :return: list of certificates (a page if specified)
373
        """
374 485913d0 Captain_Trojan
375 c839facb Captain_Trojan
        Logger.debug("Function launched.")
376
377
        try:
378
            if self.certificate_repository.read(issuer_id) is None:
379
                Logger.error(f"No such certificate found 'ID = {issuer_id}'.")
380
                raise CertificateNotFoundException(issuer_id)
381
        except DatabaseException:
382
            Logger.error(f"No such certificate found 'ID = {issuer_id}'.")
383
            raise CertificateNotFoundException(issuer_id)
384 485913d0 Captain_Trojan
385 67723931 Captain_Trojan
        return self.certificate_repository.get_all_issued_by_filter(issuer_id, target_types, target_usages,
386
                                                                    target_cn_substring, page, per_page)
387 485913d0 Captain_Trojan
388 20b33bd4 Jan Pašek
    def set_certificate_revocation_status(self, id, status, reason="unspecified"):
389
        """
390
        Set certificate status to 'valid' or 'revoked'.
391
        If the new status is revoked a reason can be provided -> default is unspecified
392
        :param reason: reason for revocation
393
        :param id: identifier of the certificate whose status is to be changed
394
        :param status: new status of the certificate
395 94f8d5cf Jan Pašek
        :raises CertificateStatusInvalidException: if status is not valid
396
        :raises RevocationReasonInvalidException: if reason is not valid
397
        :raises CertificateNotFoundException: if certificate with given id cannot be found
398
        :raises CertificateCannotBeSetToValid: if certificate was already revoked and not on hold,
399
                it cannot be set revalidated
400
        :raises CertificateAlreadyRevokedException: if caller tries to revoke a certificate that is already revoked
401
        :raises UnknownException: if the database is corrupted
402 20b33bd4 Jan Pašek
        """
403 b3c80ccb David Friesecký
404
        Logger.debug("Function launched.")
405
406 20b33bd4 Jan Pašek
        if status not in CERTIFICATE_STATES:
407 b3c80ccb David Friesecký
            Logger.error(f"Wrong parameter, invalid status '{status}'.")
408 20b33bd4 Jan Pašek
            raise CertificateStatusInvalidException(status)
409
        if reason not in CERTIFICATE_REVOCATION_REASONS:
410 b3c80ccb David Friesecký
            Logger.error(f"Wrong parameter, invalid reason '{reason}'.")
411 20b33bd4 Jan Pašek
            raise RevocationReasonInvalidException(reason)
412
413 9e6f791a Jan Pašek
        # check whether the certificate exists
414
        certificate = self.certificate_repository.read(id)
415
        if certificate is None:
416 b3c80ccb David Friesecký
            Logger.error(f"No such certificate found 'ID = {id}'.")
417 9e6f791a Jan Pašek
            raise CertificateNotFoundException(id)
418
419 9c704fb1 Jan Pašek
        updated = False
420 20b33bd4 Jan Pašek
        if status == STATUS_VALID:
421 94f8d5cf Jan Pašek
            # if the certificate is revoked but the reason is not certificateHold, it cannot be re-validated
422
            #    -> throw an exception
423
            if certificate.revocation_reason != "" and \
424
               certificate.revocation_reason != CERTIFICATE_REVOCATION_REASON_HOLD:
425
                raise CertificateCannotBeSetToValid(certificate.revocation_reason)
426 9c704fb1 Jan Pašek
            updated = self.certificate_repository.clear_certificate_revocation(id)
427 20b33bd4 Jan Pašek
        elif status == STATUS_REVOKED:
428 9e6f791a Jan Pašek
            # check if the certificate is not revoked already
429
            revoked = self.certificate_repository.get_all_revoked_by(certificate.parent_id)
430
            if certificate.certificate_id in [x.certificate_id for x in revoked]:
431 b3c80ccb David Friesecký
                Logger.error(f"Certificate already revoked 'ID = {id}'.")
432 9e6f791a Jan Pašek
                raise CertificateAlreadyRevokedException(id)
433
434 20b33bd4 Jan Pašek
            revocation_timestamp = int(time.time())
435 9c704fb1 Jan Pašek
            updated = self.certificate_repository.set_certificate_revoked(id, str(revocation_timestamp), reason)
436
437
        if not updated:
438 b3c80ccb David Friesecký
            Logger.error(f"Repository returned 'false' from clear_certificate_revocation() "
439
                         f"or set_certificate_revoked().")
440 9e6f791a Jan Pašek
            raise UnknownException("Repository returned 'false' from clear_certificate_revocation() "
441
                                   "or set_certificate_revoked().")
442 20b33bd4 Jan Pašek
443 c4ba6bb7 Jan Pašek
    def get_subject_from_certificate(self, certificate: Certificate) -> Subject:
444
        """
445
        Get Subject distinguished name from a Certificate
446
        :param certificate: certificate instance whose Subject shall be parsed
447
        :return: instance of Subject class containing resulting distinguished name
448
        """
449 b3c80ccb David Friesecký
450
        Logger.debug("Function launched.")
451
452 894cc2cd David Friesecký
        subject = Subject(certificate.common_name,
453
                          certificate.country_code,
454
                          certificate.locality,
455
                          certificate.province,
456
                          certificate.organization,
457
                          certificate.organizational_unit,
458
                          certificate.email_address)
459 c4ba6bb7 Jan Pašek
        return subject
460 d3bfacfc Stanislav Král
461
    def get_public_key_from_certificate(self, certificate: Certificate):
462
        """
463
        Extracts a public key from the given certificate
464
        :param certificate: an instance of the Certificate class containing the certificate from which a public key
465
        should be extracted.
466
        :return: a string containing the extracted public key in PEM format
467
        """
468 b3c80ccb David Friesecký
469
        Logger.debug("Function launched.")
470
471 d3bfacfc Stanislav Král
        return self.cryptography_service.extract_public_key_from_certificate(certificate.pem_data)
472 20b33bd4 Jan Pašek
473 a53e5aef Jan Pašek
    def get_certificate_state(self, id: int) -> str:
474
        """
475
        Check whether the certificate is expired, valid or revoked.
476
            - in case it's revoked and expired, revoked is returned
477
        :param id: identifier of the certificate
478
        :return: certificates state from {valid, revoked, expired}
479
        :raises CertificateNotFoundException: in case id of non-existing certificate is entered
480
        """
481 4ff15a44 Jan Pašek
        Logger.debug("Function launched.")
482 c15357a9 Jan Pašek
        status = CERTIFICATE_VALID
483
484
        # Read the selected certificate from the repository
485
        certificate = self.certificate_repository.read(id)
486
        if certificate is None:
487 1fa20e93 Stanislav Král
            Logger.error("Certificate whose details were requested does not exist.")
488 c15357a9 Jan Pašek
            raise CertificateNotFoundException(id)
489
490
        # check the expiration date using OpenSSL
491
        if not self.cryptography_service.verify_cert(certificate.pem_data):
492
            status = CERTIFICATE_EXPIRED
493
494
        # check certificate revocation
495
        all_revoked_by_parent = self.certificate_repository.get_all_revoked_by(certificate.parent_id)
496
        all_revoked_by_parent_ids = [cert.certificate_id for cert in all_revoked_by_parent]
497
498
        if id in all_revoked_by_parent_ids:
499
            status = CERTIFICATE_REVOKED
500
501
        return status
502
503 20b33bd4 Jan Pašek
    def __get_crl_endpoint(self, ca_identifier: int) -> str:
504
        """
505
        Get URL address of CRL distribution endpoint based on
506
        issuer's ID
507
508
        :param ca_identifier: ID of issuing authority
509
        :return: CRL endpoint for the given CA
510
        """
511 b3c80ccb David Friesecký
512
        Logger.debug("Function launched.")
513
514 20b33bd4 Jan Pašek
        return self.configuration.base_server_url + "/api/crl/" + str(ca_identifier)
515
516
    def __get_ocsp_endpoint(self, ca_identifier: int) -> str:
517
        """
518
        Get URL address of OCSP distribution endpoint based on
519
        issuer's ID
520
521
        :param ca_identifier: ID of issuing authority
522
        :return: OCSP endpoint for the given CA
523
        """
524 b3c80ccb David Friesecký
525
        Logger.debug("Function launched.")
526
527 20b33bd4 Jan Pašek
        return self.configuration.base_server_url + "/api/ocsp/" + str(ca_identifier)
528
529 04805a41 Stanislav Král
    def generate_pkcs_identity(self, certificate: Certificate, cert_key: PrivateKey, identity_name: str, identity_passphrase: str):
530 1fa20e93 Stanislav Král
        """
531
        Generates a PKCS identity of the certificate given by the specified ID while using the private key passed.
532
        A name of the identity to be used and certificate's passphrase have to be specified as well as the passphrase
533
        of certificate's private key (if encrypted).
534 04805a41 Stanislav Král
        :param certificate: certificate to be put into the PKCS identity store
535 1fa20e93 Stanislav Král
        :param cert_key: key used to sign the given certificate
536
        :param identity_name: name to be given to the identity to be created
537
        :param identity_passphrase: passphrase to be used to encrypt the identity
538
        :return: byte array containing the generated identity (PKCS12 store)
539
        """
540
        Logger.debug("Function launched.")
541
542
        # get the chain of trust of the certificate whose identity should be generated and exclude the certificate
543
        # whose chain of trust we are querying
544 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:]]
545 1fa20e93 Stanislav Král
546
        return self.cryptography_service.generate_pkcs_identity(certificate.pem_data, cert_key.private_key,
547
                                                                identity_name,
548
                                                                identity_passphrase, cot_pem_list, cert_key.password)
549
550 20b33bd4 Jan Pašek
551
class RevocationReasonInvalidException(Exception):
552
    """
553
    Exception that denotes that the caller was trying to revoke
554
    a certificate using an invalid revocation reason
555
    """
556
557
    def __init__(self, reason):
558
        self.reason = reason
559
560
    def __str__(self):
561
        return f"Revocation reason '{self.reason}' is not valid."
562
563
564
class CertificateStatusInvalidException(Exception):
565
    """
566
    Exception that denotes that the caller was trying to set
567
    a certificate to an invalid state
568
    """
569
570
    def __init__(self, status):
571
        self.status = status
572
573
    def __str__(self):
574
        return f"Certificate status '{self.status}' is not valid."
575 9c704fb1 Jan Pašek
576
577 9e6f791a Jan Pašek
class CertificateAlreadyRevokedException(Exception):
578
    """
579
    Exception that denotes that the caller was trying to revoke
580
    a certificate that is already revoked
581
    """
582
583
    def __init__(self, id):
584
        self.id = id
585
586
    def __str__(self):
587
        return f"Certificate id '{self.id}' is already revoked."
588 94f8d5cf Jan Pašek
589
590
class CertificateCannotBeSetToValid(Exception):
591
    """
592
    Exception that denotes that the caller was trying to
593
    set certificate to valid if the certificate was already
594
    revoked but not certificateHold.
595
    """
596
597
    def __init__(self, old_reason):
598
        self.old_state = old_reason
599
600
    def __str__(self):
601
        return "Cannot set revoked certificate back to valid when the certificate revocation reason is not " \
602
               "certificateHold. " \
603
               f"The revocation reason of the certificate is {self.old_state}"