synta 0.1.10

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
Example: X.500 Name / DN utility functions.

Demonstrates:
  - synta.format_dn()
  - synta.format_dn_slash()
  - synta.parse_name_attrs()
  - synta.find_extension_value()
  - synta.encode_general_names()
  - synta.signing_algorithm_der()
  - synta.key_usage_bit()
  - synta.decode_public_key_info()
"""

import datetime

import synta
import synta.ext as ext
import synta.general_name as gn
import synta.oids as oids


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


def demo_format_dn():
    section("format_dn / format_dn_slash")

    name_der = (
        synta.NameBuilder()
        .country("US")
        .organization("Example Corp")
        .common_name("www.example.com")
        .build()
    )

    comma_form = synta.format_dn(name_der)
    slash_form = synta.format_dn_slash(name_der)

    print(f"  comma form : {comma_form}")
    print(f"  slash form : {slash_form}")

    # Same output is available directly from a parsed certificate's subject/issuer.
    key = synta.PrivateKey.generate_ec("P-256")
    now = datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc)
    later = datetime.datetime(2027, 1, 1, tzinfo=datetime.timezone.utc)
    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(later)
        .sign(key, "sha256")
    )
    print(f"  from cert  : {synta.format_dn(cert.subject_raw_der)}")


def demo_parse_name_attrs():
    section("parse_name_attrs")

    name_der = (
        synta.NameBuilder()
        .country("DE")
        .organization("Bundesministerium")
        .common_name("pki.example.de")
        .build()
    )

    for oid_str, value in synta.parse_name_attrs(name_der):
        print(f"  {oid_str} = {value!r}")


def demo_find_extension_value():
    section("find_extension_value")

    key = synta.PrivateKey.generate_ec("P-256")
    name_der = synta.NameBuilder().common_name("Extension Demo CA").build()
    now = datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc)
    later = datetime.datetime(2027, 1, 1, tzinfo=datetime.timezone.utc)
    bc_der = ext.basic_constraints(ca=True, path_length=1)
    ku_der = ext.key_usage(ext.KU_KEY_CERT_SIGN | ext.KU_CRL_SIGN)
    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(later)
        .add_extension(str(oids.BASIC_CONSTRAINTS), True, bc_der)
        .add_extension(str(oids.KEY_USAGE), True, ku_der)
        .sign(key, "sha256")
    )

    ext_seq = cert.extensions_der
    if ext_seq:
        bc_found = synta.find_extension_value(ext_seq, "2.5.29.19")
        print(f"  BasicConstraints  : {bc_found!r}")
        ku_found = synta.find_extension_value(ext_seq, "2.5.29.15")
        print(f"  KeyUsage          : {ku_found!r}")
        absent = synta.find_extension_value(ext_seq, "1.3.6.1.5.5.7.1.11")
        print(f"  SubjectInfoAccess : {absent!r}  (not present → None)")


def demo_encode_general_names():
    section("encode_general_names")

    entries = [
        (gn.DNS_NAME, b"example.com"),
        (gn.DNS_NAME, b"www.example.com"),
        (gn.IP_ADDRESS, bytes([192, 168, 0, 1])),
        (gn.RFC822_NAME, b"admin@example.com"),
    ]

    encoded = synta.encode_general_names(entries)
    print(f"  Encoded {len(entries)} GeneralName entries → {len(encoded)} bytes")

    # Round-trip via parse_general_names
    parsed = synta.parse_general_names(encoded)
    for tag, content in parsed:
        print(f"  tag={tag}  content={content}")


def demo_signing_algorithm_der():
    section("signing_algorithm_der")

    combos = [
        ("1.2.840.113549.1.1.1", "sha256", "RSA + SHA-256"),
        ("1.2.840.113549.1.1.1", "sha384", "RSA + SHA-384"),
        ("1.2.840.10045.2.1",    "sha256", "EC  + SHA-256"),
        ("1.3.101.112",          "sha512", "Ed25519 (hash ignored)"),
        ("1.2.3.4.5.6",          "sha256", "unknown key OID"),
    ]
    for key_oid, hash_alg, label in combos:
        result = synta.signing_algorithm_der(key_oid, hash_alg)
        if result is None:
            print(f"  {label:30s} → None")
        else:
            print(f"  {label:30s}{len(result)} bytes  tag=0x{result[0]:02x}")


def demo_key_usage_bit():
    section("key_usage_bit")

    key = synta.PrivateKey.generate_ec("P-256")
    name_der = synta.NameBuilder().common_name("KU Demo CA").build()
    now = datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc)
    later = datetime.datetime(2027, 1, 1, tzinfo=datetime.timezone.utc)
    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(later)
        .add_extension(str(oids.KEY_USAGE), True,
                       ext.key_usage(ext.KU_KEY_CERT_SIGN | ext.KU_CRL_SIGN))
        .sign(key, "sha256")
    )
    ku_der = cert.get_extension_value_der("2.5.29.15")
    assert ku_der is not None

    bit_names = [
        "digitalSignature", "nonRepudiation", "keyEncipherment",
        "dataEncipherment", "keyAgreement",   "keyCertSign",
        "cRLSign",          "encipherOnly",   "decipherOnly",
    ]
    for i, name in enumerate(bit_names):
        val = synta.key_usage_bit(ku_der, i)
        print(f"  bit {i} ({name:20s}): {val}")


def demo_decode_public_key_info():
    section("decode_public_key_info")

    for curve, label in [("P-256", "EC P-256"), ("P-384", "EC P-384")]:
        key = synta.PrivateKey.generate_ec(curve)
        info = synta.decode_public_key_info(key.public_key.to_der())
        print(f"  {label}:")
        for k, v in info.items():
            display = v if not isinstance(v, bytes) else f"<{len(v)} bytes>"
            print(f"    {k}: {display}")

    key_ed = synta.PrivateKey.generate_ed25519()
    info_ed = synta.decode_public_key_info(key_ed.public_key.to_der())
    print("  Ed25519:")
    for k, v in info_ed.items():
        display = v if not isinstance(v, bytes) else f"<{len(v)} bytes>"
        print(f"    {k}: {display}")


def main():
    print("=" * 60)
    print("Name / DN utility functions")
    print("=" * 60)
    demo_format_dn()
    demo_parse_name_attrs()
    demo_find_extension_value()
    demo_encode_general_names()
    demo_signing_algorithm_der()
    demo_key_usage_bit()
    demo_decode_public_key_info()
    print("\nAll examples completed.")


if __name__ == "__main__":
    main()