synta 0.2.5

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# 7. `example_crl.py` — CRL parsing and building

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

Bindings: `CertificateList.from_der`, `CertificateList.from_pem`,
`CertificateList.to_pem`, and all `CertificateList` properties
(`issuer`, `issuer_raw_der`, `this_update`, `next_update`, `signature_algorithm`,
`signature_algorithm_oid`, `signature_value`, `revoked_count`, `crl_number`);
`CertificateListBuilder` (`issuer`, `signature_algorithm`, `this_update`,
`next_update`, `revoke`, `build`, `assemble`); `NameBuilder`.

- Parse a CRL and print issuer, dates, algorithm, revoked count.
- Show `next_update` is `None` for CRLs that omit the field.
- Round-trip through `to_pem` / `from_pem`.
- Build a CRL from scratch with `CertificateListBuilder`: set issuer name via
  `NameBuilder`, add two revoked entries with `revoke()` (one with keyCompromise
  reason, one unspecified), call `build()` for the TBSCertList, assemble with a
  dummy signature via `assemble()`; verify `revoked_count == 2`.
- Show `crl_number` returns `None` when the CRL Number extension is absent.

## Source

```python
#!/usr/bin/env python3
"""
Example 6: CRL (Certificate Revocation List) parsing and building.

Demonstrates: CertificateList.from_der, CertificateList.from_pem,
CertificateList.to_pem, and all CertificateList properties
(issuer, issuer_raw_der, this_update, next_update, signature_algorithm,
signature_algorithm_oid, signature_value, revoked_count, crl_number, to_der);
CertificateListBuilder (issuer, signature_algorithm, this_update, next_update,
revoke, build, assemble); NameBuilder.
"""

import synta

# Minimal CRL DER: RSA, CN=Test CA, thisUpdate=2026-01-01, nextUpdate=2026-07-01
_CRL_DER = bytes.fromhex(
    "3082015a3044020101300d06092a864886f70d01010b050030123110300e0603"
    "5504030c0754657374204341170d3236303130313030303030305a170d323630"
    "3730313030303030305a300d06092a864886f70d01010b050003820101004195"
    "618e7d6d6a6440919e872408156017646fd8cb0d617c6952755bfeb5b5f6391d"
    "62b7257c7f94f461851f48ef9f9ade7c05e96679c25f8cb70b619bbc34b16a9"
    "4ceaa072d66d2985407bcd6376925ba7d89fa6861cb7811c8571c9e6bb5d86a5"
    "aad7cf3b62b63e7269b90d11d0e5a5cd54fa50edc6747e997f61738866b6f00"
    "6c9bace2daba18b5bcedaa4bc25cf9add69b3680e6b170b3f57d82e74451d1a"
    "2a1968db64c7d189c8869bd47a16bf91a0c6940cda54cdcbe355176792dc787"
    "2f2a162a2482df9d7d1c6af568808962b23479bbd8229fac19cab7798d9a83f0"
    "0ea4d83a6c61a4e720966e4ee4d9c3bbc3bdb94afde24fc70c8adb265ae15104"
)


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


def demo_all_properties():
    section("CertificateList — all properties")
    crl = synta.CertificateList.from_der(_CRL_DER)

    print(f"  issuer:                   {crl.issuer}")
    print(f"  issuer_raw_der:           <{len(crl.issuer_raw_der)} bytes>")
    print(f"  this_update:              {crl.this_update}")
    print(f"  next_update:              {crl.next_update!r}")
    print(f"  signature_algorithm:      {crl.signature_algorithm}")
    print(f"  signature_algorithm_oid:  {crl.signature_algorithm_oid}")
    print(f"  signature_value:          <{len(crl.signature_value)} bytes>")
    print(f"  revoked_count:            {crl.revoked_count}")
    print(f"  to_der:                   <{len(crl.to_der())} bytes>")


def demo_next_update_optional():
    section("next_update — present vs absent")
    crl = synta.CertificateList.from_der(_CRL_DER)
    nu = crl.next_update
    if nu is not None:
        print(f"  next_update present: {nu}")
    else:
        print("  next_update: None (CRL omits the field)")
    # RFC 5280 §5.1.2.5: nextUpdate is OPTIONAL for indirect CRLs;
    # the field is None when the CRL does not include it


def demo_to_der_roundtrip():
    section("to_der() round-trip")
    crl = synta.CertificateList.from_der(_CRL_DER)
    der2 = crl.to_der()
    assert der2 == _CRL_DER, "DER round-trip mismatch"
    print("  to_der round-trip: OK (byte-identical)")


def demo_pem_roundtrip():
    section("from_pem / to_pem round-trip")
    crl = synta.CertificateList.from_der(_CRL_DER)
    pem = synta.CertificateList.to_pem(crl)
    assert pem.startswith(b"-----BEGIN X509 CRL-----")
    print(f"  to_pem(): {len(pem)} bytes")

    crl2 = synta.CertificateList.from_pem(pem)
    assert isinstance(crl2, synta.CertificateList)
    assert crl2.issuer == crl.issuer
    assert crl2.revoked_count == crl.revoked_count
    print(f"  from_pem(): issuer={crl2.issuer}  revoked={crl2.revoked_count}")
    print("  PEM round-trip: OK")


def demo_crl_builder():
    section("CertificateListBuilder — build a CRL with two revoked entries")
    # sha256WithRSAEncryption AlgorithmIdentifier DER
    SHA256_WITH_RSA = bytes.fromhex("300d06092a864886f70d01010b0500")

    # Build a Name for the issuer
    name_der = synta.NameBuilder().common_name("Test CA").build()

    # Build the TBSCertList SEQUENCE
    tbs = (
        synta.CertificateListBuilder()
        .issuer(name_der)
        .signature_algorithm(SHA256_WITH_RSA)
        .this_update("20260101000000Z")
        .next_update("20260701000000Z")
        .revoke(bytes([0x01, 0x02, 0x03]), "20251201000000Z", 1)    # keyCompromise
        .revoke(bytes([0x04, 0x05, 0x06]), "20251201000000Z", None)  # unspecified
        .build()
    )
    print(f"  TBSCertList DER: <{len(tbs)} bytes>")

    # Assemble the full CertificateList with a dummy 256-byte signature
    sig = bytes(256)
    crl_der = synta.CertificateListBuilder.assemble(tbs, SHA256_WITH_RSA, sig)
    print(f"  CertificateList DER: <{len(crl_der)} bytes>")

    # Parse and verify
    crl = synta.CertificateList.from_der(crl_der)
    assert crl.revoked_count == 2, f"expected 2, got {crl.revoked_count}"
    print(f"  revoked_count: {crl.revoked_count}  (OK)")
    print(f"  issuer: {crl.issuer}")
    print(f"  this_update: {crl.this_update}")
    print(f"  next_update: {crl.next_update!r}")

    # DER round-trip through to_der
    assert crl.to_der() == crl_der
    print("  to_der() round-trip: OK")


def demo_crl_number():
    section("crl_number — present vs absent")
    # The hard-coded _CRL_DER does not include a CRL Number extension,
    # so crl_number returns None.
    crl = synta.CertificateList.from_der(_CRL_DER)
    n = crl.crl_number
    if n is None:
        print("  crl_number: None (CRL Number extension absent — expected)")
    else:
        print(f"  crl_number: {n}")
    assert n is None, f"expected None for test CRL, got {n!r}"
    print("  crl_number absent: OK")


def main():
    print("=" * 60)
    print("Example 6: CRL parsing and building")
    print("=" * 60)
    demo_all_properties()
    demo_next_update_optional()
    demo_to_der_roundtrip()
    demo_pem_roundtrip()
    demo_crl_builder()
    demo_crl_number()
    print("\nAll CRL examples completed.")


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