tiny-ca 0.1.0__tar.gz
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-0.1.0/PKG-INFO +430 -0
- tiny_ca-0.1.0/README.md +417 -0
- tiny_ca-0.1.0/pyproject.toml +121 -0
- tiny_ca-0.1.0/setup.cfg +4 -0
- tiny_ca-0.1.0/tests/test_afile_loader.py +352 -0
- tiny_ca-0.1.0/tests/test_async_db_handler.py +409 -0
- tiny_ca-0.1.0/tests/test_async_lifecycle_manager.py +561 -0
- tiny_ca-0.1.0/tests/test_async_local_storage.py +262 -0
- tiny_ca-0.1.0/tests/test_base_db.py +129 -0
- tiny_ca-0.1.0/tests/test_base_storage.py +108 -0
- tiny_ca-0.1.0/tests/test_database_manager.py +47 -0
- tiny_ca-0.1.0/tests/test_db_const.py +84 -0
- tiny_ca-0.1.0/tests/test_factory.py +554 -0
- tiny_ca-0.1.0/tests/test_file_loader.py +344 -0
- tiny_ca-0.1.0/tests/test_life_time.py +192 -0
- tiny_ca-0.1.0/tests/test_local_storage.py +406 -0
- tiny_ca-0.1.0/tests/test_models.py +218 -0
- tiny_ca-0.1.0/tests/test_serial.py +180 -0
- tiny_ca-0.1.0/tests/test_serial_generator.py +356 -0
- tiny_ca-0.1.0/tests/test_storage_const.py +111 -0
- tiny_ca-0.1.0/tests/test_sync_db_manager.py +358 -0
- tiny_ca-0.1.0/tests/test_sync_lifecycle_manager.py +569 -0
- tiny_ca-0.1.0/tiny_ca/__init__.py +41 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/__init__.py +15 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/factory.py +658 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/utils/__init__.py +10 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/utils/afile_loader.py +157 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/utils/file_loader.py +290 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/utils/life_time.py +199 -0
- tiny_ca-0.1.0/tiny_ca/ca_factory/utils/serial.py +151 -0
- tiny_ca-0.1.0/tiny_ca/const.py +81 -0
- tiny_ca-0.1.0/tiny_ca/db/__init__.py +14 -0
- tiny_ca-0.1.0/tiny_ca/db/async_db_manager.py +358 -0
- tiny_ca-0.1.0/tiny_ca/db/base_db.py +173 -0
- tiny_ca-0.1.0/tiny_ca/db/const.py +92 -0
- tiny_ca-0.1.0/tiny_ca/db/models.py +144 -0
- tiny_ca-0.1.0/tiny_ca/db/sync_db_manager.py +415 -0
- tiny_ca-0.1.0/tiny_ca/exc.py +103 -0
- tiny_ca-0.1.0/tiny_ca/managers/__init__.py +3 -0
- tiny_ca-0.1.0/tiny_ca/managers/async_lifecycle_manager.py +702 -0
- tiny_ca-0.1.0/tiny_ca/managers/sync_lifecycle_manager.py +722 -0
- tiny_ca-0.1.0/tiny_ca/models/__init__.py +3 -0
- tiny_ca-0.1.0/tiny_ca/models/certtificate.py +300 -0
- tiny_ca-0.1.0/tiny_ca/settings.py +9 -0
- tiny_ca-0.1.0/tiny_ca/storage/__init__.py +4 -0
- tiny_ca-0.1.0/tiny_ca/storage/async_local_storage.py +218 -0
- tiny_ca-0.1.0/tiny_ca/storage/base_storage.py +166 -0
- tiny_ca-0.1.0/tiny_ca/storage/const.py +39 -0
- tiny_ca-0.1.0/tiny_ca/storage/local_storage.py +448 -0
- tiny_ca-0.1.0/tiny_ca/utils/__init__.py +0 -0
- tiny_ca-0.1.0/tiny_ca/utils/serial_generator.py +530 -0
- tiny_ca-0.1.0/tiny_ca.egg-info/PKG-INFO +430 -0
- tiny_ca-0.1.0/tiny_ca.egg-info/SOURCES.txt +54 -0
- tiny_ca-0.1.0/tiny_ca.egg-info/dependency_links.txt +1 -0
- tiny_ca-0.1.0/tiny_ca.egg-info/requires.txt +6 -0
- tiny_ca-0.1.0/tiny_ca.egg-info/top_level.txt +1 -0
tiny_ca-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tiny-ca
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: aiofiles>=25.1.0
|
|
8
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
9
|
+
Requires-Dist: cryptography>=46.0.5
|
|
10
|
+
Requires-Dist: pydantic[email]>=2.12.5
|
|
11
|
+
Requires-Dist: sqlalchemy[mypy]>=2.0.48
|
|
12
|
+
Requires-Dist: types-aiofiles>=25.1.0.20251011
|
|
13
|
+
|
|
14
|
+
# tiny_ca
|
|
15
|
+
|
|
16
|
+
[](https://github.com/Shchusia/tiny_ca)
|
|
17
|
+
[](https://coveralls.io/github/Shchusia/tiny_ca?branch=feature/docs)
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/tiny_ca/)
|
|
20
|
+
[](https://shchusia.github.io/tiny_ca/)
|
|
21
|
+
|
|
22
|
+
A lightweight Python library for managing the full lifecycle of X.509 certificates — from bootstrapping a self-signed root CA to issuing, revoking, and rotating end-entity certificates, generating CRLs, and persisting all artefacts to local storage backed by a relational database.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Table of Contents
|
|
27
|
+
|
|
28
|
+
- [Features](#features)
|
|
29
|
+
- [Architecture Overview](#architecture-overview)
|
|
30
|
+
- [Installation](#installation)
|
|
31
|
+
- [Quick Start](#quick-start)
|
|
32
|
+
- [1. Bootstrap a Self-Signed Root CA](#1-bootstrap-a-self-signed-root-ca)
|
|
33
|
+
- [2. Issue an End-Entity Certificate](#2-issue-an-end-entity-certificate)
|
|
34
|
+
- [3. Revoke a Certificate](#3-revoke-a-certificate)
|
|
35
|
+
- [4. Generate a CRL](#4-generate-a-crl)
|
|
36
|
+
- [5. Verify a Certificate](#5-verify-a-certificate)
|
|
37
|
+
- [6. Rotate a Certificate](#6-rotate-a-certificate)
|
|
38
|
+
- [7. Async Usage](#7-async-usage)
|
|
39
|
+
- [Configuration Models](#configuration-models)
|
|
40
|
+
- [Storage Backends](#storage-backends)
|
|
41
|
+
- [Database Adapters](#database-adapters)
|
|
42
|
+
- [Serial Number Encoding](#serial-number-encoding)
|
|
43
|
+
- [Error Reference](#error-reference)
|
|
44
|
+
- [Running Tests](#running-tests)
|
|
45
|
+
- [Project Structure](#project-structure)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- **Self-signed CA bootstrap** — generate a root CA certificate and key in one call.
|
|
52
|
+
- **End-entity certificate issuance** — server, client, device, user, and service certificates with SANs (DNS + IP).
|
|
53
|
+
- **Certificate revocation** — mark certificates as revoked in the database with RFC 5280 reason codes.
|
|
54
|
+
- **CRL generation** — build and sign a Certificate Revocation List from current revocation records.
|
|
55
|
+
- **Certificate verification** — validate issuer, validity window, signature, and revocation status.
|
|
56
|
+
- **Certificate rotation** — atomically revoke an old certificate and issue its replacement.
|
|
57
|
+
- **Pluggable storage** — `LocalStorage` and `AsyncLocalStorage` write PEM/key/CSR/CRL files to a configurable directory tree.
|
|
58
|
+
- **Pluggable database** — `SyncDBHandler` (SQLAlchemy sync) and `AsyncDBHandler` (SQLAlchemy async/aiosqlite) back the certificate registry.
|
|
59
|
+
- **Sync and async APIs** — `CertLifecycleManager` (sync) and `AsyncCertLifecycleManager` (async) with identical feature sets.
|
|
60
|
+
- **Smart serial numbers** — `SerialWithEncoding` packs a CertType prefix + name fragment + UUID randomness into a single 160-bit integer; fully RFC 5280-compliant.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Architecture Overview
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
CertLifecycleManager / AsyncCertLifecycleManager
|
|
68
|
+
│
|
|
69
|
+
├── CertificateFactory ← cryptographic operations only
|
|
70
|
+
│ ├── CAFileLoader / AsyncCAFileLoader ← load CA from PEM files
|
|
71
|
+
│ └── CertLifetime / CertSerialParser ← validity & serial helpers
|
|
72
|
+
│
|
|
73
|
+
├── BaseStorage
|
|
74
|
+
│ ├── LocalStorage ← sync filesystem backend
|
|
75
|
+
│ └── AsyncLocalStorage ← async filesystem backend
|
|
76
|
+
│
|
|
77
|
+
└── BaseDB
|
|
78
|
+
├── SyncDBHandler ← SQLAlchemy sync
|
|
79
|
+
└── AsyncDBHandler ← SQLAlchemy async (aiosqlite)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Every component is injected at construction time — no global state, easy to test.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install tiny_ca
|
|
90
|
+
# async support (aiosqlite + aiofiles)
|
|
91
|
+
pip install tiny_ca[async]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Dependencies: `cryptography`, `sqlalchemy`, `pydantic`.
|
|
95
|
+
Optional: `aiosqlite`, `aiofiles` (async backends).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
### 1. Bootstrap a Self-Signed Root CA
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from tiny_ca.managers.sync_lifecycle_manager import CertLifecycleManager
|
|
105
|
+
from tiny_ca.models.certtificate import CAConfig
|
|
106
|
+
from tiny_ca.storage.local_storage import LocalStorage
|
|
107
|
+
from tiny_ca.db.sync_db_manager import SyncDBHandler
|
|
108
|
+
|
|
109
|
+
storage = LocalStorage(base_folder="./pki")
|
|
110
|
+
db = SyncDBHandler(db_url="sqlite:///pki.db")
|
|
111
|
+
|
|
112
|
+
mgr = CertLifecycleManager(storage=storage, db_handler=db)
|
|
113
|
+
|
|
114
|
+
config = CAConfig(
|
|
115
|
+
common_name="My Internal CA",
|
|
116
|
+
organization="ACME Corp",
|
|
117
|
+
country="UA",
|
|
118
|
+
key_size=4096,
|
|
119
|
+
days_valid=3650,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
cert_path, key_path = mgr.create_self_signed_ca(config)
|
|
123
|
+
print(f"CA certificate: {cert_path}")
|
|
124
|
+
print(f"CA private key: {key_path}")
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Issue an End-Entity Certificate
|
|
128
|
+
|
|
129
|
+
After bootstrapping the CA you need to load it back and attach a `CertificateFactory`:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from tiny_ca.ca_factory.utils.file_loader import CAFileLoader
|
|
133
|
+
from tiny_ca.ca_factory.factory import CertificateFactory
|
|
134
|
+
from tiny_ca.models.certtificate import ClientConfig
|
|
135
|
+
from tiny_ca.const import CertType
|
|
136
|
+
|
|
137
|
+
loader = CAFileLoader(
|
|
138
|
+
ca_cert_path="./pki/<uuid>/ca.pem",
|
|
139
|
+
ca_key_path="./pki/<uuid>/ca.key",
|
|
140
|
+
)
|
|
141
|
+
mgr.factory = CertificateFactory(loader)
|
|
142
|
+
|
|
143
|
+
svc_config = ClientConfig(
|
|
144
|
+
common_name="nginx.internal",
|
|
145
|
+
serial_type=CertType.SERVICE,
|
|
146
|
+
key_size=2048,
|
|
147
|
+
days_valid=365,
|
|
148
|
+
is_server_cert=True,
|
|
149
|
+
san_dns=["nginx.internal", "www.nginx.internal"],
|
|
150
|
+
san_ip=["192.168.1.10"],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
cert, key, csr = mgr.issue_certificate(svc_config, cert_path="services")
|
|
154
|
+
print(f"Issued: {cert.serial_number}")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 3. Revoke a Certificate
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from cryptography import x509
|
|
161
|
+
|
|
162
|
+
success = mgr.revoke_certificate(
|
|
163
|
+
serial=cert.serial_number,
|
|
164
|
+
reason=x509.ReasonFlags.key_compromise,
|
|
165
|
+
)
|
|
166
|
+
print("Revoked:", success)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 4. Generate a CRL
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
crl = mgr.generate_crl(days_valid=7)
|
|
173
|
+
# Written to <base_folder>/crl.pem automatically
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 5. Verify a Certificate
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from tiny_ca.exc import ValidationCertError
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
mgr.verify_certificate(cert)
|
|
183
|
+
print("Certificate is valid")
|
|
184
|
+
except ValidationCertError as e:
|
|
185
|
+
print(f"Validation failed: {e}")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 6. Rotate a Certificate
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
new_cert, new_key, new_csr = mgr.rotate_certificate(
|
|
192
|
+
serial=cert.serial_number,
|
|
193
|
+
config=svc_config,
|
|
194
|
+
)
|
|
195
|
+
print(f"Rotated to serial: {new_cert.serial_number}")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 7. Async Usage
|
|
199
|
+
|
|
200
|
+
All operations are available as `async`/`await` via `AsyncCertLifecycleManager`:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
import asyncio
|
|
204
|
+
from tiny_ca.managers.async_lifecycle_manager import AsyncCertLifecycleManager
|
|
205
|
+
from tiny_ca.storage.async_local_storage import AsyncLocalStorage
|
|
206
|
+
from tiny_ca.db.async_db_manager import AsyncDBHandler
|
|
207
|
+
from tiny_ca.models.certtificate import CAConfig, ClientConfig
|
|
208
|
+
from tiny_ca.const import CertType
|
|
209
|
+
|
|
210
|
+
async def main():
|
|
211
|
+
storage = AsyncLocalStorage(base_folder="./pki_async")
|
|
212
|
+
db = AsyncDBHandler(db_url="sqlite+aiosqlite:///pki_async.db")
|
|
213
|
+
await db._db.init_db()
|
|
214
|
+
|
|
215
|
+
mgr = AsyncCertLifecycleManager(storage=storage, db_handler=db)
|
|
216
|
+
|
|
217
|
+
# Bootstrap CA
|
|
218
|
+
cert_path, key_path = await mgr.create_self_signed_ca(
|
|
219
|
+
CAConfig(common_name="Async CA", organization="ACME", country="UA",
|
|
220
|
+
key_size=2048, days_valid=3650)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Attach factory (after loading the CA)
|
|
224
|
+
from tiny_ca.ca_factory.utils.afile_loader import AsyncCAFileLoader
|
|
225
|
+
from tiny_ca.ca_factory.factory import CertificateFactory
|
|
226
|
+
|
|
227
|
+
loader = await AsyncCAFileLoader.create(cert_path.parent / "ca.pem",
|
|
228
|
+
cert_path.parent / "ca.key")
|
|
229
|
+
mgr.factory = CertificateFactory(loader)
|
|
230
|
+
|
|
231
|
+
# Issue
|
|
232
|
+
cert, key, csr = await mgr.issue_certificate(
|
|
233
|
+
ClientConfig(common_name="modules.internal", serial_type=CertType.SERVICE,
|
|
234
|
+
key_size=2048, days_valid=365, is_server_cert=True)
|
|
235
|
+
)
|
|
236
|
+
print("Issued:", cert.serial_number)
|
|
237
|
+
|
|
238
|
+
asyncio.run(main())
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Configuration Models
|
|
244
|
+
|
|
245
|
+
Both models are Pydantic `BaseModel` instances — all fields are validated on construction.
|
|
246
|
+
|
|
247
|
+
### `CAConfig`
|
|
248
|
+
|
|
249
|
+
| Field | Type | Default | Description |
|
|
250
|
+
|---|---|---|---|
|
|
251
|
+
| `common_name` | `str` | — | CA Common Name (CN) |
|
|
252
|
+
| `organization` | `str` | — | Organization (O) |
|
|
253
|
+
| `country` | `str` | — | Two-letter ISO country code |
|
|
254
|
+
| `key_size` | `int` | `2048` | RSA key length in bits |
|
|
255
|
+
| `days_valid` | `int` | `3650` | Validity period in days |
|
|
256
|
+
|
|
257
|
+
### `ClientConfig`
|
|
258
|
+
|
|
259
|
+
| Field | Type | Default | Description |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| `common_name` | `str` | — | Certificate CN |
|
|
262
|
+
| `serial_type` | `CertType` | `SERVICE` | Certificate category |
|
|
263
|
+
| `key_size` | `int` | `2048` | RSA key length |
|
|
264
|
+
| `days_valid` | `int` | `365` | Validity period |
|
|
265
|
+
| `email` | `str \| None` | `None` | Optional emailAddress Subject attribute |
|
|
266
|
+
| `is_server_cert` | `bool` | `False` | Adds ServerAuth EKU + DNS SAN from CN |
|
|
267
|
+
| `is_client_cert` | `bool` | `False` | Adds ClientAuth EKU |
|
|
268
|
+
| `san_dns` | `list[str] \| None` | `None` | Extra DNS Subject Alternative Names |
|
|
269
|
+
| `san_ip` | `list[str] \| None` | `None` | IP address SANs |
|
|
270
|
+
| `name` | `str \| None` | `None` | Override output file base name |
|
|
271
|
+
|
|
272
|
+
### `CertType` enum
|
|
273
|
+
|
|
274
|
+
| Value | Description |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `CA` | Root or intermediate CA |
|
|
277
|
+
| `USER` | User / human certificate |
|
|
278
|
+
| `SERVICE` | Service / server certificate |
|
|
279
|
+
| `DEVICE` | IoT / device certificate |
|
|
280
|
+
| `INTERNAL` | Internal infrastructure certificate |
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Storage Backends
|
|
285
|
+
|
|
286
|
+
### `LocalStorage` (sync)
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from tiny_ca.storage.local_storage import LocalStorage
|
|
290
|
+
from cryptography.hazmat.primitives import serialization
|
|
291
|
+
|
|
292
|
+
storage = LocalStorage(
|
|
293
|
+
base_folder="./pki",
|
|
294
|
+
base_encoding=serialization.Encoding.PEM,
|
|
295
|
+
base_private_format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
296
|
+
base_encryption_algorithm=serialization.NoEncryption(),
|
|
297
|
+
)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
File layout:
|
|
301
|
+
```
|
|
302
|
+
./pki/
|
|
303
|
+
└── [cert_path/]
|
|
304
|
+
└── <uuid>/
|
|
305
|
+
├── service.pem # x509.Certificate
|
|
306
|
+
├── service.key # RSA private key
|
|
307
|
+
└── service.csr # CertificateSigningRequest
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### `AsyncLocalStorage` (async)
|
|
311
|
+
|
|
312
|
+
Drop-in async replacement for `LocalStorage` — same constructor, same layout, all I/O methods are `async`.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Database Adapters
|
|
317
|
+
|
|
318
|
+
### `SyncDBHandler`
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from tiny_ca.db.sync_db_manager import SyncDBHandler
|
|
322
|
+
|
|
323
|
+
db = SyncDBHandler(db_url="sqlite:///pki.db")
|
|
324
|
+
# PostgreSQL: "postgresql+psycopg2://user:pass@host/dbname"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### `AsyncDBHandler`
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from tiny_ca.db.async_db_manager import AsyncDBHandler
|
|
331
|
+
|
|
332
|
+
db = AsyncDBHandler(db_url="sqlite+aiosqlite:///pki.db")
|
|
333
|
+
await db._db.init_db() # create schema on first run
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Both implement `BaseDB`:
|
|
337
|
+
|
|
338
|
+
| Method | Description |
|
|
339
|
+
|---|---|
|
|
340
|
+
| `get_by_serial(serial)` | Fetch record by X.509 serial number |
|
|
341
|
+
| `get_by_name(common_name)` | Fetch active VALID record by CN |
|
|
342
|
+
| `register_cert_in_db(cert, uuid, key_type)` | Persist new certificate |
|
|
343
|
+
| `revoke_certificate(serial, reason)` | Mark certificate as revoked |
|
|
344
|
+
| `get_revoked_certificates()` | Yield records for CRL generation |
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Serial Number Encoding
|
|
349
|
+
|
|
350
|
+
`SerialWithEncoding` packs three fields into a single 160-bit integer:
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
[ 16-bit prefix ][ 80-bit name ][ 64-bit random ]
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- **prefix** — 2-byte ASCII code of the `CertType` (e.g. `"SV"` for `SERVICE`).
|
|
357
|
+
- **name** — up to 10 ASCII characters from the CN, zero-padded.
|
|
358
|
+
- **random** — lower 64 bits of a fresh `uuid.uuid4()`.
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
from tiny_ca.utils.serial_generator import SerialWithEncoding
|
|
362
|
+
from tiny_ca.const import CertType
|
|
363
|
+
|
|
364
|
+
serial = SerialWithEncoding.generate("nginx", CertType.SERVICE)
|
|
365
|
+
cert_type, name = SerialWithEncoding.parse(serial)
|
|
366
|
+
# cert_type == CertType.SERVICE
|
|
367
|
+
# name == "nginx"
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Error Reference
|
|
373
|
+
|
|
374
|
+
| Exception | When raised |
|
|
375
|
+
|---|---|
|
|
376
|
+
| `DBNotInitedError` | A DB-required operation is called but `db_handler` is `None` |
|
|
377
|
+
| `NotUniqueCertOwner` | CN conflict detected and `is_overwrite=False` |
|
|
378
|
+
| `CertNotFound` | `rotate_certificate` called for a non-existent serial |
|
|
379
|
+
| `ValidationCertError` | Issuer mismatch, expired, or signature verification failure |
|
|
380
|
+
| `InvalidRangeTimeCertificate` | Computed `not_after` is already in the past |
|
|
381
|
+
| `FileAlreadyExists` | Target file exists and `is_overwrite=False` |
|
|
382
|
+
| `NotExistCertFile` | CA PEM file path does not exist |
|
|
383
|
+
| `IsNotFile` | CA PEM path exists but is not a regular file |
|
|
384
|
+
| `WrongType` | CA PEM file has an unsupported extension |
|
|
385
|
+
| `ErrorLoadCert` | PEM deserialisation failed |
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Running Tests
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
pip install pytest pytest-cov aiosqlite aiofiles
|
|
393
|
+
pytest tests/ --cov=tiny_ca --cov-report=term-missing
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Project Structure
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
tiny_ca/
|
|
402
|
+
├── ca_factory/
|
|
403
|
+
│ ├── factory.py # CertificateFactory — crypto generation
|
|
404
|
+
│ └── utils/
|
|
405
|
+
│ ├── file_loader.py # CAFileLoader + ICALoader protocol
|
|
406
|
+
│ ├── afile_loader.py # AsyncCAFileLoader
|
|
407
|
+
│ ├── life_time.py # CertLifetime — validity window helpers
|
|
408
|
+
│ └── serial.py # CertSerialParser
|
|
409
|
+
├── db/
|
|
410
|
+
│ ├── base_db.py # BaseDB ABC
|
|
411
|
+
│ ├── models.py # CertificateRecord ORM model
|
|
412
|
+
│ ├── const.py # RevokeStatus, CertificateStatus
|
|
413
|
+
│ ├── sync_db_manager.py # SyncDBHandler + DatabaseManager
|
|
414
|
+
│ └── async_db_manager.py # AsyncDBHandler + async DatabaseManager
|
|
415
|
+
├── managers/
|
|
416
|
+
│ ├── sync_lifecycle_manager.py # CertLifecycleManager
|
|
417
|
+
│ └── async_lifecycle_manager.py # AsyncCertLifecycleManager
|
|
418
|
+
├── models/
|
|
419
|
+
│ └── certtificate.py # CAConfig, ClientConfig, CertificateInfo
|
|
420
|
+
├── storage/
|
|
421
|
+
│ ├── base_storage.py # BaseStorage ABC
|
|
422
|
+
│ ├── const.py # CryptoObject type alias
|
|
423
|
+
│ ├── local_storage.py # LocalStorage + _CertSerializer
|
|
424
|
+
│ └── async_local_storage.py # AsyncLocalStorage
|
|
425
|
+
├── utils/
|
|
426
|
+
│ └── serial_generator.py # SerialGenerator, SerialWithEncoding, _PrefixRegistry
|
|
427
|
+
├── const.py # CertType enum
|
|
428
|
+
├── exc.py # All custom exceptions
|
|
429
|
+
└── settings.py # DEFAULT_LOGGER
|
|
430
|
+
```
|