synta 0.2.3

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
RFC 9118 Enhanced JWT Claim Constraints — synta.schema usage example.

Demonstrates how to use ``@asn1_sequence``, ``@asn1_sequence_of``, and
``asn1_field`` from ``synta.schema`` to implement a complete RFC-defined
ASN.1 module in pure Python, including DER round-trips and nested
SEQUENCE OF types.

ASN.1 module (RFC 9118 Appendix A, EXPLICIT TAGS):

    EnhancedJWTClaimConstraints ::= SEQUENCE {
      mustInclude    [0] JWTClaimNames       OPTIONAL,
      permittedValues [1] JWTClaimValuesList  OPTIONAL,
      mustExclude    [2] JWTClaimNames       OPTIONAL }

    JWTClaimValuesList ::= SEQUENCE SIZE (1..MAX) OF JWTClaimValues
    JWTClaimValues     ::= SEQUENCE {
      claim  JWTClaimName,
      values SEQUENCE SIZE (1..MAX) OF UTF8String }
    JWTClaimNames  ::= SEQUENCE SIZE (1..MAX) OF JWTClaimName
    JWTClaimName   ::= IA5String
"""

from __future__ import annotations

from dataclasses import dataclass

import synta
from synta.schema import asn1_field, asn1_sequence, asn1_sequence_of

# OID: id-pe-eJWTClaimConstraints = { id-pe 33 } = 1.3.6.1.5.5.7.1.33
ID_PE_EJWT_CLAIM_CONSTRAINTS = synta.ObjectIdentifier("1.3.6.1.5.5.7.1.33")


# ---------------------------------------------------------------------------
# Type definitions
# ---------------------------------------------------------------------------

@asn1_sequence_of
@dataclass
class JWTClaimNames:
    names: list[synta.IA5String]


@asn1_sequence
@dataclass
class JWTClaimValues:
    claim: synta.IA5String
    values: list[synta.Utf8String]


@asn1_sequence_of
@dataclass
class JWTClaimValuesList:
    items: list[JWTClaimValues]


@asn1_sequence
@dataclass
class EnhancedJWTClaimConstraints:
    mustInclude:     JWTClaimNames     | None = asn1_field(tag=0, explicit=True)
    permittedValues: JWTClaimValuesList | None = asn1_field(tag=1, explicit=True)
    mustExclude:     JWTClaimNames     | None = asn1_field(tag=2, explicit=True)


# ---------------------------------------------------------------------------
# Demo
# ---------------------------------------------------------------------------

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


def demo_must_include_only() -> None:
    section("mustInclude only")
    ext = EnhancedJWTClaimConstraints(
        mustInclude=JWTClaimNames([synta.IA5String("div"), synta.IA5String("opt")]),
    )
    der = ext.to_der()
    print(f"  DER ({len(der)} bytes): {der.hex()}")

    ext2 = EnhancedJWTClaimConstraints.from_der(der)
    names = [n.as_str() for n in ext2.mustInclude.names]
    print(f"  mustInclude:     {names}")
    print(f"  permittedValues: {ext2.permittedValues}")
    print(f"  mustExclude:     {ext2.mustExclude}")
    assert names == ["div", "opt"]
    assert ext2.permittedValues is None
    assert ext2.mustExclude is None


def demo_permitted_values() -> None:
    section("permittedValues only")
    ext = EnhancedJWTClaimConstraints(
        permittedValues=JWTClaimValuesList([
            JWTClaimValues(
                claim=synta.IA5String("div"),
                values=[synta.Utf8String("1"), synta.Utf8String("2")],
            ),
            JWTClaimValues(
                claim=synta.IA5String("iss"),
                values=[synta.Utf8String("https://example.com")],
            ),
        ]),
    )
    der = ext.to_der()
    print(f"  DER ({len(der)} bytes): {der.hex()}")

    ext2 = EnhancedJWTClaimConstraints.from_der(der)
    for entry in ext2.permittedValues.items:
        vals = [v.as_str() for v in entry.values]
        print(f"  {entry.claim.as_str()!r}: {vals}")


def demo_all_three_fields() -> None:
    section("all three fields")
    ext = EnhancedJWTClaimConstraints(
        mustInclude=JWTClaimNames([synta.IA5String("div")]),
        permittedValues=JWTClaimValuesList([
            JWTClaimValues(
                claim=synta.IA5String("div"),
                values=[synta.Utf8String("1"), synta.Utf8String("2")],
            ),
        ]),
        mustExclude=JWTClaimNames([synta.IA5String("iss")]),
    )
    der = ext.to_der()
    print(f"  DER ({len(der)} bytes): {der.hex()}")

    ext2 = EnhancedJWTClaimConstraints.from_der(der)
    print(f"  mustInclude:     {[n.as_str() for n in ext2.mustInclude.names]}")
    print(f"  permittedValues: {ext2.permittedValues.items[0].claim.as_str()!r}"
          f"{[v.as_str() for v in ext2.permittedValues.items[0].values]}")
    print(f"  mustExclude:     {[n.as_str() for n in ext2.mustExclude.names]}")


def demo_oid() -> None:
    section("OID constant")
    print(f"  id-pe-eJWTClaimConstraints = {ID_PE_EJWT_CLAIM_CONSTRAINTS}")
    assert str(ID_PE_EJWT_CLAIM_CONSTRAINTS) == "1.3.6.1.5.5.7.1.33"


if __name__ == "__main__":
    demo_must_include_only()
    demo_permitted_values()
    demo_all_three_fields()
    demo_oid()
    print("\nDone.")