# 10. `example_pkcs12.py` — PKCS#12 archive parsing and creation
[← Example index](index.md) · [example_pkcs12.py on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_pkcs12.py)
Bindings: `load_pkcs12_certificates`, `load_pkcs12_keys`, `load_pkcs12`,
`create_pkcs12`.
- Parse a password-less PKCS#12 file; print subjects of extracted certificates.
- Extract private keys using `load_pkcs12_keys`; show raw PKCS#8 DER length.
- Extract both with `load_pkcs12`; show the `(certs, keys)` tuple.
- Parse an AES-256-CBC encrypted PKCS#12 with a password.
- Show `ValueError` for wrong password (encrypted case, with `openssl` feature).
- Build a PFX from extracted certificates using `create_pkcs12` without a password.
- Build a password-protected PFX with `create_pkcs12` (requires `openssl` feature).
- Build a cert+key PFX bundle.
- Roundtrip: build an archive then parse it back and verify the extracted certificates.
## Source
```python
#!/usr/bin/env python3
"""
Example 9: PKCS#12 archive parsing and creation.
Demonstrates: load_pkcs12_certificates, load_pkcs12_keys, load_pkcs12,
create_pkcs12.
Test vectors:
tests/vectors/test_bundle_nopass.p12 (no password, 1+ certs)
tests/vectors/test_bundle_pass.p12 (password-protected)
tests/vectors/test_bundle_2certs.p12 (2 certs, no password)
tests/vectors/cryptography/…/pkcs12/cert-none-key-none.p12
(1 cert + 1 unencrypted key, no password)
tests/vectors/cryptography/…/pkcs12/cert-key-aes256cbc.p12
(1 cert + 1 AES-256-CBC encrypted key,
password = b"cryptography")
"""
import pathlib
import tempfile
import synta
VECTORS = pathlib.Path(__file__).parent.parent / "tests" / "vectors"
CRYPTO_PKCS12 = (
VECTORS
/ "cryptography"
/ "vectors"
/ "cryptography_vectors"
/ "pkcs12"
)
def section(title):
print(f"\n{'─' * 60}\n{title}\n{'─' * 60}")
def demo_no_password():
section("load_pkcs12_certificates — no password")
path = VECTORS / "test_bundle_nopass.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
certs = synta.load_pkcs12_certificates(data, None)
print(f" Loaded {len(certs)} certificate(s) from no-password archive")
for i, cert in enumerate(certs):
print(f" [{i}] subject={cert.subject} sig_alg={cert.signature_algorithm}")
def demo_two_certs():
section("load_pkcs12_certificates — two certificates, no password")
path = VECTORS / "test_bundle_2certs.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
certs = synta.load_pkcs12_certificates(data, None)
print(f" Loaded {len(certs)} certificate(s)")
for i, cert in enumerate(certs):
print(f" [{i}] subject={cert.subject}")
def demo_with_password():
section("load_pkcs12_certificates — AES-256-CBC encrypted, with password")
path = VECTORS / "test_bundle_pass.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
try:
certs = synta.load_pkcs12_certificates(data, b"synta")
print(f" Loaded {len(certs)} certificate(s) with correct password")
for i, cert in enumerate(certs):
print(f" [{i}] subject={cert.subject}")
except ValueError as e:
# May fail if the openssl feature is not compiled in
print(f" ValueError: {e}")
print(" (Encrypted PKCS#12 requires the 'openssl' feature to be enabled)")
def demo_wrong_password():
section("ValueError for wrong password")
path = VECTORS / "test_bundle_pass.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
try:
synta.load_pkcs12_certificates(data, b"wrong_password")
print(" No error raised (unexpected — perhaps openssl feature not enabled)")
except ValueError as e:
print(f" ValueError (wrong password): {e}")
def demo_keys_unencrypted():
section("load_pkcs12_keys — unencrypted keyBag (cert-none-key-none.p12)")
path = CRYPTO_PKCS12 / "cert-none-key-none.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
keys = synta.load_pkcs12_keys(data)
print(f" Loaded {len(keys)} key(s)")
for i, key_der in enumerate(keys):
# Each blob is a DER-encoded OneAsymmetricKey (PKCS#8) SEQUENCE (tag 0x30)
print(f" [{i}] PKCS#8 DER: {len(key_der)} bytes, tag=0x{key_der[0]:02x}")
def demo_both_unencrypted():
section("load_pkcs12 — cert + unencrypted key in one call")
path = CRYPTO_PKCS12 / "cert-none-key-none.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
certs, keys = synta.load_pkcs12(data)
print(f" Certificates: {len(certs)}, Keys: {len(keys)}")
for i, cert in enumerate(certs):
print(f" cert[{i}] subject={cert.subject}")
for i, key_der in enumerate(keys):
print(f" key[{i}] PKCS#8 DER: {len(key_der)} bytes")
def demo_keys_encrypted():
section("load_pkcs12_keys — AES-256-CBC shrouded keyBag (cert-key-aes256cbc.p12)")
path = CRYPTO_PKCS12 / "cert-key-aes256cbc.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
data = path.read_bytes()
try:
keys = synta.load_pkcs12_keys(data, b"cryptography")
print(f" Loaded {len(keys)} decrypted key(s)")
for i, key_der in enumerate(keys):
print(f" [{i}] PKCS#8 DER: {len(key_der)} bytes, tag=0x{key_der[0]:02x}")
except ValueError as e:
print(f" ValueError: {e}")
print(" (Encrypted keys require the 'openssl' feature to be enabled)")
def demo_create_pkcs12():
section("create_pkcs12 — archive creation")
# ── Build a PFX from certs extracted from an existing archive ────────────
path = VECTORS / "test_bundle_nopass.p12"
if not path.exists():
print(f" Skipped: {path} not found")
return
source_data = path.read_bytes()
certs = synta.load_pkcs12_certificates(source_data, None)
if not certs:
print(" Skipped: no certificates found in source archive")
return
# ── Without a password — Certificate objects passed directly ─────────────
# No .to_der() call needed; create_pkcs12 accepts Certificate or bytes.
pfx_nopass = synta.create_pkcs12(certs)
print(f" No-password PFX: {len(pfx_nopass)} bytes")
# Save to a temporary file to show the API
with tempfile.NamedTemporaryFile(suffix=".p12", delete=False) as tmp:
tmp.write(pfx_nopass)
out_path = pathlib.Path(tmp.name)
print(f" Written to {out_path}")
# ── With a password (requires openssl feature) ────────────────────────────
try:
pfx_pass = synta.create_pkcs12(certs, password=b"example_password")
print(f" Password-protected PFX: {len(pfx_pass)} bytes")
with tempfile.NamedTemporaryFile(suffix=".p12", delete=False) as tmp:
tmp.write(pfx_pass)
out_path_pass = pathlib.Path(tmp.name)
print(f" Written to {out_path_pass}")
except ValueError as e:
print(f" Skipped (password-protected): {e}")
print(" (Encrypted PKCS#12 creation requires the 'openssl' feature)")
# ── With a cert + key — PrivateKey object passed directly ────────────────
key_path = CRYPTO_PKCS12 / "cert-none-key-none.p12"
if key_path.exists():
key_data = key_path.read_bytes()
bundle_certs, bundle_keys = synta.load_pkcs12(key_data)
if bundle_certs and bundle_keys:
# Load as PrivateKey; pass the object directly (no .to_der() needed)
key = synta.PrivateKey.from_der(bundle_keys[0])
try:
pfx_bundle = synta.create_pkcs12(
bundle_certs,
private_key=key,
)
print(f" Cert+key PFX (PrivateKey object): {len(pfx_bundle)} bytes")
# Raw DER bytes are also accepted for both arguments
pfx_bundle_der = synta.create_pkcs12(
[c.to_der() for c in bundle_certs],
private_key=bundle_keys[0],
)
print(f" Cert+key PFX (DER bytes): {len(pfx_bundle_der)} bytes")
except ValueError as e:
print(f" Cert+key bundle skipped: {e}")
else:
print(f" Cert+key demo skipped: {key_path} not found")
# ── Roundtrip: build → parse back → verify ────────────────────────────────
roundtrip_certs = synta.load_pkcs12_certificates(pfx_nopass, None)
print(
f" Roundtrip: built {len(certs)} cert(s), "
f"parsed back {len(roundtrip_certs)} cert(s)"
)
for i, cert in enumerate(roundtrip_certs):
print(f" [{i}] subject={cert.subject} sig_alg={cert.signature_algorithm}")
def main():
print("=" * 60)
print("Example 9: PKCS#12 archive parsing and creation")
print("=" * 60)
demo_no_password()
demo_two_certs()
demo_with_password()
demo_wrong_password()
demo_keys_unencrypted()
demo_both_unencrypted()
demo_keys_encrypted()
demo_create_pkcs12()
print("\nAll PKCS#12 examples completed.")
if __name__ == "__main__":
main()
```