synta 0.3.3

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
Example: Composite ML-KEM key encapsulation and certificate embedding.

Demonstrates composite ML-KEM (draft-ietf-lamps-pq-composite-kem-18):
- PrivateKey.generate_composite_kem(sub_arc) for each of the 12 variants
- PublicKey.composite_kem_encapsulate() / PrivateKey.composite_kem_decapsulate()
- PKCS#8 serialisation round-trip (to_der / from_der)
- Embedding a composite ML-KEM public key as a certificate's SubjectPublicKeyInfo
- synta.oids composite ML-KEM OID constants

Composite ML-KEM combines an ML-KEM component with a traditional KEM
(RSA-OAEP, ECDH, X25519, or X448). Encapsulation runs both component KEMs
and combines their shared secrets with SHA3-256; the composite ciphertext
is the concatenation of both component ciphertexts.

OID arc: 1.3.6.1.5.5.7.6.55 through .66  (12 variants).

Run:
    PYTHONPATH=python python3 examples/example_composite_mlkem.py
"""

import datetime
import synta
import synta.oids as oids
import synta.ext as ext


_UTC = datetime.timezone.utc
_NOW = datetime.datetime(2026, 1, 1, tzinfo=_UTC)
_ONE_YEAR = datetime.datetime(2027, 1, 1, tzinfo=_UTC)


def section(title):
    print(f"\n{'' * 70}\n{title}\n{'' * 70}")


# ── Mapping: sub_arc → (OID constant, display name) ──────────────────────────

COMPOSITE_KEM_VARIANTS = [
    (55, oids.MLKEM768_RSA2048_SHA3_256,               "MLKEM768-RSA2048-SHA3-256"),
    (56, oids.MLKEM768_RSA3072_SHA3_256,               "MLKEM768-RSA3072-SHA3-256"),
    (57, oids.MLKEM768_RSA4096_SHA3_256,               "MLKEM768-RSA4096-SHA3-256"),
    (58, oids.MLKEM768_X25519_SHA3_256,                "MLKEM768-X25519-SHA3-256"),
    (59, oids.MLKEM768_ECDH_P256_SHA3_256,             "MLKEM768-ECDH-P256-SHA3-256"),
    (60, oids.MLKEM768_ECDH_P384_SHA3_256,             "MLKEM768-ECDH-P384-SHA3-256"),
    (61, oids.MLKEM768_ECDH_BRAINPOOL_P256R1_SHA3_256, "MLKEM768-ECDH-brainpoolP256r1-SHA3-256"),
    (62, oids.MLKEM1024_RSA3072_SHA3_256,              "MLKEM1024-RSA3072-SHA3-256"),
    (63, oids.MLKEM1024_ECDH_P384_SHA3_256,            "MLKEM1024-ECDH-P384-SHA3-256"),
    (64, oids.MLKEM1024_ECDH_BRAINPOOL_P384R1_SHA3_256, "MLKEM1024-ECDH-brainpoolP384r1-SHA3-256"),
    (65, oids.MLKEM1024_X448_SHA3_256,                 "MLKEM1024-X448-SHA3-256"),
    (66, oids.MLKEM1024_ECDH_P521_SHA3_256,            "MLKEM1024-ECDH-P521-SHA3-256"),
]


def demo_oid_constants():
    section("synta.oids composite ML-KEM OID constants")
    arc = oids.COMPOSITE_KEM_ARC
    print(f"  COMPOSITE_KEM_ARC = {arc}")
    assert str(arc) == "1.3.6.1.5.5.7.6", f"unexpected arc: {arc}"

    for sub_arc, oid, name in COMPOSITE_KEM_VARIANTS:
        expected = f"1.3.6.1.5.5.7.6.{sub_arc}"
        assert str(oid) == expected, f"{name}: expected {expected}, got {oid}"
        print(f"  sub_arc {sub_arc:2d}: {oid}  ({name})")

    print("\n  All 12 composite ML-KEM OID constants verified.")


def demo_key_generation_and_kem(sub_arc: int, name: str):
    """Generate a composite ML-KEM key, encapsulate, decapsulate, round-trip verify."""
    print(f"\n  [{sub_arc}] {name}")

    # Generate composite ML-KEM key.
    try:
        priv = synta.PrivateKey.generate_composite_kem(sub_arc)
    except ValueError as e:
        print(f"      SKIP (not supported in this build: {e})")
        return

    # PKCS#8 DER round-trip.
    pkcs8_der = priv.to_der()
    priv2 = synta.PrivateKey.from_der(pkcs8_der)
    assert priv2.to_der() == pkcs8_der, "PKCS#8 round-trip mismatch"

    # Encapsulate against the public key, decapsulate with the private key.
    pub = priv.public_key
    ct, ss_encap = pub.composite_kem_encapsulate()
    ss_decap = priv.composite_kem_decapsulate(ct)

    assert len(ss_encap) == 32, f"shared secret must be 32 bytes, got {len(ss_encap)}"
    assert ss_encap == ss_decap, "encaps/decaps must agree on the shared secret"

    print(f"      PKCS#8 DER:     {len(pkcs8_der):6d} bytes")
    print(f"      SPKI DER:       {len(pub.to_der()):6d} bytes")
    print(f"      ciphertext:     {len(ct):6d} bytes")
    print(f"      shared secret:  {ss_encap.hex()}")
    print(f"      decaps matches: OK")


def demo_all_variants():
    section("Composite ML-KEM encapsulation/decapsulation — all 12 variants")
    for sub_arc, _oid, name in COMPOSITE_KEM_VARIANTS:
        demo_key_generation_and_kem(sub_arc, name)


def demo_focused_round_trip():
    """Detailed walk-through of one composite variant (MLKEM768-ECDH-P256-SHA3-256)."""
    section("Detailed round-trip: MLKEM768-ECDH-P256-SHA3-256 (sub_arc=59)")

    sub_arc = 59
    try:
        kem_key = synta.PrivateKey.generate_composite_kem(sub_arc)
    except ValueError as e:
        print(f"  SKIP (not supported in this build: {e})")
        return

    # ── Key properties ────────────────────────────────────────────────────────
    print(f"  Key generated.  PKCS#8 DER length: {len(kem_key.to_der())} bytes")
    print(f"  Public key SPKI length:             {len(kem_key.public_key.to_der())} bytes")

    # ── PKCS#8 serialisation round-trip ───────────────────────────────────────
    pkcs8_der = kem_key.to_der()
    kem_key2 = synta.PrivateKey.from_der(pkcs8_der)
    assert kem_key2.to_der() == pkcs8_der
    print("  PKCS#8 DER round-trip: OK")

    # ── Encapsulation / decapsulation ─────────────────────────────────────────
    ct, ss_encap = kem_key.public_key.composite_kem_encapsulate()
    ss_decap = kem_key.composite_kem_decapsulate(ct)
    assert ss_encap == ss_decap
    print(f"  Ciphertext length:       {len(ct)} bytes")
    print(f"  Shared secret (32B):     {ss_encap.hex()}")
    print("  Encaps/decaps shared secret match: OK")

    # Independently generated keys must not share a secret.
    other_key = synta.PrivateKey.generate_composite_kem(sub_arc)
    _, ss_other = other_key.public_key.composite_kem_encapsulate()
    assert ss_other != ss_encap
    print("  Independent keypairs produce different secrets: OK")

    # ── Embed the composite ML-KEM public key in a certificate's SPKI ─────────
    # A KEM key cannot sign, so a separate classical key signs the certificate;
    # the certificate's *subject* public key is the composite ML-KEM key.
    ca_key = synta.PrivateKey.generate_ec("P-256")
    ca_name = synta.NameBuilder().country("DE").organization("Synta PQC").common_name("KEM Issuer CA").build()
    leaf_name = synta.NameBuilder().common_name("kem-leaf.example.com").build()

    bc_der = ext.basic_constraints(ca=False)
    ku_der = ext.key_usage(ext.KU_KEY_ENCIPHERMENT)
    ski_der = ext.subject_key_identifier(kem_key.public_key.to_der())

    leaf_cert = (
        synta.CertificateBuilder()
        .issuer_name(ca_name)
        .subject_name(leaf_name)
        .public_key(kem_key.public_key)
        .serial_number(sub_arc)
        .not_valid_before_utc(_NOW)
        .not_valid_after_utc(_ONE_YEAR)
        .add_extension("2.5.29.19", True, bc_der)
        .add_extension("2.5.29.15", True, ku_der)
        .add_extension("2.5.29.14", False, ski_der)
        .sign(ca_key, "sha256")
    )

    print(f"  Leaf cert subject:            {leaf_cert.subject}")
    print(f"  Leaf cert public key alg:     {leaf_cert.public_key_algorithm}")
    print(f"  Leaf cert public key alg OID: {leaf_cert.public_key_algorithm_oid}")

    # Verify identify_public_key_algorithm() recognizes the composite ML-KEM OID.
    expected_oid = f"1.3.6.1.5.5.7.6.{sub_arc}"
    assert str(leaf_cert.public_key_algorithm_oid) == expected_oid
    assert leaf_cert.public_key_algorithm == "MLKEM768-ECDH-P256-SHA3-256"
    print("  Composite ML-KEM public-key algorithm recognized: OK")

    # ── Certificate DER round-trip ────────────────────────────────────────────
    leaf_cert2 = synta.Certificate.from_der(leaf_cert.to_der())
    assert leaf_cert2.subject == leaf_cert.subject
    assert leaf_cert2.public_key == leaf_cert.public_key
    print("  Certificate DER round-trip: OK")


def main():
    print("=" * 70)
    print("Composite ML-KEM example")
    print("draft-ietf-lamps-pq-composite-kem-18")
    print("=" * 70)

    demo_oid_constants()
    demo_focused_round_trip()
    demo_all_variants()

    print("\nAll composite ML-KEM examples completed.")


if __name__ == "__main__":
    main()