synta 0.1.8

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
Example 7: OCSP response parsing and building.

Demonstrates: OCSPResponse.from_der, OCSPResponse.from_pem, OCSPResponse.to_pem,
and all OCSPResponse properties (status, response_type_oid, response_bytes, to_der);
OCSPResponseBuilder (responder_name, responder_key_hash, produced_at,
add_response, build_tbs, assemble); OCSPSingleResponse; NameBuilder.
"""

import hashlib
import synta

# Successful OCSP response (status=successful, contains BasicOCSPResponse)
_OCSP_SUCCESS_DER = bytes.fromhex(
    "308201c40a0100a08201bd308201b906092b0601050507300101048201aa3082"
    "01a630818fa216041451faf8a55a16074dc0703456a9a831e6f1f8d1b8180f32"
    "303236303331303136303033345a30643062303a300906052b0e03021a050004"
    "14bf7052c8b9c0f760c89123e099815eb2c0394226041451faf8a55a16074dc0"
    "703456a9a831e6f1f8d1b802012a8000180f32303236303130313030303030305a"
    "30110a0f32303236303130313030303030305a300d06092a864886f70d01010b"
    "050003820101004de5a70defb08fe67d2ae1c4ab1ce22db43707db4ca3b65537"
    "a99cf799acbda8194697f4aaf51ab13d7c36ff45abbff19ebd1071d329b30058"
    "025185837125cba733ae42413ab56899d408b934121b16d8325331392a343772"
    "083daa11da186476ea1fb4201c7bc1ac71d380357ff78071b18ecdbfdd6b1c0c"
    "0ab996dae5475d849d27b2c22780b4dc76371eaaaa487d11ed13d48bfd121c9f"
    "847f82ff70bf0143977d06f3f59e70cd8332976058e8dcf23bc1cd2b520ddc36"
    "5a61cc81d6e2e881719056d4db925eb4d86ab4f116f6fc376365c85912dea5b3"
    "4983cea9efe51cfbcbbabef08263aa39bc5f1742e9ecf0986b09025b56ed15a2"
    "a2f8a012fbaad5"
)

# Non-successful OCSP response: tryLater (status=3), no responseBytes
_OCSP_TRYLATER_DER = bytes.fromhex("30030a0103")


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


def demo_successful_response():
    section("Successful OCSP response")
    resp = synta.OCSPResponse.from_der(_OCSP_SUCCESS_DER)

    print(f"  status:              {resp.status}")
    print(f"  response_type_oid:   {resp.response_type_oid}")
    print(f"  response_bytes:      <{len(resp.response_bytes)} bytes>")
    print(f"  to_der():            <{len(resp.to_der())} bytes>")

    assert resp.status == "successful"
    assert resp.response_type_oid is not None
    # id-pkix-ocsp-basic = 1.3.6.1.5.5.7.48.1.1
    assert str(resp.response_type_oid) == "1.3.6.1.5.5.7.48.1.1"
    assert resp.response_bytes is not None


def demo_trylater_response():
    section("Non-successful OCSP response — tryLater")
    resp = synta.OCSPResponse.from_der(_OCSP_TRYLATER_DER)

    print(f"  status:            {resp.status}")
    print(f"  response_type_oid: {resp.response_type_oid!r}  (None for error responses)")
    print(f"  response_bytes:    {resp.response_bytes!r}  (None for error responses)")

    assert resp.status == "tryLater"
    assert resp.response_type_oid is None
    assert resp.response_bytes is None


def demo_all_status_values():
    section("All OCSP response status strings")
    status_values = [
        (0, "successful"),
        (1, "malformedRequest"),
        (2, "internalError"),
        (3, "tryLater"),
        (5, "sigRequired"),
        (6, "unauthorized"),
    ]
    for code, name in status_values:
        # Build minimal OCSPResponse DER: SEQUENCE { ENUMERATED(code) }
        der = bytes([0x30, 0x03, 0x0a, 0x01, code])
        resp = synta.OCSPResponse.from_der(der)
        print(f"  ENUMERATED({code}) → status={resp.status!r}")
        assert resp.status == name


def demo_to_der_roundtrip():
    section("to_der() round-trip")
    resp = synta.OCSPResponse.from_der(_OCSP_SUCCESS_DER)
    der2 = resp.to_der()
    assert der2 == _OCSP_SUCCESS_DER
    print("  to_der() round-trip: OK")


def demo_pem_roundtrip():
    section("from_pem / to_pem round-trip")
    resp = synta.OCSPResponse.from_der(_OCSP_SUCCESS_DER)
    pem = synta.OCSPResponse.to_pem(resp)
    assert pem.startswith(b"-----BEGIN OCSP RESPONSE-----")
    print(f"  to_pem(): {len(pem)} bytes")

    resp2 = synta.OCSPResponse.from_pem(pem)
    assert resp2.status == resp.status
    assert resp2.response_bytes == resp.response_bytes
    print("  from_pem() round-trip: OK")


def demo_ocsp_builder():
    section("OCSPResponseBuilder — build a complete OCSPResponse")
    # id-sha1 AlgorithmIdentifier DER: SEQUENCE { OID 1.3.14.3.2.26 NULL }
    SHA1_ALG = bytes.fromhex("300906052b0e03021a0500")
    # sha256WithRSAEncryption AlgorithmIdentifier DER
    SHA256_WITH_RSA = bytes.fromhex("300d06092a864886f70d01010b0500")

    # Build an issuer name and compute SHA-1 of its DER for the name hash
    name_der = synta.NameBuilder().common_name("Test CA").build()
    name_hash = hashlib.sha1(name_der).digest()

    # Use a fixed 20-byte key hash for demonstration
    key_hash = bytes(20)
    serial = bytes([0x01])

    # Construct a single response entry (certificate status = good).
    # OCSPSingleResponse is available via synta._synta (the native extension module).
    single = synta.OCSPSingleResponse(
        hash_algorithm_der=SHA1_ALG,
        issuer_name_hash=name_hash,
        issuer_key_hash=key_hash,
        serial=serial,
        status=0,                       # 0 = good
        this_update="20260101000000Z",
        next_update="20260701000000Z",
    )

    # Build the ResponseData TBS SEQUENCE
    tbs = (
        synta.OCSPResponseBuilder()
        .responder_key_hash(key_hash)
        .produced_at("20260101000000Z")
        .add_response(single)
        .build_tbs()
    )
    print(f"  ResponseData DER: <{len(tbs)} bytes>")

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

    # Parse and verify
    resp = synta.OCSPResponse.from_der(ocsp_der)
    assert resp.status == "successful", f"expected 'successful', got {resp.status!r}"
    print(f"  status:            {resp.status}")
    print(f"  response_type_oid: {resp.response_type_oid}")
    assert resp.response_type_oid is not None
    # id-pkix-ocsp-basic = 1.3.6.1.5.5.7.48.1.1
    assert str(resp.response_type_oid) == "1.3.6.1.5.5.7.48.1.1"
    print("  OCSPResponseBuilder: OK")

    # Also demonstrate responder_name variant (rebuild the single response object
    # since the first add_response consumed it inside the builder)
    single2 = synta.OCSPSingleResponse(
        hash_algorithm_der=SHA1_ALG,
        issuer_name_hash=name_hash,
        issuer_key_hash=key_hash,
        serial=serial,
        status=0,
        this_update="20260101000000Z",
        next_update="20260701000000Z",
    )
    tbs_name = (
        synta.OCSPResponseBuilder()
        .responder_name(name_der)
        .produced_at("20260101000000Z")
        .add_response(single2)
        .build_tbs()
    )
    ocsp_name_der = synta.OCSPResponseBuilder.assemble(tbs_name, SHA256_WITH_RSA, sig)
    resp2 = synta.OCSPResponse.from_der(ocsp_name_der)
    assert resp2.status == "successful"
    print(f"  responder_name variant: status={resp2.status!r}  OK")


def main():
    print("=" * 60)
    print("Example 7: OCSP response parsing and building")
    print("=" * 60)
    demo_successful_response()
    demo_trylater_response()
    demo_all_status_values()
    demo_to_der_roundtrip()
    demo_pem_roundtrip()
    demo_ocsp_builder()
    print("\nAll OCSP examples completed.")


if __name__ == "__main__":
    main()