synta 0.2.4

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# 6. `example_certificate_builder.py` — X.509 certificate building

[← Example index](index.md) · [example_certificate_builder.py on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_certificate_builder.py)

Bindings: `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_*` bitmask constants.

- Generate a self-signed CA certificate with `PrivateKey.generate_ec("P-256")`,
  `NameBuilder`, and `CertificateBuilder` (basicConstraints CA:TRUE, keyUsage
  keyCertSign+cRLSign, subjectKeyIdentifier); verify DER round-trip.
- Issue a leaf certificate signed by the CA key with `issuer_name` from
  `ca_cert.subject_raw_der`, SAN from `SubjectAlternativeNameBuilder`, EKU
  from `ExtendedKeyUsageBuilder` (serverAuth), basicConstraints CA:FALSE,
  authorityKeyIdentifier, and keyUsage digitalSignature+keyEncipherment.
- Demonstrate `public_key_der()` setter: pass pre-encoded SPKI DER bytes directly
  (zero re-encoding); verify `cert.subject_public_key_info_der` round-trip.
- Show `sign_unsigned()` (RFC 9925): confirm `signature_algorithm_oid` is
  `id-alg-unsigned` (1.3.6.1.5.5.7.6.36) and `signature_value` is empty.

## Source

```python
#!/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()
```