synta 0.2.6

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
Example: RFC 3279 algorithm parameter types (synta.pkixalgs).

Demonstrates ``DssParms``, ``DssSigValue``, ``EcdsaSigValue``, and
``ECParameters``, plus the OID constants for DSA, DH, EC, and named-curve
algorithms.

All DER inputs are constructed in-process using the synta encoder so that
no external files are required.
"""

import synta
import synta.pkixalgs as pa


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


# ── DER building helpers ──────────────────────────────────────────────────────

def _enc(fn):
    """Encode one element and return its DER bytes."""
    enc = synta.Encoder(synta.Encoding.DER)
    fn(enc)
    return enc.finish()


def _seq(*parts):
    """Wrap raw TLV bytes in a SEQUENCE."""
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_sequence(b"".join(parts))
    return enc.finish()


def _int_bytes(b: bytes) -> bytes:
    """DER INTEGER TLV from big-endian unsigned bytes.

    Prepends a 0x00 padding byte when the most-significant bit is set
    (to keep the encoding non-negative in two's-complement).
    Assumes short-form length (< 128 bytes of content).
    """
    if b[0] & 0x80:
        b = b"\x00" + b
    return bytes([0x02, len(b)]) + b


def _oid(dotted: str) -> bytes:
    """DER OID TLV from a dotted-decimal string."""
    return _enc(lambda e: e.encode_oid(synta.ObjectIdentifier(dotted)))


# ── Pre-built DER ─────────────────────────────────────────────────────────────

# DSA domain parameters: small illustrative p, q, g values.
# All have a clear MSB so _int_bytes() does not prepend 0x00; the bytes
# returned by .p / .q / .g will equal these exact values.
_P = b"\x73\xd0" + b"\xff" * 13      # 15 bytes  (simulated prime modulus)
_Q = b"\x25\xf1" * 9 + b"\x25"      # 19 bytes  (simulated subgroup order)
_G = b"\x01\xaa" * 7 + b"\x01"      # 15 bytes  (simulated generator)

DSS_PARMS_DER = _seq(_int_bytes(_P), _int_bytes(_Q), _int_bytes(_G))

# DSA signature: illustrative r and s (31 bytes each, MSB clear)
_R_DSA = b"\x41" + b"\xbb" * 30
_S_DSA = b"\x42" + b"\xcc" * 30

DSS_SIG_DER = _seq(_int_bytes(_R_DSA), _int_bytes(_S_DSA))

# ECDSA signature: illustrative r and s (32 bytes each, MSB clear)
_R_ECDSA = b"\x41" + b"\xbb" * 31
_S_ECDSA = b"\x42" + b"\xcc" * 31

ECDSA_SIG_DER = _seq(_int_bytes(_R_ECDSA), _int_bytes(_S_ECDSA))

# ECParameters namedCurve: the curve OID is the entire encoding.
# ECParameters is a CHOICE { ecParameters SEQUENCE, namedCurve OID, implicitlyCA NULL }.
# The DER tag of each alternative identifies the arm — no context tags are used.
PRIME256V1_DOTTED = "1.2.840.10045.3.1.7"
EC_PARAMS_NAMED_CURVE_DER = _oid(PRIME256V1_DOTTED)  # bare OID TLV


# ── Demo functions ─────────────────────────────────────────────────────────────

def demo_dss_parms():
    section("DssParms — DSA domain parameters (RFC 3279 §2.3.2)")

    parms = pa.DssParms.from_der(DSS_PARMS_DER)

    # .p / .q / .g return the big-endian two's-complement bytes of each INTEGER
    assert parms.p == _P, f"p mismatch: {parms.p.hex()}"
    assert parms.q == _Q, f"q mismatch: {parms.q.hex()}"
    assert parms.g == _G, f"g mismatch: {parms.g.hex()}"

    print(f"  p ({len(parms.p)} bytes): {parms.p.hex()[:20]}")
    print(f"  q ({len(parms.q)} bytes): {parms.q.hex()[:20]}")
    print(f"  g ({len(parms.g)} bytes): {parms.g.hex()[:20]}")
    print(f"  repr: {repr(parms)}")

    # Round-trip: re-encode and parse again
    der2 = parms.to_der()
    parms2 = pa.DssParms.from_der(der2)
    assert parms2.p == parms.p
    assert parms2.q == parms.q
    assert parms2.g == parms.g
    print("  round-trip: ✓")


def demo_dss_sig_value():
    section("DssSigValue — DSA signature (RFC 3279 §2.2.2)")

    sig = pa.DssSigValue.from_der(DSS_SIG_DER)

    assert sig.r == _R_DSA, f"r mismatch: {sig.r.hex()}"
    assert sig.s == _S_DSA, f"s mismatch: {sig.s.hex()}"

    print(f"  r ({len(sig.r)} bytes): {sig.r.hex()[:16]}")
    print(f"  s ({len(sig.s)} bytes): {sig.s.hex()[:16]}")
    print(f"  repr: {repr(sig)}")

    # Round-trip
    der2 = sig.to_der()
    sig2 = pa.DssSigValue.from_der(der2)
    assert sig2.r == sig.r
    assert sig2.s == sig.s
    print("  round-trip: ✓")


def demo_ecdsa_sig_value():
    section("EcdsaSigValue — ECDSA signature (RFC 3279 §2.2.3, X9.62)")

    sig = pa.EcdsaSigValue.from_der(ECDSA_SIG_DER)

    assert sig.r == _R_ECDSA, f"r mismatch: {sig.r.hex()}"
    assert sig.s == _S_ECDSA, f"s mismatch: {sig.s.hex()}"

    print(f"  r ({len(sig.r)} bytes): {sig.r.hex()[:16]}")
    print(f"  s ({len(sig.s)} bytes): {sig.s.hex()[:16]}")
    print(f"  repr: {repr(sig)}")

    # Round-trip
    der2 = sig.to_der()
    sig2 = pa.EcdsaSigValue.from_der(der2)
    assert sig2.r == sig.r
    assert sig2.s == sig.s
    print("  round-trip: ✓")


def demo_ec_parameters_named_curve():
    section("ECParameters namedCurve — P-256 (RFC 3279 §2.3.5, X9.62)")

    # ECParameters is decoded from its bare DER encoding:
    #   namedCurve → OID TLV  (tag 0x06)
    #   ecParameters → SEQUENCE TLV  (tag 0x30)
    #   implicitlyCA → NULL TLV  (tag 0x05)
    params = pa.ECParameters.from_der(EC_PARAMS_NAMED_CURVE_DER)

    assert params.arm == "namedCurve", f"unexpected arm: {params.arm!r}"

    oid = params.named_curve_oid
    assert oid is not None
    assert str(oid) == PRIME256V1_DOTTED, f"OID mismatch: {oid}"

    print(f"  arm:             {params.arm!r}")
    print(f"  named_curve_oid: {oid}  (P-256 / prime256v1)")
    print(f"  repr:            {repr(params)}")

    # ecParameters arm → named_curve_oid is None
    # Build a minimal SpecifiedECDomain SEQUENCE (just enough for parsing)
    # A real ecParameters would contain field, curve, base, order, cofactor.
    # For this example, demonstrate that named_curve_oid is None for the other arms.

    # implicitlyCA arm: encoded as NULL (05 00)
    null_der = b"\x05\x00"
    implicit_params = pa.ECParameters.from_der(null_der)
    assert implicit_params.arm == "implicitlyCA"
    assert implicit_params.named_curve_oid is None
    print(f"  implicitlyCA arm: named_curve_oid = {implicit_params.named_curve_oid}")

    # Round-trip for namedCurve
    der2 = params.to_der()
    params2 = pa.ECParameters.from_der(der2)
    assert params2.arm == "namedCurve"
    assert str(params2.named_curve_oid) == PRIME256V1_DOTTED
    print("  round-trip: ✓")


def demo_oid_constants():
    section("OID constants — synta.pkixalgs")

    constants = [
        # (attribute_name, ObjectIdentifier, expected_dotted_string)
        ("ID_DSA",             pa.ID_DSA,             "1.2.840.10040.4.1"),
        ("ID_DSA_WITH_SHA1",   pa.ID_DSA_WITH_SHA1,   "1.2.840.10040.4.3"),
        ("DHPUBLICNUMBER",     pa.DHPUBLICNUMBER,     "1.2.840.10046.2.1"),
        ("ID_EC_PUBLIC_KEY",   pa.ID_EC_PUBLIC_KEY,   "1.2.840.10045.2.1"),
        ("ECDSA_WITH_SHA1",    pa.ECDSA_WITH_SHA1,    "1.2.840.10045.4.1"),
        ("ECDSA_WITH_SHA256",  pa.ECDSA_WITH_SHA256,  "1.2.840.10045.4.3.2"),
        ("ECDSA_WITH_SHA384",  pa.ECDSA_WITH_SHA384,  "1.2.840.10045.4.3.3"),
        ("ECDSA_WITH_SHA512",  pa.ECDSA_WITH_SHA512,  "1.2.840.10045.4.3.4"),
        ("PRIME192V1",         pa.PRIME192V1,         "1.2.840.10045.3.1.1"),
        ("PRIME256V1",         pa.PRIME256V1,         "1.2.840.10045.3.1.7"),
        ("SECP224R1",          pa.SECP224R1,          "1.3.132.0.33"),
        ("SECP384R1",          pa.SECP384R1,          "1.3.132.0.34"),
        ("SECP521R1",          pa.SECP521R1,          "1.3.132.0.35"),
    ]

    for name, oid, expected in constants:
        dotted = str(oid)
        assert dotted == expected, f"{name}: expected {expected!r}, got {dotted!r}"
        print(f"  pa.{name:<22} = {dotted}")

    # OID constants are ObjectIdentifier objects — usable directly with the encoder
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_oid(pa.PRIME256V1)
    prime256_der = enc.finish()
    assert prime256_der == _oid(PRIME256V1_DOTTED)
    print()
    print(f"  PRIME256V1 DER: {prime256_der.hex()}")

    # OID constants compare equal to ObjectIdentifier constructed from dotted string
    assert pa.ID_EC_PUBLIC_KEY == synta.ObjectIdentifier("1.2.840.10045.2.1")
    print("  OID equality check: ✓")


# ── main ──────────────────────────────────────────────────────────────────────

def main():
    print("=" * 60)
    print("Example: RFC 3279 algorithm parameter types (synta.pkixalgs)")
    print("=" * 60)
    demo_dss_parms()
    demo_dss_sig_value()
    demo_ecdsa_sig_value()
    demo_ec_parameters_named_curve()
    demo_oid_constants()
    print("\nAll pkixalgs examples completed.")


if __name__ == "__main__":
    main()