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.
- tiny_ca/__init__.py +41 -0
- tiny_ca/ca_factory/__init__.py +15 -0
- tiny_ca/ca_factory/factory.py +658 -0
- tiny_ca/ca_factory/utils/__init__.py +10 -0
- tiny_ca/ca_factory/utils/afile_loader.py +157 -0
- tiny_ca/ca_factory/utils/file_loader.py +290 -0
- tiny_ca/ca_factory/utils/life_time.py +199 -0
- tiny_ca/ca_factory/utils/serial.py +151 -0
- tiny_ca/const.py +81 -0
- tiny_ca/db/__init__.py +14 -0
- tiny_ca/db/async_db_manager.py +358 -0
- tiny_ca/db/base_db.py +173 -0
- tiny_ca/db/const.py +92 -0
- tiny_ca/db/models.py +144 -0
- tiny_ca/db/sync_db_manager.py +415 -0
- tiny_ca/exc.py +103 -0
- tiny_ca/managers/__init__.py +3 -0
- tiny_ca/managers/async_lifecycle_manager.py +702 -0
- tiny_ca/managers/sync_lifecycle_manager.py +722 -0
- tiny_ca/models/__init__.py +3 -0
- tiny_ca/models/certtificate.py +300 -0
- tiny_ca/settings.py +9 -0
- tiny_ca/storage/__init__.py +4 -0
- tiny_ca/storage/async_local_storage.py +218 -0
- tiny_ca/storage/base_storage.py +166 -0
- tiny_ca/storage/const.py +39 -0
- tiny_ca/storage/local_storage.py +448 -0
- tiny_ca/utils/__init__.py +0 -0
- tiny_ca/utils/serial_generator.py +530 -0
- tiny_ca-0.1.0.dist-info/METADATA +430 -0
- tiny_ca-0.1.0.dist-info/RECORD +33 -0
- tiny_ca-0.1.0.dist-info/WHEEL +5 -0
- tiny_ca-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Synchronous SQLAlchemy-backed implementation of the certificate registry.
|
|
3
|
+
|
|
4
|
+
Module-level contents
|
|
5
|
+
---------------------
|
|
6
|
+
``DatabaseManager`` — owns the SQLAlchemy engine and session factory.
|
|
7
|
+
No domain logic; connection lifecycle only.
|
|
8
|
+
``SyncDBHandler`` — implements ``BaseDB``; all certificate registry
|
|
9
|
+
operations with explicit, atomic transaction management.
|
|
10
|
+
|
|
11
|
+
SOLID notes
|
|
12
|
+
-----------
|
|
13
|
+
SRP : ``DatabaseManager`` handles engine creation and session provisioning.
|
|
14
|
+
``SyncDBHandler`` handles domain operations on ``CertificateRecord`` rows.
|
|
15
|
+
Neither class knows about the other's internal details.
|
|
16
|
+
OCP : Additional query methods are added to ``SyncDBHandler`` without touching
|
|
17
|
+
``DatabaseManager`` or ``BaseDB``.
|
|
18
|
+
LSP : ``SyncDBHandler`` satisfies the full ``BaseDB`` contract and is
|
|
19
|
+
transparently substitutable in any consumer that depends on ``BaseDB``.
|
|
20
|
+
ISP : ``BaseDB`` is declared in ``db/base_db.py``; this module imports and
|
|
21
|
+
extends it without adding unrelated methods visible to callers.
|
|
22
|
+
DIP : ``CertLifecycleManager`` depends on ``BaseDB``, never on
|
|
23
|
+
``SyncDBHandler`` directly.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from collections.abc import Generator
|
|
29
|
+
import datetime
|
|
30
|
+
from logging import Logger
|
|
31
|
+
|
|
32
|
+
from cryptography import x509
|
|
33
|
+
from cryptography.hazmat._oid import NameOID
|
|
34
|
+
from cryptography.hazmat.primitives import serialization
|
|
35
|
+
from sqlalchemy import create_engine, delete, select
|
|
36
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
37
|
+
|
|
38
|
+
from tiny_ca.const import CertType
|
|
39
|
+
from tiny_ca.settings import DEFAULT_LOGGER
|
|
40
|
+
|
|
41
|
+
from .base_db import BaseDB
|
|
42
|
+
from .const import CertificateStatus, RevokeStatus
|
|
43
|
+
from .models import Base, CertificateRecord
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# SRP: engine and session-factory management
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DatabaseManager:
|
|
51
|
+
"""
|
|
52
|
+
Manages the SQLAlchemy engine and ``sessionmaker`` factory.
|
|
53
|
+
|
|
54
|
+
Responsibility: connection lifecycle *only*. This class creates the
|
|
55
|
+
engine, optionally initialises the schema, and hands out new ``Session``
|
|
56
|
+
objects on request. It performs no queries and contains no domain logic.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
db_url : str
|
|
61
|
+
SQLAlchemy database URL. Supports any SQLAlchemy-compatible backend,
|
|
62
|
+
e.g. ``"sqlite:///ca.db"`` or
|
|
63
|
+
``"postgresql+psycopg2://user:pass@host/dbname"``.
|
|
64
|
+
Default: ``"sqlite:///ca_repository.db"``.
|
|
65
|
+
create_all : bool
|
|
66
|
+
When ``True`` (default), calls ``Base.metadata.create_all`` at
|
|
67
|
+
construction so the schema is present before the first query.
|
|
68
|
+
Set to ``False`` when using Alembic or another migration tool that
|
|
69
|
+
manages the schema independently.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
db_url: str = "sqlite:///ca_repository.db",
|
|
75
|
+
create_all: bool = True,
|
|
76
|
+
) -> None:
|
|
77
|
+
self._engine = create_engine(db_url)
|
|
78
|
+
if create_all:
|
|
79
|
+
Base.metadata.create_all(self._engine)
|
|
80
|
+
self._Session: sessionmaker[Session] = sessionmaker(bind=self._engine)
|
|
81
|
+
|
|
82
|
+
def session(self) -> Session:
|
|
83
|
+
"""
|
|
84
|
+
Provision a new SQLAlchemy ``Session`` bound to the managed engine.
|
|
85
|
+
|
|
86
|
+
The caller is fully responsible for the session lifecycle: committing
|
|
87
|
+
successful transactions, rolling back on errors, and closing the
|
|
88
|
+
session in a ``finally`` block. Example::
|
|
89
|
+
|
|
90
|
+
session = db_manager.session()
|
|
91
|
+
try:
|
|
92
|
+
session.add(record)
|
|
93
|
+
session.commit()
|
|
94
|
+
except Exception:
|
|
95
|
+
session.rollback()
|
|
96
|
+
raise
|
|
97
|
+
finally:
|
|
98
|
+
session.close()
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
Session
|
|
103
|
+
A new, uncommitted SQLAlchemy ORM session.
|
|
104
|
+
"""
|
|
105
|
+
return self._Session()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# SRP: domain operations on CertificateRecord rows
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SyncDBHandler(BaseDB):
|
|
114
|
+
"""
|
|
115
|
+
Synchronous, SQLAlchemy-backed certificate registry.
|
|
116
|
+
|
|
117
|
+
Implements the full ``BaseDB`` contract with explicit, atomic transaction
|
|
118
|
+
management. Every public method follows the same pattern:
|
|
119
|
+
|
|
120
|
+
1. Open a new session via ``self._db.session()``.
|
|
121
|
+
2. Execute the query / mutation inside a ``try`` block.
|
|
122
|
+
3. Commit on success or roll back on any exception.
|
|
123
|
+
4. Always close the session in the ``finally`` block.
|
|
124
|
+
|
|
125
|
+
This guarantees that no session is leaked regardless of outcome, and that
|
|
126
|
+
partial writes are never visible to other readers.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
db_url : str
|
|
131
|
+
SQLAlchemy database URL forwarded to ``DatabaseManager``.
|
|
132
|
+
logger : Logger | None
|
|
133
|
+
Logger for operational and diagnostic messages.
|
|
134
|
+
Falls back to ``DEFAULT_LOGGER`` when ``None``.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, db_url: str, logger: Logger | None = None) -> None:
|
|
138
|
+
self._logger = logger or DEFAULT_LOGGER
|
|
139
|
+
self._db = DatabaseManager(db_url=db_url)
|
|
140
|
+
|
|
141
|
+
# ------------------------------------------------------------------
|
|
142
|
+
# BaseDB interface
|
|
143
|
+
# ------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def get_by_serial(self, serial: int) -> CertificateRecord | None:
|
|
146
|
+
"""
|
|
147
|
+
Fetch a single certificate record by its X.509 serial number.
|
|
148
|
+
|
|
149
|
+
The serial is stored as a string in the database (to avoid integer
|
|
150
|
+
overflow across all backends); the conversion is handled internally.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
serial : int
|
|
155
|
+
Integer serial number to look up.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
CertificateRecord | None
|
|
160
|
+
The matching ORM record, or ``None`` if no record exists for
|
|
161
|
+
*serial* or if a database error occurs.
|
|
162
|
+
"""
|
|
163
|
+
session = self._db.session()
|
|
164
|
+
try:
|
|
165
|
+
stmt = select(CertificateRecord).where(
|
|
166
|
+
CertificateRecord.serial_number == str(serial)
|
|
167
|
+
)
|
|
168
|
+
cert: CertificateRecord | None = session.execute(stmt).scalar_one_or_none()
|
|
169
|
+
self._logger.debug("get_by_serial(%d) → %s", serial, cert)
|
|
170
|
+
return cert
|
|
171
|
+
except Exception as exc:
|
|
172
|
+
self._logger.error(
|
|
173
|
+
"get_by_serial(%d) failed: %s", serial, exc, exc_info=True
|
|
174
|
+
)
|
|
175
|
+
return None
|
|
176
|
+
finally:
|
|
177
|
+
session.close()
|
|
178
|
+
|
|
179
|
+
def get_by_name(self, common_name: str) -> CertificateRecord | None:
|
|
180
|
+
"""
|
|
181
|
+
Fetch the active VALID certificate record for the given Common Name.
|
|
182
|
+
|
|
183
|
+
Only records with ``status == CertificateStatus.VALID`` are returned.
|
|
184
|
+
Revoked and expired records are ignored so that the caller always
|
|
185
|
+
receives the currently-active certificate for a given CN, or ``None``
|
|
186
|
+
if no active certificate exists.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
common_name : str
|
|
191
|
+
The CN (Common Name) value from the certificate Subject field.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
CertificateRecord | None
|
|
196
|
+
The matching VALID record, or ``None`` if absent or on DB error.
|
|
197
|
+
"""
|
|
198
|
+
session = self._db.session()
|
|
199
|
+
try:
|
|
200
|
+
stmt = select(CertificateRecord).where(
|
|
201
|
+
CertificateRecord.common_name == common_name,
|
|
202
|
+
CertificateRecord.status == CertificateStatus.VALID,
|
|
203
|
+
)
|
|
204
|
+
cert: CertificateRecord | None = session.execute(stmt).scalar_one_or_none()
|
|
205
|
+
self._logger.debug("get_by_name(%r) → %s", common_name, cert)
|
|
206
|
+
return cert
|
|
207
|
+
except Exception as exc:
|
|
208
|
+
self._logger.error(
|
|
209
|
+
"get_by_name(%r) failed: %s", common_name, exc, exc_info=True
|
|
210
|
+
)
|
|
211
|
+
return None
|
|
212
|
+
finally:
|
|
213
|
+
session.close()
|
|
214
|
+
|
|
215
|
+
def register_cert_in_db(
|
|
216
|
+
self,
|
|
217
|
+
cert: x509.Certificate,
|
|
218
|
+
uuid: str,
|
|
219
|
+
key_type: CertType = CertType.DEVICE,
|
|
220
|
+
) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
Persist a newly issued certificate to the registry.
|
|
223
|
+
|
|
224
|
+
Creates a new ``CertificateRecord`` row with ``status=VALID`` from the
|
|
225
|
+
metadata and PEM encoding of *cert*. The full PEM is stored so the
|
|
226
|
+
certificate can be reconstructed independently of the filesystem.
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
----------
|
|
230
|
+
cert : x509.Certificate
|
|
231
|
+
The issued X.509 certificate. Its Subject must contain at least
|
|
232
|
+
one ``commonName`` (CN) attribute.
|
|
233
|
+
uuid : str
|
|
234
|
+
UUID string that identifies the storage folder holding the
|
|
235
|
+
corresponding ``.pem``, ``.key``, and ``.csr`` files.
|
|
236
|
+
key_type : CertType
|
|
237
|
+
Certificate category. Stored as its ``str`` value (e.g.
|
|
238
|
+
``"device"``). Default: ``CertType.DEVICE``.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
bool
|
|
243
|
+
``True`` if the record was committed successfully;
|
|
244
|
+
``False`` if the operation was rolled back due to an error
|
|
245
|
+
(e.g. duplicate serial, constraint violation).
|
|
246
|
+
|
|
247
|
+
Raises
|
|
248
|
+
------
|
|
249
|
+
IndexError
|
|
250
|
+
Re-raised if the certificate contains no CN attribute, indicating
|
|
251
|
+
a malformed certificate that should not be stored.
|
|
252
|
+
"""
|
|
253
|
+
session = self._db.session()
|
|
254
|
+
try:
|
|
255
|
+
common_name = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[
|
|
256
|
+
0
|
|
257
|
+
].value
|
|
258
|
+
common_name = (
|
|
259
|
+
common_name
|
|
260
|
+
if isinstance(common_name, str)
|
|
261
|
+
else common_name.decode("utf-8")
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
record = CertificateRecord(
|
|
265
|
+
serial_number=str(cert.serial_number),
|
|
266
|
+
common_name=common_name,
|
|
267
|
+
not_valid_before=cert.not_valid_before_utc,
|
|
268
|
+
not_valid_after=cert.not_valid_after_utc,
|
|
269
|
+
certificate_pem=cert.public_bytes(serialization.Encoding.PEM).decode(
|
|
270
|
+
"utf-8"
|
|
271
|
+
),
|
|
272
|
+
status=CertificateStatus.VALID,
|
|
273
|
+
key_type=key_type.value,
|
|
274
|
+
uuid=uuid,
|
|
275
|
+
)
|
|
276
|
+
session.add(record)
|
|
277
|
+
session.commit()
|
|
278
|
+
self._logger.info(
|
|
279
|
+
"Certificate registered: CN=%r, serial=%s, uuid=%s",
|
|
280
|
+
common_name,
|
|
281
|
+
cert.serial_number,
|
|
282
|
+
uuid,
|
|
283
|
+
)
|
|
284
|
+
return True
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
session.rollback()
|
|
287
|
+
self._logger.error(
|
|
288
|
+
"register_cert_in_db failed (serial=%s): %s",
|
|
289
|
+
cert.serial_number,
|
|
290
|
+
exc,
|
|
291
|
+
exc_info=True,
|
|
292
|
+
)
|
|
293
|
+
return False
|
|
294
|
+
finally:
|
|
295
|
+
session.close()
|
|
296
|
+
|
|
297
|
+
def revoke_certificate(
|
|
298
|
+
self,
|
|
299
|
+
serial_number: int,
|
|
300
|
+
reason: x509.ReasonFlags = x509.ReasonFlags.unspecified,
|
|
301
|
+
) -> tuple[bool, RevokeStatus]:
|
|
302
|
+
"""
|
|
303
|
+
Mark a certificate as revoked and record the reason and timestamp.
|
|
304
|
+
|
|
305
|
+
Looks up the certificate by *serial_number* filtered to
|
|
306
|
+
``status == VALID`` — already-revoked or unknown serials are treated
|
|
307
|
+
as not found. On success the record is updated in-place:
|
|
308
|
+
- ``status`` → ``CertificateStatus.REVOKED``
|
|
309
|
+
- ``revocation_reason`` → integer value of *reason*
|
|
310
|
+
- ``revocation_date`` → current UTC timestamp
|
|
311
|
+
|
|
312
|
+
The change is committed atomically; a rollback is performed on any
|
|
313
|
+
unexpected error.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
serial_number : int
|
|
318
|
+
Serial number of the certificate to revoke.
|
|
319
|
+
reason : x509.ReasonFlags
|
|
320
|
+
RFC 5280 §5.3.1 revocation reason code.
|
|
321
|
+
Default: ``x509.ReasonFlags.unspecified`` (code 0).
|
|
322
|
+
|
|
323
|
+
Returns
|
|
324
|
+
-------
|
|
325
|
+
tuple[bool, RevokeStatus]
|
|
326
|
+
``(True, RevokeStatus.OK)`` — revocation committed.
|
|
327
|
+
``(False, RevokeStatus.NOT_FOUND)`` — no VALID cert with that serial.
|
|
328
|
+
``(False, RevokeStatus.UNKNOWN_ERROR)`` — unexpected internal error.
|
|
329
|
+
"""
|
|
330
|
+
session = self._db.session()
|
|
331
|
+
try:
|
|
332
|
+
stmt = select(CertificateRecord).where(
|
|
333
|
+
CertificateRecord.serial_number == str(serial_number),
|
|
334
|
+
CertificateRecord.status == CertificateStatus.VALID,
|
|
335
|
+
)
|
|
336
|
+
cert: CertificateRecord | None = session.execute(stmt).scalar_one_or_none()
|
|
337
|
+
|
|
338
|
+
if cert is None:
|
|
339
|
+
self._logger.warning(
|
|
340
|
+
"revoke_certificate: no VALID record found for serial=%d",
|
|
341
|
+
serial_number,
|
|
342
|
+
)
|
|
343
|
+
return False, RevokeStatus.NOT_FOUND
|
|
344
|
+
|
|
345
|
+
cert.status = CertificateStatus.REVOKED
|
|
346
|
+
cert.revocation_reason = reason.value if hasattr(reason, "value") else 0 # type: ignore
|
|
347
|
+
cert.revocation_date = datetime.datetime.now(datetime.UTC) # type: ignore
|
|
348
|
+
|
|
349
|
+
session.commit()
|
|
350
|
+
self._logger.info(
|
|
351
|
+
"Certificate revoked: serial=%d, reason=%s", serial_number, reason
|
|
352
|
+
)
|
|
353
|
+
return True, RevokeStatus.OK
|
|
354
|
+
|
|
355
|
+
except Exception as exc:
|
|
356
|
+
session.rollback()
|
|
357
|
+
self._logger.error(
|
|
358
|
+
"revoke_certificate(%d) failed: %s", serial_number, exc, exc_info=True
|
|
359
|
+
)
|
|
360
|
+
return False, RevokeStatus.UNKNOWN_ERROR
|
|
361
|
+
finally:
|
|
362
|
+
session.close()
|
|
363
|
+
|
|
364
|
+
def get_revoked_certificates(self) -> Generator[CertificateRecord, None, None]:
|
|
365
|
+
"""
|
|
366
|
+
Yield revoked certificate rows relevant for the current CRL window.
|
|
367
|
+
|
|
368
|
+
A record is included when **all** of the following conditions hold:
|
|
369
|
+
|
|
370
|
+
1. ``revocation_date`` is not ``NULL`` — the certificate was actually revoked.
|
|
371
|
+
2. ``not_valid_after > now`` — the certificate has not yet expired; expired
|
|
372
|
+
certificates need not appear in a CRL because relying parties will
|
|
373
|
+
reject them regardless.
|
|
374
|
+
3. ``revocation_date > now - 365 days`` — the revocation is recent enough
|
|
375
|
+
to be relevant. This prevents unbounded CRL growth from very old
|
|
376
|
+
entries that no relying party could still encounter.
|
|
377
|
+
|
|
378
|
+
Only three columns are selected (``serial_number``, ``revocation_date``,
|
|
379
|
+
``revocation_reason``) to minimise data transfer; callers must not
|
|
380
|
+
access other ``CertificateRecord`` attributes on the yielded rows.
|
|
381
|
+
|
|
382
|
+
Yields
|
|
383
|
+
------
|
|
384
|
+
CertificateRecord
|
|
385
|
+
SQLAlchemy ``Row`` objects with ``serial_number``,
|
|
386
|
+
``revocation_date``, and ``revocation_reason`` attributes.
|
|
387
|
+
|
|
388
|
+
Notes
|
|
389
|
+
-----
|
|
390
|
+
All rows are fetched in a single query before yielding begins. The
|
|
391
|
+
session is closed in the ``finally`` block; do not use the yielded
|
|
392
|
+
rows after the generator has been exhausted or abandoned.
|
|
393
|
+
"""
|
|
394
|
+
now = datetime.datetime.now(datetime.UTC)
|
|
395
|
+
cutoff = now - datetime.timedelta(days=365)
|
|
396
|
+
|
|
397
|
+
session = self._db.session()
|
|
398
|
+
try:
|
|
399
|
+
stmt = select(
|
|
400
|
+
CertificateRecord.serial_number,
|
|
401
|
+
CertificateRecord.revocation_date,
|
|
402
|
+
CertificateRecord.revocation_reason,
|
|
403
|
+
).where(
|
|
404
|
+
CertificateRecord.revocation_date.isnot(None),
|
|
405
|
+
CertificateRecord.not_valid_after > now,
|
|
406
|
+
CertificateRecord.revocation_date > cutoff,
|
|
407
|
+
)
|
|
408
|
+
rows = session.execute(stmt)
|
|
409
|
+
yield from rows # type: ignore
|
|
410
|
+
except Exception as exc:
|
|
411
|
+
self._logger.error(
|
|
412
|
+
"get_revoked_certificates failed: %s", exc, exc_info=True
|
|
413
|
+
)
|
|
414
|
+
finally:
|
|
415
|
+
session.close()
|
tiny_ca/exc.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from tiny_ca.settings import DT_STR_FORMAT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CryptoException(Exception):
|
|
8
|
+
"""Raised when certificate validation fails."""
|
|
9
|
+
|
|
10
|
+
message: str
|
|
11
|
+
|
|
12
|
+
def __init__(self, message: str) -> None:
|
|
13
|
+
super().__init__(message)
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
return f"Error `{self.message}`"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NotExistCertFile(CryptoException):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
path_to_file: Path | str,
|
|
23
|
+
) -> None:
|
|
24
|
+
message = f"Not found cert file by path `{path_to_file}`"
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.message = message
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class IsNotFile(CryptoException):
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
path_to_file: Path | str,
|
|
33
|
+
) -> None:
|
|
34
|
+
message = f"The specified path `{path_to_file}` does not contain a file "
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.message = message
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class WrongType(CryptoException):
|
|
40
|
+
def __init__(
|
|
41
|
+
self, wrong_type: str, allowed_types: tuple[str, ...] = (".key", ".pem", ".csr")
|
|
42
|
+
) -> None:
|
|
43
|
+
message = f"The specified type `{wrong_type}` is invalid for the key. Available types: `{allowed_types}`"
|
|
44
|
+
super().__init__(message)
|
|
45
|
+
self.message = message
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ErrorLoadCert(CryptoException):
|
|
49
|
+
def __init__(self, path_to_file: Path | str, exc: str) -> None:
|
|
50
|
+
message = f"Error in time load cert {path_to_file}. Error: {exc}"
|
|
51
|
+
super().__init__(message)
|
|
52
|
+
self.message = message
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class InvalidRangeTimeCertificate(CryptoException):
|
|
56
|
+
def __init__(self, valid_from: datetime, valid_to: datetime, now: datetime) -> None:
|
|
57
|
+
message = f"""The data provided for certificate generation is not valid.
|
|
58
|
+
The desired certificate lifetime is from: `{valid_from.strftime(DT_STR_FORMAT)}`
|
|
59
|
+
to: `{valid_to.strftime(DT_STR_FORMAT)}`; now: `{now.strftime(DT_STR_FORMAT)}`.
|
|
60
|
+
Please specify a valid lifetime.
|
|
61
|
+
"""
|
|
62
|
+
super().__init__(message)
|
|
63
|
+
self.message = message
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class FileAlreadyExists(CryptoException):
|
|
67
|
+
def __init__(self, path_save_cert: Path) -> None:
|
|
68
|
+
message = (
|
|
69
|
+
f"A certificate at the specified path `{path_save_cert}` already exists."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
super().__init__(message)
|
|
73
|
+
self.message = message
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class NotUniqueCertOwner(CryptoException):
|
|
77
|
+
def __init__(self, common_name: str) -> None:
|
|
78
|
+
message = f"Not unique common name - `{common_name}`. Change common_name or use is_overwrite=True"
|
|
79
|
+
|
|
80
|
+
super().__init__(message)
|
|
81
|
+
self.message = message
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DBNotInitedError(CryptoException):
|
|
85
|
+
def __init__(self) -> None:
|
|
86
|
+
message = (
|
|
87
|
+
"The action cannot be performed because the database is not initialized."
|
|
88
|
+
)
|
|
89
|
+
super().__init__(message)
|
|
90
|
+
self.message = message
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ValidationCertError(CryptoException):
|
|
94
|
+
def __init__(self, message: str) -> None:
|
|
95
|
+
super().__init__(message)
|
|
96
|
+
self.message = message
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class CertNotFound(CryptoException):
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
message = "Certificate not found"
|
|
102
|
+
super().__init__(message)
|
|
103
|
+
self.message = message
|