0
# Key Management
1
2
Parsing and handling of cryptographic keys and certificates from various formats including DER, PEM, PKCS#8, and PKCS#12. These functions provide format detection and conversion capabilities for interoperability with different cryptographic systems.
3
4
## Capabilities
5
6
### Certificate Parsing
7
8
Parse X.509 certificates from DER or PEM format data.
9
10
```python { .api }
11
def parse_certificate(data: bytes) -> Certificate:
12
"""
13
Parse an X.509 certificate from DER or PEM data.
14
15
Parameters:
16
- data: bytes - Certificate data in DER or PEM format
17
18
Returns:
19
Certificate object with parsed certificate information
20
21
Raises:
22
ValueError if data is not a valid certificate
23
"""
24
```
25
26
### Private Key Parsing
27
28
Parse private keys from various formats including PKCS#8, PKCS#1, and OpenSSL formats.
29
30
```python { .api }
31
def parse_private(data: bytes) -> PrivateKey:
32
"""
33
Parse a private key from DER or PEM encoded data.
34
35
Parameters:
36
- data: bytes - Private key data in supported formats:
37
- PKCS#8 (encrypted or unencrypted)
38
- PKCS#1 RSA private key
39
- OpenSSL traditional format
40
- SEC1 EC private key
41
42
Returns:
43
PrivateKey object for the parsed key
44
45
Raises:
46
ValueError if data is not a valid private key
47
AsymmetricKeyError if key format is unsupported
48
"""
49
```
50
51
### Public Key Parsing
52
53
Parse public keys from DER or PEM format data.
54
55
```python { .api }
56
def parse_public(data: bytes) -> PublicKey:
57
"""
58
Parse a public key from DER or PEM encoded data.
59
60
Parameters:
61
- data: bytes - Public key data in supported formats:
62
- X.509 SubjectPublicKeyInfo
63
- PKCS#1 RSA public key
64
- RFC 3279 formats
65
66
Returns:
67
PublicKey object for the parsed key
68
69
Raises:
70
ValueError if data is not a valid public key
71
AsymmetricKeyError if key format is unsupported
72
"""
73
```
74
75
### PKCS#12 Bundle Parsing
76
77
Parse PKCS#12 bundles containing certificates, private keys, and certificate chains.
78
79
```python { .api }
80
def parse_pkcs12(data: bytes, password: bytes = None) -> Tuple[Certificate, PrivateKey, List[Certificate]]:
81
"""
82
Parse a PKCS#12 bundle containing certificate and private key.
83
84
Parameters:
85
- data: bytes - PKCS#12 bundle data
86
- password: bytes - Password for encrypted bundle (None for unencrypted)
87
88
Returns:
89
Tuple of (end_entity_certificate, private_key, intermediate_certificates)
90
91
Raises:
92
ValueError if data is not a valid PKCS#12 bundle
93
AsymmetricKeyError if password is incorrect or bundle is corrupted
94
"""
95
```
96
97
## Usage Examples
98
99
### Certificate File Parsing
100
101
```python
102
from oscrypto.keys import parse_certificate
103
import os
104
105
def load_certificate_file(file_path: str):
106
"""Load and parse a certificate file."""
107
with open(file_path, 'rb') as f:
108
cert_data = f.read()
109
110
try:
111
certificate = parse_certificate(cert_data)
112
113
print(f"Certificate loaded from: {file_path}")
114
print(f"Subject: {certificate.subject}")
115
print(f"Issuer: {certificate.issuer}")
116
print(f"Serial Number: {certificate.serial_number}")
117
print(f"Valid From: {certificate.not_valid_before}")
118
print(f"Valid Until: {certificate.not_valid_after}")
119
print(f"Key Algorithm: {certificate.public_key.algorithm}")
120
121
if hasattr(certificate.public_key, 'bit_size'):
122
print(f"Key Size: {certificate.public_key.bit_size} bits")
123
124
return certificate
125
126
except ValueError as e:
127
print(f"Error parsing certificate: {e}")
128
return None
129
130
# Example usage
131
cert_files = [
132
'server.crt',
133
'ca-certificate.pem',
134
'client.der'
135
]
136
137
for cert_file in cert_files:
138
if os.path.exists(cert_file):
139
load_certificate_file(cert_file)
140
print("-" * 50)
141
```
142
143
### Private Key File Parsing
144
145
```python
146
from oscrypto.keys import parse_private
147
from oscrypto.errors import AsymmetricKeyError
148
import getpass
149
150
def load_private_key_file(file_path: str, password: str = None):
151
"""Load and parse a private key file with optional password."""
152
with open(file_path, 'rb') as f:
153
key_data = f.read()
154
155
# Convert password to bytes if provided
156
password_bytes = password.encode('utf-8') if password else None
157
158
try:
159
# First try without password
160
private_key = parse_private(key_data)
161
print(f"Private key loaded from: {file_path} (unencrypted)")
162
163
except (ValueError, AsymmetricKeyError) as e:
164
if "password" in str(e).lower() or "encrypted" in str(e).lower():
165
# Key is encrypted, prompt for password if not provided
166
if not password_bytes:
167
password = getpass.getpass("Enter private key password: ")
168
password_bytes = password.encode('utf-8')
169
170
try:
171
from oscrypto.asymmetric import load_private_key
172
private_key = load_private_key(key_data, password_bytes)
173
print(f"Private key loaded from: {file_path} (encrypted)")
174
except Exception as e2:
175
print(f"Error loading encrypted private key: {e2}")
176
return None
177
else:
178
print(f"Error parsing private key: {e}")
179
return None
180
181
# Display key information
182
print(f"Algorithm: {private_key.algorithm}")
183
if hasattr(private_key, 'bit_size'):
184
print(f"Key Size: {private_key.bit_size} bits")
185
if hasattr(private_key, 'curve'):
186
print(f"Curve: {private_key.curve}")
187
188
return private_key
189
190
# Example usage
191
key_files = [
192
'private_key.pem',
193
'encrypted_key.p8',
194
'rsa_key.der'
195
]
196
197
for key_file in key_files:
198
if os.path.exists(key_file):
199
load_private_key_file(key_file)
200
print("-" * 50)
201
```
202
203
### PKCS#12 Bundle Processing
204
205
```python
206
from oscrypto.keys import parse_pkcs12
207
from oscrypto.errors import AsymmetricKeyError
208
import getpass
209
210
def process_pkcs12_bundle(file_path: str, password: str = None):
211
"""Process a PKCS#12 bundle file."""
212
with open(file_path, 'rb') as f:
213
p12_data = f.read()
214
215
# Prompt for password if not provided
216
if not password:
217
password = getpass.getpass(f"Enter password for {file_path}: ")
218
219
password_bytes = password.encode('utf-8') if password else None
220
221
try:
222
certificate, private_key, intermediates = parse_pkcs12(p12_data, password_bytes)
223
224
print(f"PKCS#12 bundle processed: {file_path}")
225
print(f"End-entity certificate: {certificate.subject}")
226
print(f"Private key algorithm: {private_key.algorithm}")
227
print(f"Intermediate certificates: {len(intermediates)}")
228
229
# Display certificate chain
230
print("\nCertificate Chain:")
231
print(f"1. {certificate.subject} (end-entity)")
232
233
for i, intermediate in enumerate(intermediates, 2):
234
print(f"{i}. {intermediate.subject} (intermediate)")
235
236
# Verify private key matches certificate
237
cert_public_key = certificate.public_key
238
private_public_key = private_key.public_key
239
240
# Simple check - compare key algorithms and sizes
241
keys_match = (cert_public_key.algorithm == private_public_key.algorithm)
242
if hasattr(cert_public_key, 'bit_size') and hasattr(private_public_key, 'bit_size'):
243
keys_match = keys_match and (cert_public_key.bit_size == private_public_key.bit_size)
244
245
print(f"\nPrivate key matches certificate: {keys_match}")
246
247
return certificate, private_key, intermediates
248
249
except AsymmetricKeyError as e:
250
print(f"Error processing PKCS#12 bundle: {e}")
251
return None, None, []
252
253
# Example usage
254
p12_files = [
255
'client.p12',
256
'server.pfx',
257
'identity.p12'
258
]
259
260
for p12_file in p12_files:
261
if os.path.exists(p12_file):
262
process_pkcs12_bundle(p12_file)
263
print("=" * 60)
264
```
265
266
### Multi-Format Key Detection
267
268
```python
269
from oscrypto.keys import parse_certificate, parse_private, parse_public, parse_pkcs12
270
from oscrypto.errors import AsymmetricKeyError
271
272
def identify_crypto_file(file_path: str):
273
"""Identify the type and contents of a cryptographic file."""
274
with open(file_path, 'rb') as f:
275
data = f.read()
276
277
print(f"Analyzing file: {file_path}")
278
print(f"File size: {len(data)} bytes")
279
280
# Check if it's text (PEM) or binary (DER/P12)
281
is_text = all(byte < 128 for byte in data[:100])
282
print(f"Format: {'PEM/Text' if is_text else 'Binary (DER/P12)'}")
283
284
if is_text:
285
# Look for PEM markers
286
data_str = data.decode('utf-8', errors='ignore')
287
pem_types = []
288
289
if '-----BEGIN CERTIFICATE-----' in data_str:
290
pem_types.append('Certificate')
291
if '-----BEGIN PRIVATE KEY-----' in data_str:
292
pem_types.append('PKCS#8 Private Key')
293
if '-----BEGIN RSA PRIVATE KEY-----' in data_str:
294
pem_types.append('PKCS#1 RSA Private Key')
295
if '-----BEGIN EC PRIVATE KEY-----' in data_str:
296
pem_types.append('SEC1 EC Private Key')
297
if '-----BEGIN PUBLIC KEY-----' in data_str:
298
pem_types.append('Public Key')
299
300
if pem_types:
301
print(f"PEM content types: {', '.join(pem_types)}")
302
303
# Try parsing as different types
304
results = {}
305
306
# Try certificate
307
try:
308
cert = parse_certificate(data)
309
results['certificate'] = f"X.509 Certificate - Subject: {cert.subject}"
310
except:
311
pass
312
313
# Try private key
314
try:
315
key = parse_private(data)
316
results['private_key'] = f"Private Key - Algorithm: {key.algorithm}"
317
if hasattr(key, 'bit_size'):
318
results['private_key'] += f", {key.bit_size} bits"
319
except:
320
pass
321
322
# Try public key
323
try:
324
pub_key = parse_public(data)
325
results['public_key'] = f"Public Key - Algorithm: {pub_key.algorithm}"
326
if hasattr(pub_key, 'bit_size'):
327
results['public_key'] += f", {pub_key.bit_size} bits"
328
except:
329
pass
330
331
# Try PKCS#12 (typically requires password, so this might fail)
332
try:
333
cert, key, intermediates = parse_pkcs12(data, None)
334
results['pkcs12'] = f"PKCS#12 Bundle - Cert: {cert.subject}, Key: {key.algorithm}, Intermediates: {len(intermediates)}"
335
except:
336
# Try with empty password
337
try:
338
cert, key, intermediates = parse_pkcs12(data, b'')
339
results['pkcs12'] = f"PKCS#12 Bundle (empty password) - Cert: {cert.subject}, Key: {key.algorithm}, Intermediates: {len(intermediates)}"
340
except:
341
pass
342
343
if results:
344
print("Successful parses:")
345
for parse_type, description in results.items():
346
print(f" {parse_type}: {description}")
347
else:
348
print("Could not parse as any recognized cryptographic format")
349
350
print()
351
352
# Example usage - analyze all crypto files in directory
353
import glob
354
355
crypto_extensions = ['*.pem', '*.crt', '*.cer', '*.der', '*.key', '*.p8', '*.p12', '*.pfx']
356
crypto_files = []
357
358
for extension in crypto_extensions:
359
crypto_files.extend(glob.glob(extension))
360
361
for crypto_file in crypto_files:
362
identify_crypto_file(crypto_file)
363
```
364
365
### Key Conversion Utility
366
367
```python
368
from oscrypto.keys import parse_private, parse_certificate
369
from oscrypto.asymmetric import dump_private_key, dump_certificate, dump_public_key
370
import base64
371
372
def convert_key_format(input_file: str, output_file: str, output_format: str = 'pem'):
373
"""Convert cryptographic files between formats."""
374
375
with open(input_file, 'rb') as f:
376
input_data = f.read()
377
378
try:
379
# Try parsing as private key
380
private_key = parse_private(input_data)
381
382
# Export in requested format
383
if output_format.lower() == 'der':
384
output_data = dump_private_key(private_key)
385
else: # PEM
386
der_data = dump_private_key(private_key)
387
pem_data = base64.b64encode(der_data).decode('ascii')
388
# Add PEM wrapper
389
output_data = (
390
"-----BEGIN PRIVATE KEY-----\n" +
391
'\n'.join(pem_data[i:i+64] for i in range(0, len(pem_data), 64)) +
392
"\n-----END PRIVATE KEY-----\n"
393
).encode('ascii')
394
395
with open(output_file, 'wb') as f:
396
f.write(output_data)
397
398
print(f"Converted private key from {input_file} to {output_file} ({output_format.upper()})")
399
return
400
401
except:
402
pass
403
404
try:
405
# Try parsing as certificate
406
certificate = parse_certificate(input_data)
407
408
# Export in requested format
409
if output_format.lower() == 'der':
410
output_data = dump_certificate(certificate)
411
else: # PEM
412
der_data = dump_certificate(certificate)
413
pem_data = base64.b64encode(der_data).decode('ascii')
414
# Add PEM wrapper
415
output_data = (
416
"-----BEGIN CERTIFICATE-----\n" +
417
'\n'.join(pem_data[i:i+64] for i in range(0, len(pem_data), 64)) +
418
"\n-----END CERTIFICATE-----\n"
419
).encode('ascii')
420
421
with open(output_file, 'wb') as f:
422
f.write(output_data)
423
424
print(f"Converted certificate from {input_file} to {output_file} ({output_format.upper()})")
425
return
426
427
except Exception as e:
428
print(f"Error converting {input_file}: {e}")
429
430
# Example usage
431
conversions = [
432
('server.crt', 'server.der', 'der'),
433
('client.der', 'client.pem', 'pem'),
434
('private.p8', 'private.pem', 'pem')
435
]
436
437
for input_file, output_file, format_type in conversions:
438
if os.path.exists(input_file):
439
convert_key_format(input_file, output_file, format_type)
440
```