synta 0.1.6

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/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()