synta 0.2.5

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# 19. `example_krb5_principal.py` — Kerberos principal names

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

Bindings: `synta.krb5.Krb5PrincipalName`, `synta.krb5.KRB5_PRINCIPAL_NAME_OID`,
and every `NT_*` constant.

- Construct `Krb5PrincipalName` instances for each name type (`NT_PRINCIPAL`,
  `NT_SRV_INST`, `NT_SRV_HST`, `NT_ENTERPRISE`, `NT_WELLKNOWN`).
- Encode each with `to_der()` and decode back with `from_der()`.
- Verify realm, name_type, and components survive the round-trip.
- Show `__eq__` and `__repr__`.
- Print `KRB5_PRINCIPAL_NAME_OID` and demonstrate it matches `synta.oids.PKINIT_SAN`.
- Show `ValueError` for non-ASCII realm or component.

## Source

```python
#!/usr/bin/env python3
"""
Example 18: Kerberos principal names.

Demonstrates: synta.krb5.Krb5PrincipalName, synta.krb5.KRB5_PRINCIPAL_NAME_OID,
and every NT_* constant.
"""

import synta
import synta.krb5 as krb5
import synta.oids as oids


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


def demo_nt_constants():
    section("NT_* name-type constants (RFC 4120 §6.2)")
    nt_constants = [
        ("NT_UNKNOWN",         krb5.NT_UNKNOWN,         0),
        ("NT_PRINCIPAL",       krb5.NT_PRINCIPAL,       1),
        ("NT_SRV_INST",        krb5.NT_SRV_INST,        2),
        ("NT_SRV_HST",         krb5.NT_SRV_HST,         3),
        ("NT_SRV_XHST",        krb5.NT_SRV_XHST,        4),
        ("NT_UID",             krb5.NT_UID,              5),
        ("NT_X500_PRINCIPAL",  krb5.NT_X500_PRINCIPAL,  6),
        ("NT_SMTP_NAME",       krb5.NT_SMTP_NAME,        7),
        ("NT_ENTERPRISE",      krb5.NT_ENTERPRISE,      10),
        ("NT_WELLKNOWN",       krb5.NT_WELLKNOWN,       11),
        ("NT_SRV_HST_DOMAIN",  krb5.NT_SRV_HST_DOMAIN, 12),
    ]
    for name, val, expected in nt_constants:
        assert val == expected, f"{name}: expected {expected}, got {val}"
        print(f"  {name:20} = {val}")


def demo_krb5_principal_name_oid():
    section("KRB5_PRINCIPAL_NAME_OID — id-pkinit-san (1.3.6.1.5.2.2)")
    oid = krb5.KRB5_PRINCIPAL_NAME_OID
    print(f"  KRB5_PRINCIPAL_NAME_OID = {oid}")
    assert str(oid) == "1.3.6.1.5.2.2"
    assert list(oid.components()) == [1, 3, 6, 1, 5, 2, 2]

    # Must equal synta.oids.PKINIT_SAN
    assert oid == oids.PKINIT_SAN
    print(f"  == synta.oids.PKINIT_SAN: OK")


def demo_construct_all_name_types():
    section("Construct Krb5PrincipalName for each NT_* type")
    principals = [
        ("NT_PRINCIPAL",      krb5.NT_PRINCIPAL,      "EXAMPLE.COM", ["alice"]),
        ("NT_SRV_INST",       krb5.NT_SRV_INST,       "EXAMPLE.COM", ["krbtgt", "EXAMPLE.COM"]),
        ("NT_SRV_HST",        krb5.NT_SRV_HST,        "EXAMPLE.COM", ["host", "server.example.com"]),
        ("NT_ENTERPRISE",     krb5.NT_ENTERPRISE,     "EXAMPLE.COM", ["user@sub.example.com"]),
        ("NT_WELLKNOWN",      krb5.NT_WELLKNOWN,      "WELLKNOWN:ANONYMOUS",  ["ANONYMOUS"]),
    ]
    for label, name_type, realm, components in principals:
        p = krb5.Krb5PrincipalName(realm, name_type, components)
        assert p.realm == realm
        assert p.name_type == name_type
        assert list(p.components) == components
        print(f"  {label:16} → {repr(p)}")


def demo_roundtrip():
    section("Encode to DER and decode back — all properties preserved")
    p = krb5.Krb5PrincipalName("EXAMPLE.COM", krb5.NT_SRV_INST, ["krbtgt", "EXAMPLE.COM"])
    der = p.to_der()
    assert isinstance(der, bytes)
    print(f"  DER: {der.hex()}")

    q = krb5.Krb5PrincipalName.from_der(der)
    assert q.realm == p.realm
    assert q.name_type == p.name_type
    assert list(q.components) == list(p.components)
    print(f"  realm:      {q.realm!r}")
    print(f"  name_type:  {q.name_type}  (NT_SRV_INST)")
    print(f"  components: {list(q.components)}")
    print("  Round-trip: OK")


def demo_eq_and_repr():
    section("__eq__ and __repr__")
    a = krb5.Krb5PrincipalName("REALM", krb5.NT_PRINCIPAL, ["bob"])
    b = krb5.Krb5PrincipalName("REALM", krb5.NT_PRINCIPAL, ["bob"])
    c = krb5.Krb5PrincipalName("REALM", krb5.NT_PRINCIPAL, ["carol"])

    assert a == b
    assert not (a == c)
    print(f"  a == b: True  (same principal)")
    print(f"  a == c: False (different components)")

    r = repr(a)
    assert "Krb5PrincipalName" in r
    assert "REALM" in r
    assert "bob" in r
    print(f"  repr: {r}")


def demo_validation_errors():
    section("ValueError for non-ASCII realm or component")
    try:
        krb5.Krb5PrincipalName("RÉALM", krb5.NT_PRINCIPAL, ["alice"])
        print("  No error (unexpected)")
    except ValueError as e:
        print(f"  ValueError (non-ASCII realm): {e}")

    try:
        krb5.Krb5PrincipalName("REALM", krb5.NT_PRINCIPAL, ["alïce"])
        print("  No error (unexpected)")
    except ValueError as e:
        print(f"  ValueError (non-ASCII component): {e}")


def main():
    print("=" * 60)
    print("Example 18: Kerberos principal names")
    print("=" * 60)
    demo_nt_constants()
    demo_krb5_principal_name_oid()
    demo_construct_all_name_types()
    demo_roundtrip()
    demo_eq_and_repr()
    demo_validation_errors()
    print("\nAll Krb5PrincipalName examples completed.")


if __name__ == "__main__":
    main()
```