synta 0.2.3

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
Example 6: X.509 certificate building.

Demonstrates: CertificateBuilder (issuer_name, subject_name, public_key,
public_key_der, serial_number, not_valid_before_utc, not_valid_after_utc,
add_extension, sign, sign_unsigned); NameBuilder; PrivateKey.generate_ec;
PublicKey.to_der; synta.ext.SubjectAlternativeNameBuilder,
synta.ext.ExtendedKeyUsageBuilder, synta.ext.basic_constraints,
synta.ext.key_usage, synta.ext.subject_key_identifier,
synta.ext.authority_key_identifier, synta.ext.KU_* constants.
"""

import datetime
import synta
import synta.ext as ext


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


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


def demo_self_signed_ca():
    section("CertificateBuilder — self-signed CA certificate")
    # Generate an EC P-256 key pair for the CA.
    ca_key = synta.PrivateKey.generate_ec("P-256")
    assert ca_key.key_type == "ec"
    assert ca_key.public_key.curve_name == "P-256"

    # Build the CA distinguished name.
    ca_name = (
        synta.NameBuilder()
        .country("US")
        .organization("Synta")
        .common_name("Test CA")
        .build()
    )
    print(f"  CA name DER: <{len(ca_name)} bytes>")

    # Encode extensions for a CA certificate.
    bc_der = ext.basic_constraints(ca=True, path_length=0)
    ku_der = ext.key_usage(ext.KU_KEY_CERT_SIGN | ext.KU_CRL_SIGN)
    spki_der = ca_key.public_key.to_der()
    ski_der = ext.subject_key_identifier(spki_der)

    # Build and sign the self-signed CA certificate.
    ca_cert = (
        synta.CertificateBuilder()
        .issuer_name(ca_name)
        .subject_name(ca_name)
        .public_key(ca_key.public_key)
        .serial_number(1)
        .not_valid_before_utc(_NOW)
        .not_valid_after_utc(_TWO_YEARS)
        .add_extension("2.5.29.19", True, bc_der)    # basicConstraints critical
        .add_extension("2.5.29.15", True, ku_der)    # keyUsage critical
        .add_extension("2.5.29.14", False, ski_der)  # subjectKeyIdentifier
        .sign(ca_key, "sha256")
    )

    assert isinstance(ca_cert, synta.Certificate)
    print(f"  CA cert issuer:  {ca_cert.issuer}")
    print(f"  CA cert subject: {ca_cert.subject}")
    print(f"  CA cert serial:  {ca_cert.serial_number}")
    print(f"  CA cert DER:     <{len(ca_cert.to_der())} bytes>")

    # Verify DER round-trip
    ca_cert2 = synta.Certificate.from_der(ca_cert.to_der())
    assert ca_cert2.issuer == ca_cert.issuer
    assert ca_cert2.subject == ca_cert.subject
    print("  DER round-trip: OK")

    # Confirm basicConstraints is present and critical
    bc_ext = ca_cert.get_extension_value_der("2.5.29.19")
    assert bc_ext is not None
    print(f"  basicConstraints value: {bc_ext.hex()}")

    return ca_cert, ca_key


def demo_leaf_certificate(ca_cert, ca_key):
    section("CertificateBuilder — leaf certificate signed by CA")
    # Generate a fresh EC P-256 key for the leaf.
    leaf_key = synta.PrivateKey.generate_ec("P-256")

    # Build the leaf subject name.
    leaf_name = (
        synta.NameBuilder()
        .common_name("leaf.example.com")
        .build()
    )

    # Build extensions for the leaf certificate.
    san_der = (
        ext.SubjectAlternativeNameBuilder()
        .dns_name("leaf.example.com")
        .dns_name("www.leaf.example.com")
        .build()
    )

    eku_der = (
        ext.ExtendedKeyUsageBuilder()
        .server_auth()
        .build()
    )

    bc_ee = ext.basic_constraints(ca=False)
    ku_ee = ext.key_usage(ext.KU_DIGITAL_SIGNATURE | ext.KU_KEY_ENCIPHERMENT)
    spki_der = leaf_key.public_key.to_der()
    ski_der = ext.subject_key_identifier(spki_der)
    # Authority key identifier derived from the CA's public key
    ca_spki_der = ca_key.public_key.to_der()
    aki_der = ext.authority_key_identifier(ca_spki_der)

    # Use the CA cert's subject_raw_der as the leaf's issuer.
    leaf_cert = (
        synta.CertificateBuilder()
        .issuer_name(ca_cert.subject_raw_der)
        .subject_name(leaf_name)
        .public_key(leaf_key.public_key)
        .serial_number(2)
        .not_valid_before_utc(_NOW)
        .not_valid_after_utc(_ONE_YEAR)
        .add_extension("2.5.29.17", False, san_der)  # subjectAltName
        .add_extension("2.5.29.37", False, eku_der)  # extendedKeyUsage
        .add_extension("2.5.29.19", False, bc_ee)    # basicConstraints
        .add_extension("2.5.29.15", True, ku_ee)     # keyUsage critical
        .add_extension("2.5.29.14", False, ski_der)  # subjectKeyIdentifier
        .add_extension("2.5.29.35", False, aki_der)  # authorityKeyIdentifier
        .sign(ca_key, "sha256")
    )

    assert isinstance(leaf_cert, synta.Certificate)
    print(f"  Leaf cert issuer:  {leaf_cert.issuer}")
    print(f"  Leaf cert subject: {leaf_cert.subject}")
    print(f"  Leaf cert serial:  {leaf_cert.serial_number}")
    print(f"  Leaf cert DER:     <{len(leaf_cert.to_der())} bytes>")
    print(f"  signature_algorithm: {leaf_cert.signature_algorithm}")

    # Check the SAN extension is present
    san_ext = leaf_cert.get_extension_value_der("2.5.29.17")
    assert san_ext is not None
    print(f"  subjectAltName value: <{len(san_ext)} bytes>  OK")

    # Check the EKU extension is present
    eku_ext = leaf_cert.get_extension_value_der("2.5.29.37")
    assert eku_ext is not None
    print(f"  extendedKeyUsage value: <{len(eku_ext)} bytes>  OK")

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

    return leaf_cert


def demo_public_key_der_setter():
    section("CertificateBuilder.public_key_der — use pre-encoded SPKI bytes")
    key = synta.PrivateKey.generate_ec("P-256")
    spki_der = key.public_key.to_der()  # raw SubjectPublicKeyInfo DER

    name_der = synta.NameBuilder().common_name("SPKI DER Test").build()
    bc_der = ext.basic_constraints()

    cert = (
        synta.CertificateBuilder()
        .issuer_name(name_der)
        .subject_name(name_der)
        .public_key_der(spki_der)          # pre-encoded SPKI
        .serial_number(99)
        .not_valid_before_utc(_NOW)
        .not_valid_after_utc(_ONE_YEAR)
        .add_extension("2.5.29.19", False, bc_der)
        .sign(key, "sha256")
    )

    assert isinstance(cert, synta.Certificate)
    assert cert.serial_number == 99
    # The public key DER stored in the cert must match what we passed
    assert cert.subject_public_key_info_der == spki_der
    print(f"  subject_public_key_info_der matches input SPKI: OK")
    print(f"  cert DER: <{len(cert.to_der())} bytes>")


def demo_sign_unsigned():
    section("CertificateBuilder.sign_unsigned — RFC 9925 unsigned certificate")
    key = synta.PrivateKey.generate_ec("P-256")
    name_der = synta.NameBuilder().common_name("Unsigned Root").build()
    bc_der = ext.basic_constraints(ca=True, path_length=0)
    ku_der = ext.key_usage(ext.KU_KEY_CERT_SIGN | ext.KU_CRL_SIGN)
    spki_der = key.public_key.to_der()
    ski_der = ext.subject_key_identifier(spki_der)

    unsigned_cert = (
        synta.CertificateBuilder()
        .issuer_name(name_der)
        .subject_name(name_der)
        .public_key(key.public_key)
        .serial_number(1)
        .not_valid_before_utc(_NOW)
        .not_valid_after_utc(_TWO_YEARS)
        .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_unsigned()
    )

    assert isinstance(unsigned_cert, synta.Certificate)
    print(f"  unsigned cert issuer:  {unsigned_cert.issuer}")
    print(f"  signature_algorithm:   {unsigned_cert.signature_algorithm}")
    # RFC 9925: id-alg-unsigned = 1.3.6.1.5.5.7.6.36
    assert str(unsigned_cert.signature_algorithm_oid) == "1.3.6.1.5.5.7.6.36", (
        f"expected id-alg-unsigned OID, got {unsigned_cert.signature_algorithm_oid}"
    )
    print(f"  id-alg-unsigned OID:   {unsigned_cert.signature_algorithm_oid}  OK")
    # Signature value is a zero-length BIT STRING
    assert len(unsigned_cert.signature_value) == 0
    print(f"  signature_value length: {len(unsigned_cert.signature_value)}  (zero — OK)")

    # DER round-trip
    unsigned_cert2 = synta.Certificate.from_der(unsigned_cert.to_der())
    assert unsigned_cert2.subject == unsigned_cert.subject
    print("  DER round-trip: OK")


def main():
    print("=" * 60)
    print("Example 31: X.509 certificate building")
    print("=" * 60)
    ca_cert, ca_key = demo_self_signed_ca()
    demo_leaf_certificate(ca_cert, ca_key)
    demo_public_key_der_setter()
    demo_sign_unsigned()
    print("\nAll certificate builder examples completed.")


if __name__ == "__main__":
    main()