Projekt

Obecné

Profil

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