tiny-ca 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,702 @@
1
+ import asyncio
2
+ from datetime import UTC, datetime
3
+ from logging import Logger
4
+ import os
5
+ from pathlib import Path
6
+
7
+ from cryptography import x509
8
+
9
+ from tiny_ca.ca_factory import CertificateFactory
10
+ from tiny_ca.const import CertType
11
+ from tiny_ca.db.async_db_manager import AsyncDBHandler
12
+ from tiny_ca.db.base_db import BaseDB
13
+ from tiny_ca.db.const import CertificateStatus
14
+ from tiny_ca.exc import (
15
+ CertNotFound,
16
+ DBNotInitedError,
17
+ NotUniqueCertOwner,
18
+ ValidationCertError,
19
+ )
20
+ from tiny_ca.models.certtificate import CAConfig, ClientConfig
21
+ from tiny_ca.settings import DEFAULT_LOGGER
22
+ from tiny_ca.storage import BaseStorage, LocalStorage
23
+ from tiny_ca.storage.async_local_storage import AsyncLocalStorage
24
+
25
+
26
+ class AsyncCertLifecycleManager:
27
+ """
28
+ Orchestrates the full lifecycle of X.509 certificates.
29
+
30
+ ``CertLifecycleManager`` is the facade that application code interacts with.
31
+ It coordinates four collaborators through dependency injection:
32
+
33
+ - ``CertificateFactory`` — cryptographic generation of certificates and CRLs.
34
+ - ``BaseStorage`` — persistent storage of PEM/key/CSR/CRL files.
35
+ - ``BaseDB`` — registration, lookup, revocation, and rotation
36
+ records in a relational database.
37
+ - ``Logger`` — structured operational logging.
38
+
39
+ All three external collaborators are optional at construction time, but
40
+ specific operations will raise ``ValueError`` or ``DBNotInitedError`` if
41
+ a required collaborator is absent when that operation is invoked.
42
+
43
+ Parameters
44
+ ----------
45
+ storage : BaseStorage
46
+ File storage backend. Defaults to ``LocalStorage()`` which writes to
47
+ ``./certs`` relative to the working directory.
48
+ factory : CertificateFactory | None
49
+ Cryptographic factory. Must be set before calling
50
+ ``issue_certificate``, ``generate_crl``, or ``verify_certificate``.
51
+ Can be set after construction via the ``factory`` property setter.
52
+ db_handler : BaseDB | None
53
+ Database adapter. When ``None``, all operations that require
54
+ persistence (status lookup, revocation, rotation) will raise
55
+ ``DBNotInitedError``.
56
+ logger : Logger | None
57
+ Logger for operational messages. Falls back to ``DEFAULT_LOGGER``.
58
+
59
+ Raises
60
+ ------
61
+ TypeError
62
+ If *storage* is not a ``BaseStorage`` instance.
63
+ TypeError
64
+ If *db_handler* is provided but is not a ``BaseDB`` instance.
65
+ """
66
+
67
+ """
68
+ Orchestrates the full lifecycle of X.509 certificates.
69
+
70
+ ``CertLifecycleManager`` is the facade that application code interacts with.
71
+ It coordinates four collaborators through dependency injection:
72
+
73
+ - ``CertificateFactory`` — cryptographic generation of certificates and CRLs.
74
+ - ``BaseStorage`` — persistent storage of PEM/key/CSR/CRL files.
75
+ - ``BaseDB`` — registration, lookup, revocation, and rotation
76
+ records in a relational database.
77
+ - ``Logger`` — structured operational logging.
78
+
79
+ All three external collaborators are optional at construction time, but
80
+ specific operations will raise ``ValueError`` or ``DBNotInitedError`` if
81
+ a required collaborator is absent when that operation is invoked.
82
+
83
+ Parameters
84
+ ----------
85
+ storage : BaseStorage
86
+ File storage backend. Defaults to ``LocalStorage()`` which writes to
87
+ ``./certs`` relative to the working directory.
88
+ factory : CertificateFactory | None
89
+ Cryptographic factory. Must be set before calling
90
+ ``issue_certificate``, ``generate_crl``, or ``verify_certificate``.
91
+ Can be set after construction via the ``factory`` property setter.
92
+ db_handler : BaseDB | None
93
+ Database adapter. When ``None``, all operations that require
94
+ persistence (status lookup, revocation, rotation) will raise
95
+ ``DBNotInitedError``.
96
+ logger : Logger | None
97
+ Logger for operational messages. Falls back to ``DEFAULT_LOGGER``.
98
+
99
+ Raises
100
+ ------
101
+ TypeError
102
+ If *storage* is not a ``BaseStorage`` instance.
103
+ TypeError
104
+ If *db_handler* is provided but is not a ``BaseDB`` instance.
105
+ """
106
+
107
+ def __init__(
108
+ self,
109
+ storage: AsyncLocalStorage | None = None,
110
+ factory: CertificateFactory | None = None,
111
+ db_handler: AsyncDBHandler | None = None,
112
+ logger: Logger | None = None,
113
+ ) -> None:
114
+ self._storage = storage or AsyncLocalStorage()
115
+ self._db = db_handler
116
+ self._factory = factory
117
+ self._logger = logger or DEFAULT_LOGGER
118
+
119
+ # ------------------------------------------------------------------
120
+ # factory property
121
+ # ------------------------------------------------------------------
122
+
123
+ @property
124
+ def factory(self) -> CertificateFactory | None:
125
+ """
126
+ The active ``CertificateFactory`` used for certificate issuance.
127
+
128
+ Returns
129
+ -------
130
+ CertificateFactory | None
131
+ The current factory, or ``None`` if not yet initialised.
132
+ """
133
+ return self._factory
134
+
135
+ @factory.setter
136
+ def factory(self, value: CertificateFactory) -> None:
137
+ """
138
+ Replace the active ``CertificateFactory``.
139
+
140
+ Useful for rotating to a new CA without recreating the entire manager.
141
+
142
+ Parameters
143
+ ----------
144
+ value : CertificateFactory
145
+ New factory instance to use for subsequent issuance operations.
146
+
147
+ Raises
148
+ ------
149
+ TypeError
150
+ If *value* is not a ``CertificateFactory`` instance.
151
+ """
152
+ if not isinstance(value, CertificateFactory):
153
+ raise TypeError(
154
+ f"factory must be a CertificateFactory instance, got {type(value)}"
155
+ )
156
+ self._logger.debug("CertificateFactory replaced: %s", value)
157
+ self._factory = value
158
+
159
+ # ------------------------------------------------------------------
160
+ # CA bootstrap
161
+ # ------------------------------------------------------------------
162
+
163
+ async def create_self_signed_ca(
164
+ self,
165
+ config: CAConfig,
166
+ cert_path: str | None = None,
167
+ uuid_str: str | None = None,
168
+ is_overwrite: bool = False,
169
+ ) -> tuple[Path, Path]:
170
+ """
171
+ Generate a self-signed root CA certificate and persist both artefacts.
172
+
173
+ Delegates cryptographic generation to
174
+ ``CertificateFactory.build_self_signed_ca`` and then saves the
175
+ certificate and private key through the configured ``BaseStorage``.
176
+ When a ``db_handler`` is present the certificate is also registered in
177
+ the database.
178
+
179
+ Parameters
180
+ ----------
181
+ config : CAConfig
182
+ Pydantic model carrying ``common_name``, ``organization``,
183
+ ``country``, ``key_size``, and ``days_valid``.
184
+ cert_path : str | None
185
+ Sub-directory under the storage base folder. ``None`` places the
186
+ files directly in the base folder.
187
+ uuid_str : str | None
188
+ Explicit UUID for the storage sub-directory. ``None`` causes the
189
+ storage layer to generate one automatically.
190
+ is_overwrite : bool
191
+ When ``True``, any existing certificate with the same CN is revoked
192
+ and its files are deleted before the new one is saved. When
193
+ ``False`` (default), ``NotUniqueCertOwner`` is raised if a conflict
194
+ exists.
195
+
196
+ Returns
197
+ -------
198
+ tuple[str, str]
199
+ ``(path_to_cert_pem, path_to_key_pem)`` as returned by the storage
200
+ layer.
201
+
202
+ Examples
203
+ --------
204
+ >>> cert_path, key_path = await mgr.create_self_signed_ca(ca_config)
205
+ """
206
+ self._logger.info(
207
+ "Creating self-signed CA: CN=%s, overwrite=%s",
208
+ config.common_name,
209
+ is_overwrite,
210
+ )
211
+
212
+ certificate, private_key = await asyncio.to_thread(
213
+ CertificateFactory.build_self_signed_ca,
214
+ **config.model_dump(),
215
+ logger=self._logger,
216
+ )
217
+
218
+ path_to_cert, uuid_str = await self._storage.save_certificate(
219
+ cert=certificate,
220
+ cert_path=cert_path,
221
+ file_name="ca",
222
+ uuid_str=uuid_str,
223
+ is_overwrite=is_overwrite,
224
+ )
225
+ path_to_key, uuid_str = await self._storage.save_certificate(
226
+ cert=private_key,
227
+ cert_path=cert_path,
228
+ file_name="ca",
229
+ uuid_str=uuid_str,
230
+ is_overwrite=is_overwrite,
231
+ )
232
+
233
+ if self._db:
234
+ await self._persist_cert_to_db(
235
+ common_name=config.common_name,
236
+ uuid_str=uuid_str,
237
+ certificate=certificate,
238
+ cert_type=CertType.CA,
239
+ cert_path=cert_path,
240
+ is_overwrite=is_overwrite,
241
+ )
242
+
243
+ self._logger.info("CA saved: cert=%s, key=%s", path_to_cert, path_to_key)
244
+ return path_to_cert, path_to_key
245
+
246
+ # ------------------------------------------------------------------
247
+ # Certificate issuance
248
+ # ------------------------------------------------------------------
249
+
250
+ async def issue_certificate(
251
+ self,
252
+ config: ClientConfig,
253
+ cert_path: str | None = None,
254
+ uuid_str: str | None = None,
255
+ is_overwrite: bool = False,
256
+ ) -> tuple[x509.Certificate, object, x509.CertificateSigningRequest]:
257
+ """
258
+ Issue a new end-entity certificate and persist all three artefacts.
259
+
260
+ The method generates the certificate, private key, and CSR via the
261
+ configured ``CertificateFactory``, then saves each file through
262
+ ``BaseStorage``. If a ``db_handler`` is configured the certificate
263
+ metadata is also written to the database.
264
+
265
+ Artefacts written to storage
266
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
267
+ - ``<file_name>.pem`` — the signed certificate.
268
+ - ``<file_name>.key`` — the private key (unencrypted by default).
269
+ - ``<file_name>.csr`` — the certificate signing request (for audit).
270
+
271
+ Parameters
272
+ ----------
273
+ config : ClientConfig
274
+ Pydantic model containing all certificate parameters: ``common_name``,
275
+ ``serial_type``, ``key_size``, ``days_valid``, ``email``,
276
+ ``is_server_cert``, ``is_client_cert``, ``san_dns``, ``san_ip``,
277
+ and an optional ``name`` for the output file basename.
278
+ cert_path : str | None
279
+ Sub-directory under the storage base folder.
280
+ uuid_str : str | None
281
+ Explicit UUID; ``None`` auto-generates one.
282
+ is_overwrite : bool
283
+ Allow replacing an existing certificate with the same CN.
284
+
285
+ Returns
286
+ -------
287
+ tuple[x509.Certificate, rsa.RSAPrivateKey, x509.CertificateSigningRequest]
288
+ ``(certificate, private_key, csr)`` in-memory objects.
289
+
290
+ Raises
291
+ ------
292
+ ValueError
293
+ If ``self.factory`` has not been initialised.
294
+ NotUniqueCertOwner
295
+ If *is_overwrite* is ``False`` and a certificate with the same CN
296
+ already exists in the database.
297
+ """
298
+
299
+ self._require_factory()
300
+
301
+ file_name = await self._derive_file_name(config)
302
+ self._logger.info(
303
+ "Issuing certificate: CN=%s, file=%s, overwrite=%s",
304
+ config.common_name,
305
+ file_name,
306
+ is_overwrite,
307
+ )
308
+
309
+ certificate, private_key, csr = await asyncio.to_thread(
310
+ self._factory.issue_certificate, # type: ignore[union-attr]
311
+ **config.model_dump(exclude={"name"}),
312
+ )
313
+
314
+ path_to_cert, uuid_str = await self._storage.save_certificate(
315
+ cert=certificate,
316
+ cert_path=cert_path,
317
+ file_name=file_name,
318
+ uuid_str=uuid_str,
319
+ is_overwrite=is_overwrite,
320
+ )
321
+ path_to_key, uuid_str = await self._storage.save_certificate(
322
+ cert=private_key,
323
+ cert_path=cert_path,
324
+ file_name=file_name,
325
+ uuid_str=uuid_str,
326
+ is_overwrite=is_overwrite,
327
+ )
328
+ path_to_csr, uuid_str = await self._storage.save_certificate(
329
+ cert=csr,
330
+ cert_path=cert_path,
331
+ file_name=file_name,
332
+ uuid_str=uuid_str,
333
+ is_overwrite=is_overwrite,
334
+ )
335
+
336
+ if self._db:
337
+ await self._persist_cert_to_db(
338
+ common_name=config.common_name,
339
+ uuid_str=uuid_str,
340
+ certificate=certificate,
341
+ cert_type=config.serial_type,
342
+ cert_path=cert_path,
343
+ is_overwrite=is_overwrite,
344
+ )
345
+
346
+ self._logger.info(
347
+ "Certificate artefacts saved: cert=%s, key=%s, csr=%s",
348
+ path_to_cert,
349
+ path_to_key,
350
+ path_to_csr,
351
+ )
352
+ return certificate, private_key, csr
353
+
354
+ # ------------------------------------------------------------------
355
+ # Revocation
356
+ # ------------------------------------------------------------------
357
+
358
+ async def revoke_certificate(
359
+ self,
360
+ serial: int,
361
+ reason: x509.ReasonFlags,
362
+ ) -> bool:
363
+ """
364
+ Revoke the certificate identified by *serial*.
365
+
366
+ Delegates to ``BaseDB.revoke_certificate``. The record is updated
367
+ in-place (status → REVOKED, revocation date and reason stored); no
368
+ file is deleted. Call ``generate_crl`` afterwards to publish the
369
+ updated revocation list.
370
+
371
+ Parameters
372
+ ----------
373
+ serial : int
374
+ Integer serial number of the certificate to revoke.
375
+ reason : x509.ReasonFlags
376
+ RFC 5280 revocation reason code (e.g.
377
+ ``x509.ReasonFlags.key_compromise``).
378
+
379
+ Returns
380
+ -------
381
+ bool
382
+ ``True`` if the revocation was recorded successfully;
383
+ ``False`` if the operation failed (see logs for details).
384
+
385
+ Raises
386
+ ------
387
+ DBNotInitedError
388
+ If no ``db_handler`` was provided at construction time.
389
+ """
390
+
391
+ self._require_db()
392
+ self._logger.info("Revoking certificate: serial=%d, reason=%s", serial, reason)
393
+
394
+ is_success, status = await self._db.revoke_certificate( # type: ignore[union-attr]
395
+ serial_number=serial, reason=reason
396
+ )
397
+ if not is_success:
398
+ self._logger.warning(
399
+ "Failed to revoke certificate serial=%d: %s", serial, status
400
+ )
401
+ else:
402
+ self._logger.info("Certificate serial=%d revoked successfully", serial)
403
+
404
+ return is_success
405
+
406
+ # ------------------------------------------------------------------
407
+ # CRL generation
408
+ # ------------------------------------------------------------------
409
+
410
+ async def generate_crl(self, days_valid: int = 1) -> x509.CertificateRevocationList:
411
+ """
412
+ Build, sign, and persist a fresh Certificate Revocation List.
413
+
414
+ Retrieves all currently-revoked certificates from the database,
415
+ passes them to ``CertificateFactory.build_crl``, and writes the
416
+ resulting CRL to storage as ``crl.pem`` (overwriting any previous
417
+ version — CRLs are always regenerated in-place).
418
+
419
+ Parameters
420
+ ----------
421
+ days_valid : int
422
+ Number of days until the CRL's ``nextUpdate`` field. Relying
423
+ parties will reject the CRL after this point. Default: ``1``.
424
+
425
+ Returns
426
+ -------
427
+ x509.CertificateRevocationList
428
+ The signed CRL object (also persisted to storage).
429
+
430
+ Raises
431
+ ------
432
+ DBNotInitedError
433
+ If no ``db_handler`` was provided.
434
+ ValueError
435
+ If ``self.factory`` has not been initialised.
436
+ """
437
+
438
+ self._require_db()
439
+ self._require_factory()
440
+
441
+ self._logger.info("Generating CRL: days_valid=%d", days_valid)
442
+
443
+ revoked_rows = self._db.get_revoked_certificates()
444
+ print(type(revoked_rows))
445
+ crl = await self.factory.abuild_crl(revoked_rows)
446
+ # revoked_rows = [
447
+ # row
448
+ # async for row in self._db.get_revoked_certificates() # type: ignore[union-attr]
449
+ # ]
450
+
451
+ # crl = await asyncio.to_thread(
452
+ # self._factory.build_crl, # type: ignore[union-attr]
453
+ # revoked_certs=iter(revoked_rows),
454
+ # days_valid=days_valid,
455
+ # )
456
+ path, _ = await self._storage.save_certificate(
457
+ cert=crl, file_name="crl", is_overwrite=True, is_add_uuid=False
458
+ )
459
+ self._logger.info("CRL saved to %s", path)
460
+ return crl
461
+
462
+ # ------------------------------------------------------------------
463
+ # Status and verification
464
+ # ------------------------------------------------------------------
465
+
466
+ async def get_certificate_status(self, serial: int) -> CertificateStatus:
467
+ """
468
+ Determine the current status of the certificate identified by *serial*.
469
+
470
+ Looks up the certificate record in the database and evaluates its
471
+ state in the following priority order:
472
+ 1. Not found → ``UNKNOWN``
473
+ 2. Revocation date is set → ``REVOKED``
474
+ 3. ``not_valid_after`` is in the past → ``EXPIRED``
475
+ 4. Otherwise → ``VALID``
476
+
477
+ Parameters
478
+ ----------
479
+ serial : int
480
+ Integer serial number of the certificate to check.
481
+
482
+ Returns
483
+ -------
484
+ CertificateStatus
485
+ One of ``VALID``, ``REVOKED``, ``EXPIRED``, or ``UNKNOWN``.
486
+
487
+ Raises
488
+ ------
489
+ DBNotInitedError
490
+ If no ``db_handler`` was provided.
491
+ """
492
+
493
+ self._require_db()
494
+
495
+ cert = await self._db.get_by_serial(serial=serial) # type: ignore[union-attr]
496
+ if not cert:
497
+ return CertificateStatus.UNKNOWN
498
+ if cert.revocation_date:
499
+ return CertificateStatus.REVOKED
500
+ now = datetime.now(UTC)
501
+ if cert.not_valid_after < now:
502
+ return CertificateStatus.EXPIRED
503
+ return CertificateStatus.VALID
504
+
505
+ async def verify_certificate(self, cert: x509.Certificate) -> bool:
506
+ """
507
+ Perform a full verification of *cert*: chain, signature, and revocation.
508
+
509
+ Combines cryptographic validation (via ``CertificateFactory.validate_cert``)
510
+ with a database revocation check (via ``get_certificate_status``).
511
+
512
+ Validation steps
513
+ ~~~~~~~~~~~~~~~~
514
+ 1. Issuer field matches the CA subject.
515
+ 2. Current UTC time is within the validity window.
516
+ 3. Cryptographic signature is valid.
517
+ 4. Certificate is not listed as revoked in the database.
518
+
519
+ Parameters
520
+ ----------
521
+ cert : x509.Certificate
522
+ The certificate object to verify.
523
+
524
+ Returns
525
+ -------
526
+ bool
527
+ ``True`` when all checks pass.
528
+
529
+ Raises
530
+ ------
531
+ ValueError
532
+ If ``self.factory`` has not been initialised.
533
+ ValidationCertError
534
+ If the certificate fails any cryptographic check or is revoked.
535
+ """
536
+
537
+ self._require_factory()
538
+
539
+ self._logger.info("Verifying certificate serial=%s", cert.serial_number)
540
+ await asyncio.to_thread(self._factory.validate_cert, cert=cert) # type: ignore[union-attr]
541
+
542
+ status = await self.get_certificate_status(cert.serial_number)
543
+ if status == CertificateStatus.REVOKED:
544
+ self._logger.warning(
545
+ "Verification failed: certificate serial=%s is revoked",
546
+ cert.serial_number,
547
+ )
548
+ raise ValidationCertError("Certificate is revoked")
549
+
550
+ self._logger.info(
551
+ "Certificate serial=%s verified successfully", cert.serial_number
552
+ )
553
+ return True
554
+
555
+ # ------------------------------------------------------------------
556
+ # Rotation
557
+ # ------------------------------------------------------------------
558
+
559
+ async def rotate_certificate(
560
+ self,
561
+ serial: int,
562
+ config: ClientConfig,
563
+ ) -> tuple[x509.Certificate, object, x509.CertificateSigningRequest]:
564
+ """
565
+ Revoke an existing certificate and issue a replacement in a single operation.
566
+
567
+ The old certificate is revoked with reason ``superseded`` before the
568
+ new one is issued. Both operations are performed against the configured
569
+ ``db_handler``; if the revocation fails an exception is propagated and
570
+ the new certificate is not issued.
571
+
572
+ Parameters
573
+ ----------
574
+ serial : int
575
+ Serial number of the certificate to replace.
576
+ config : ClientConfig
577
+ Parameters for the replacement certificate. The CN may differ
578
+ from the original.
579
+
580
+ Returns
581
+ -------
582
+ tuple[x509.Certificate, rsa.RSAPrivateKey, x509.CertificateSigningRequest]
583
+ ``(new_certificate, new_private_key, new_csr)``.
584
+
585
+ Raises
586
+ ------
587
+ DBNotInitedError
588
+ If no ``db_handler`` was provided.
589
+ CertNotFound
590
+ If no certificate with *serial* exists in the database.
591
+ ValueError
592
+ If ``self.factory`` has not been initialised.
593
+ """
594
+
595
+ self._require_db()
596
+
597
+ self._logger.info(
598
+ "Rotating certificate: serial=%d, new_CN=%s", serial, config.common_name
599
+ )
600
+
601
+ existing = await self._db.get_by_serial(serial=serial) # type: ignore[union-attr]
602
+ if not existing:
603
+ self._logger.error("Certificate serial=%d not found for rotation", serial)
604
+ raise CertNotFound()
605
+
606
+ await self.revoke_certificate(serial=serial, reason=x509.ReasonFlags.superseded)
607
+
608
+ new_cert, new_key, new_csr = await self.issue_certificate(config)
609
+ self._logger.info(
610
+ "Rotation complete: old serial=%d replaced by serial=%s",
611
+ serial,
612
+ new_cert.serial_number,
613
+ )
614
+ return new_cert, new_key, new_csr
615
+
616
+ # ------------------------------------------------------------------
617
+ # private helpers
618
+ # ------------------------------------------------------------------
619
+
620
+ async def _persist_cert_to_db(
621
+ self,
622
+ common_name: str,
623
+ uuid_str: str,
624
+ certificate: x509.Certificate,
625
+ cert_type: CertType,
626
+ cert_path: str | None,
627
+ is_overwrite: bool,
628
+ ) -> None:
629
+ """
630
+ Register *certificate* in the database, handling CN conflicts.
631
+
632
+ If a VALID certificate with *common_name* already exists:
633
+ - *is_overwrite=True* → the existing cert is revoked (``key_compromise``)
634
+ and its storage folder is deleted before the new record is inserted.
635
+ - *is_overwrite=False* → ``NotUniqueCertOwner`` is raised immediately.
636
+
637
+ Parameters
638
+ ----------
639
+ common_name : str
640
+ The CN of the certificate being registered.
641
+ uuid_str : str
642
+ UUID of the storage folder that holds the artefact files.
643
+ certificate : x509.Certificate
644
+ The newly issued certificate object.
645
+ cert_type : CertType
646
+ Category of the certificate (CA, SERVICE, DEVICE, etc.).
647
+ cert_path : str | None
648
+ Base sub-path used by the storage layer; needed when deleting the
649
+ old artefact folder on overwrite.
650
+ is_overwrite : bool
651
+ Whether to replace an existing certificate with the same CN.
652
+
653
+ Raises
654
+ ------
655
+ NotUniqueCertOwner
656
+ If *common_name* is already in use and *is_overwrite* is ``False``.
657
+ """
658
+
659
+ existing = await self._db.get_by_name(common_name=common_name) # type: ignore[union-attr]
660
+ print("exis", existing)
661
+ if existing:
662
+ print("exiss")
663
+ if not is_overwrite:
664
+ self._logger.warning(
665
+ "Duplicate CN detected: CN=%s is already registered", common_name
666
+ )
667
+ raise NotUniqueCertOwner(common_name)
668
+
669
+ self._logger.info(
670
+ "Overwriting existing certificate: CN=%s (serial=%s)",
671
+ common_name,
672
+ existing.serial_number,
673
+ )
674
+ await self.revoke_certificate(
675
+ serial=int(existing.serial_number),
676
+ reason=x509.ReasonFlags.key_compromise,
677
+ )
678
+ await self._storage.delete_certificate_folder(
679
+ uuid_str=str(existing.uuid), cert_path=cert_path
680
+ )
681
+
682
+ await self._db.register_cert_in_db( # type: ignore[union-attr]
683
+ cert=certificate, uuid=uuid_str, key_type=cert_type
684
+ )
685
+ self._logger.debug(
686
+ "Certificate registered in DB: CN=%s, uuid=%s", common_name, uuid_str
687
+ )
688
+
689
+ @staticmethod
690
+ async def _derive_file_name(config: ClientConfig) -> str:
691
+ return config.name or config.common_name.lower().replace(os.sep, "_")
692
+
693
+ def _require_db(self) -> None:
694
+ if not self._db:
695
+ raise DBNotInitedError()
696
+
697
+ def _require_factory(self) -> None:
698
+ if not self._factory:
699
+ raise ValueError(
700
+ "factory is not initialised. "
701
+ "Set AsyncCertLifecycleManager.factory before using this operation."
702
+ )