0
# Cryptographic Utilities
1
2
Certificate and key generation, management, and validation functions for handling SSL/TLS certificates, private keys, and certificate signing requests.
3
4
## Capabilities
5
6
### Key Generation
7
8
Generate private keys for certificate signing requests and SSL/TLS certificates.
9
10
```python { .api }
11
def generate_key(key_size: int, key_dir: Optional[str], key_type: str = "rsa",
12
elliptic_curve: str = "secp256r1", keyname: str = "key-certbot.pem",
13
strict_permissions: bool = True) -> util.Key:
14
"""
15
Initialize and save a private key in PEM format.
16
17
Args:
18
key_size: Key size in bits if key type is RSA
19
key_dir: Optional directory to save key file
20
key_type: Key type ("rsa" or "ecdsa")
21
elliptic_curve: Name of elliptic curve if key type is ECDSA
22
keyname: Filename for the key (may be modified if file exists)
23
strict_permissions: If true, enforce 0700 permissions on key_dir
24
25
Returns:
26
Key object containing file path and PEM data
27
28
Raises:
29
ValueError: If unable to generate key with given parameters
30
"""
31
32
def make_key(bits: int = 2048, key_type: str = "rsa",
33
elliptic_curve: Optional[str] = None) -> bytes:
34
"""
35
Generate a private key and return PEM-encoded bytes.
36
37
Args:
38
bits: Key size in bits for RSA keys (minimum 2048)
39
key_type: Type of key to generate ("rsa" or "ecdsa")
40
elliptic_curve: Elliptic curve name for ECDSA keys
41
42
Returns:
43
PEM-encoded private key as bytes
44
45
Raises:
46
ValueError: If key generation parameters are invalid
47
"""
48
49
def valid_privkey(privkey: Union[str, bytes]) -> bool:
50
"""
51
Check if private key is valid and loadable.
52
53
Args:
54
privkey: Private key file contents in PEM format
55
56
Returns:
57
True if private key is valid and can be loaded
58
"""
59
```
60
61
Usage examples:
62
63
```python
64
from certbot import crypto_util
65
66
# Generate RSA key
67
rsa_key = crypto_util.generate_key(
68
key_size=2048,
69
key_dir='/etc/letsencrypt/keys',
70
key_type='rsa'
71
)
72
73
# Generate ECDSA key
74
ecdsa_key = crypto_util.generate_key(
75
key_size=256,
76
key_dir='/etc/letsencrypt/keys',
77
key_type='ecdsa',
78
elliptic_curve='secp256r1'
79
)
80
81
# Generate key without saving to file
82
key_pem = crypto_util.make_key(bits=2048, key_type='rsa')
83
```
84
85
### Certificate Signing Request Generation
86
87
Create certificate signing requests (CSRs) for obtaining certificates from ACME CAs.
88
89
```python { .api }
90
def generate_csr(privkey: util.Key, names: Union[list[str], set[str]],
91
path: Optional[str], must_staple: bool = False,
92
strict_permissions: bool = True) -> util.CSR:
93
94
def make_csr(private_key_pem: bytes, domains: list[str],
95
must_staple: bool = False) -> bytes:
96
"""
97
Generate a CSR and return DER-encoded bytes.
98
99
Args:
100
private_key_pem: PEM-encoded private key
101
domains: List of domain names for the certificate
102
must_staple: Whether to include OCSP Must-Staple extension
103
104
Returns:
105
DER-encoded CSR as bytes
106
"""
107
108
def valid_csr(csr: bytes) -> bool:
109
"""
110
Validate CSR with correct self-signed signature.
111
112
Args:
113
csr: CSR in PEM format
114
115
Returns:
116
True if CSR is valid with correct signature
117
"""
118
119
def csr_matches_pubkey(csr: bytes, privkey: bytes) -> bool:
120
"""
121
Check if private key corresponds to the CSR's public key.
122
123
Args:
124
csr: CSR in PEM format
125
privkey: Private key file contents in PEM format
126
127
Returns:
128
True if private key matches CSR public key
129
"""
130
131
def import_csr_file(csrfile: str, data: bytes) -> tuple[acme_crypto_util.Format, util.CSR, list[str]]:
132
"""
133
Import a CSR file which can be either PEM or DER format.
134
135
Args:
136
csrfile: CSR filename
137
data: Contents of the CSR file
138
139
Returns:
140
Tuple of (Format.PEM, CSR object, list of domains requested)
141
142
Raises:
143
errors.Error: If CSR file cannot be parsed
144
"""
145
```
146
147
The generate_csr function creates certificate signing requests for ACME certificate requests:
148
149
```python { .api }
150
def generate_csr(privkey: util.Key, names: Union[list[str], set[str]],
151
path: Optional[str], must_staple: bool = False,
152
strict_permissions: bool = True) -> util.CSR:
153
"""
154
Generate a certificate signing request.
155
156
Args:
157
privkey: Private key to sign the CSR
158
names: Domain names to include in the CSR (first is CN, rest are SANs)
159
path: Optional path to save CSR file
160
must_staple: Whether to include OCSP Must-Staple extension
161
strict_permissions: If true, enforce 0755 permissions on path directory
162
163
Returns:
164
CSR object containing file path, data, and format
165
166
Raises:
167
ValueError: If names list is empty or invalid
168
"""
169
170
def make_csr(private_key_pem: bytes, domains: list[str],
171
must_staple: bool = False) -> bytes:
172
"""
173
Generate a CSR and return DER-encoded bytes.
174
175
Args:
176
private_key_pem: PEM-encoded private key
177
domains: List of domain names for the certificate
178
must_staple: Whether to include OCSP Must-Staple extension
179
180
Returns:
181
DER-encoded CSR as bytes
182
"""
183
```
184
185
Usage examples:
186
187
```python
188
from certbot import crypto_util, util
189
190
# Generate key first
191
key = crypto_util.generate_key(2048, '/etc/letsencrypt/keys')
192
193
# Generate CSR for multiple domains
194
domains = ['example.com', 'www.example.com', 'api.example.com']
195
csr = crypto_util.generate_csr(
196
privkey=key,
197
names=domains,
198
path='/etc/letsencrypt/csr/example.csr',
199
must_staple=True
200
)
201
202
# Generate CSR without saving to file
203
csr_data = crypto_util.make_csr(key.pem, domains, must_staple=False)
204
```
205
206
### Certificate Information
207
208
Extract information and validate certificates.
209
210
```python { .api }
211
def notAfter(cert_path: str) -> datetime:
212
"""
213
Get certificate expiration date.
214
215
Args:
216
cert_path: Path to certificate file
217
218
Returns:
219
Certificate expiration datetime
220
221
Raises:
222
errors.Error: If certificate cannot be read
223
"""
224
225
def notBefore(cert_path: str) -> datetime:
226
"""
227
Get certificate valid from date.
228
229
Args:
230
cert_path: Path to certificate file
231
232
Returns:
233
Certificate valid from datetime
234
235
Raises:
236
errors.Error: If certificate cannot be read
237
"""
238
239
def cert_from_chain(chain_path: str) -> x509.Certificate:
240
"""
241
Extract the first certificate from a certificate chain file.
242
243
Args:
244
chain_path: Path to certificate chain file
245
246
Returns:
247
X.509 certificate object
248
249
Raises:
250
errors.Error: If chain file cannot be read or parsed
251
"""
252
253
def get_sans_from_cert(cert_path: str, typ: type = x509.DNSName) -> list[str]:
254
"""
255
Get Subject Alternative Names from certificate.
256
257
Args:
258
cert_path: Path to certificate file
259
typ: Type of SAN to extract (default: DNS names)
260
261
Returns:
262
List of SAN values
263
"""
264
```
265
266
Usage examples:
267
268
```python
269
from certbot import crypto_util
270
from datetime import datetime, timezone
271
272
# Check certificate expiration
273
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
274
expiry = crypto_util.notAfter(cert_path)
275
if expiry < datetime.now(timezone.utc):
276
print("Certificate has expired")
277
278
# Get certificate validity period
279
valid_from = crypto_util.notBefore(cert_path)
280
valid_until = crypto_util.notAfter(cert_path)
281
282
# Extract certificate from chain
283
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
284
cert = crypto_util.cert_from_chain(chain_path)
285
286
# Get domain names from certificate
287
domains = crypto_util.get_sans_from_cert(cert_path)
288
print(f"Certificate covers domains: {domains}")
289
```
290
291
### Certificate Validation
292
293
Validate certificates and certificate chains.
294
295
```python { .api }
296
def valid_privkey(privkey_path: str) -> bool:
297
"""
298
Check if private key is valid and loadable.
299
300
Args:
301
privkey_path: Path to private key file
302
303
Returns:
304
True if private key is valid
305
"""
306
307
def verify_cert_matches_priv_key(cert_path: str, key_path: str) -> bool:
308
"""
309
Verify that certificate matches private key.
310
311
Args:
312
cert_path: Path to certificate file
313
key_path: Path to private key file
314
315
Returns:
316
True if certificate and key match
317
318
Raises:
319
errors.Error: If files cannot be read
320
"""
321
322
def cert_chain_matches(cert_path: str, chain_path: str) -> bool:
323
"""
324
Verify that certificate chain is valid for certificate.
325
326
Args:
327
cert_path: Path to certificate file
328
chain_path: Path to certificate chain file
329
330
Returns:
331
True if chain is valid for certificate
332
"""
333
334
def verify_renewable_cert(renewable_cert: interfaces.RenewableCert) -> None:
335
"""
336
Comprehensive verification of a renewable certificate.
337
338
Checks signature verification, fullchain integrity, and key matching.
339
340
Args:
341
renewable_cert: Certificate to verify
342
343
Raises:
344
errors.Error: If verification fails
345
"""
346
347
def verify_renewable_cert_sig(renewable_cert: interfaces.RenewableCert) -> None:
348
"""
349
Verify the signature of a RenewableCert object.
350
351
Args:
352
renewable_cert: Certificate to verify
353
354
Raises:
355
errors.Error: If signature verification fails
356
"""
357
358
def verify_fullchain(renewable_cert: interfaces.RenewableCert) -> None:
359
"""
360
Verify that fullchain is cert concatenated with chain.
361
362
Args:
363
renewable_cert: Certificate to verify
364
365
Raises:
366
errors.Error: If cert and chain do not combine to fullchain
367
"""
368
369
def get_names_from_cert(cert: bytes, typ: Union[acme_crypto_util.Format, int] = acme_crypto_util.Format.PEM) -> list[str]:
370
"""
371
Get all domain names from a certificate including CN.
372
373
Args:
374
cert: Certificate in encoded format
375
typ: Format of the cert bytes (PEM or DER)
376
377
Returns:
378
List of domain names from certificate
379
"""
380
381
def get_names_from_req(csr: bytes, typ: Union[acme_crypto_util.Format, int] = acme_crypto_util.Format.PEM) -> list[str]:
382
"""
383
Get domain names from a CSR including CN.
384
385
Args:
386
csr: CSR in encoded format
387
typ: Format of the CSR bytes (PEM or DER)
388
389
Returns:
390
List of domain names from CSR
391
"""
392
393
def sha256sum(filename: str) -> str:
394
"""
395
Compute SHA256 hash of a file.
396
397
Args:
398
filename: Path to file to hash
399
400
Returns:
401
SHA256 digest in hexadecimal format
402
"""
403
404
def cert_and_chain_from_fullchain(fullchain_pem: str) -> tuple[str, str]:
405
"""
406
Split fullchain PEM into separate cert and chain PEMs.
407
408
Args:
409
fullchain_pem: Concatenated certificate + chain
410
411
Returns:
412
Tuple of (cert_pem, chain_pem)
413
414
Raises:
415
errors.Error: If less than 2 certificates in chain
416
"""
417
418
def get_serial_from_cert(cert_path: str) -> int:
419
"""
420
Get certificate serial number.
421
422
Args:
423
cert_path: Path to certificate file
424
425
Returns:
426
Certificate serial number
427
"""
428
429
def find_chain_with_issuer(fullchains: list[str], issuer_cn: str,
430
warn_on_no_match: bool = False) -> str:
431
"""
432
Find certificate chain with matching issuer common name.
433
434
Args:
435
fullchains: List of fullchains in PEM format
436
issuer_cn: Exact Subject Common Name to match
437
warn_on_no_match: Whether to warn if no chain matches
438
439
Returns:
440
Best-matching fullchain or first if none match
441
"""
442
```
443
444
Usage examples:
445
446
```python
447
from certbot import crypto_util
448
449
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
450
key_path = '/etc/letsencrypt/live/example.com/privkey.pem'
451
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
452
453
# Validate private key
454
if not crypto_util.valid_privkey(key_path):
455
print("Private key is invalid")
456
457
# Verify certificate matches private key
458
if crypto_util.verify_cert_matches_priv_key(cert_path, key_path):
459
print("Certificate and private key match")
460
461
# Verify certificate chain
462
if crypto_util.cert_chain_matches(cert_path, chain_path):
463
print("Certificate chain is valid")
464
```
465
466
### OCSP Support
467
468
Check certificate revocation status using OCSP.
469
470
```python { .api }
471
class RevocationChecker:
472
"""OCSP revocation checking functionality."""
473
474
def ocsp_revoked(self, cert: RenewableCert) -> bool:
475
"""
476
Get revocation status for a certificate.
477
478
Args:
479
cert: Certificate object to check
480
481
Returns:
482
True if revoked; False if valid or check failed
483
"""
484
485
def ocsp_revoked_by_paths(self, cert_path: str, chain_path: str,
486
timeout: int = 10) -> bool:
487
"""
488
Check revocation status using file paths.
489
490
Args:
491
cert_path: Path to certificate file
492
chain_path: Path to certificate chain file
493
timeout: Timeout in seconds for OCSP query
494
495
Returns:
496
True if revoked; False if valid or check failed
497
"""
498
```
499
500
Usage example:
501
502
```python
503
from certbot import ocsp
504
505
# Create revocation checker
506
checker = ocsp.RevocationChecker()
507
508
# Check revocation by file paths
509
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
510
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
511
512
if checker.ocsp_revoked_by_paths(cert_path, chain_path):
513
print("Certificate has been revoked")
514
else:
515
print("Certificate is valid (not revoked)")
516
```
517
518
## Types
519
520
```python { .api }
521
class Key(NamedTuple):
522
"""Container for private key data."""
523
file: Optional[str] # Path to key file (None if not saved)
524
pem: bytes # PEM-encoded key data
525
526
class CSR(NamedTuple):
527
"""Container for certificate signing request data."""
528
file: Optional[str] # Path to CSR file (None if not saved)
529
data: bytes # CSR data (PEM or DER encoded)
530
form: str # Format of data ("pem" or "der")
531
```