# 8. `example_ocsp.py` — OCSP parsing and building
[← Example index](index.md) · [example_ocsp.py on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_ocsp.py)
Bindings: `OCSPResponse.from_der`, `OCSPResponse.from_pem`, `OCSPResponse.to_pem`,
and all `OCSPResponse` properties; `OCSPResponseBuilder` (`responder_name`,
`responder_key_hash`, `produced_at`, `add_response`, `build_tbs`, `assemble`);
`OCSPSingleResponse`; `OCSPRequest.from_der`, `OCSPRequest.to_der`,
and all `OCSPRequest` properties (`request_list`, `requestor_name`, `request_extensions`);
`CertID` properties (`hash_algorithm_oid`, `issuer_name_hash`, `issuer_key_hash`,
`serial_number`); `OCSPRequestBuilder` (`add_request`, `requestor_name`, `build_tbs`,
`build_tbs_inner`, `assemble`); `OCSPCertIDSpec`; `NameBuilder`.
- Parse a successful OCSP response; print `status`, `response_type_oid`, length of `response_bytes`.
- Parse a non-successful response (e.g. `tryLater`); confirm `response_bytes` is `None`.
- Round-trip through `to_pem` / `from_pem`.
- Build an OCSP response from scratch with `OCSPResponseBuilder`: create a
`OCSPSingleResponse` entry (SHA-1 name hash of issuer name, fixed key hash,
status=good), set `responder_key_hash`, call `build_tbs()`, assemble with a
dummy signature; verify `status == "successful"` and response OID.
- Demonstrate the `responder_name` variant using a DER Name from `NameBuilder`.
- Build an unsigned `OCSPRequest` with `OCSPRequestBuilder`: create an `OCSPCertIDSpec`
(SHA-1 issuer name hash, fixed key hash, serial), call `build_tbs()`, parse the result
back with `OCSPRequest.from_der`, and verify all `CertID` fields.
- Demonstrate the signed-request path: call `build_tbs_inner()`, assemble with a dummy
signature via `assemble()`, parse back and confirm `request_list` length.
- Demonstrate `requestor_name` on `OCSPRequestBuilder` using a DER Name from `NameBuilder`.
## Source
```python
#!/usr/bin/env python3
"""
Example 8: OCSP response and request parsing/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;
OCSPRequest.from_der, OCSPRequest.from_pem, OCSPRequest.to_der, and all
OCSPRequest properties (request_list, requestor_name, request_extensions);
CertID properties (hash_algorithm_oid, issuer_name_hash, issuer_key_hash,
serial_number); OCSPRequestBuilder (add_request, requestor_name, build_tbs,
build_tbs_inner, assemble); OCSPCertIDSpec.
"""
import synta
# ── AlgorithmIdentifier helpers ───────────────────────────────────────────────
def _alg_id_with_null(oid: synta.ObjectIdentifier) -> bytes:
"""Build an AlgorithmIdentifier DER SEQUENCE { oid NULL } from an OID."""
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_oid(oid)
inner.encode_null()
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
return bytes(outer.finish())
def _der_length(n: int) -> bytes:
"""Encode a DER length in the minimal number of octets."""
if n < 0x80:
return bytes([n])
if n < 0x100:
return bytes([0x81, n])
return bytes([0x82, n >> 8, n & 0xFF])
# SHA-1 hash AlgorithmIdentifier (id-sha1, 1.3.14.3.2.26, OIW arc).
# synta.oids does not expose a standalone SHA1 constant (SHA-1 as a digest
# predates the NIST SHA-2 arc), so we construct it from the raw OID string.
_SHA1_ALG: bytes = _alg_id_with_null(synta.ObjectIdentifier("1.3.14.3.2.26"))
# sha256WithRSAEncryption AlgorithmIdentifier — uses the named synta.oids constant.
_SHA256_WITH_RSA_ALG: bytes = _alg_id_with_null(synta.oids.SHA256_WITH_RSA)
# 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")
# 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 = synta.digest("sha1", name_der)
# 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_ALG, 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_ALG, sig)
resp2 = synta.OCSPResponse.from_der(ocsp_name_der)
assert resp2.status == "successful"
print(f" responder_name variant: status={resp2.status!r} OK")
def demo_ocsp_request():
section("OCSPRequestBuilder — build an unsigned OCSPRequest")
name_der = synta.NameBuilder().common_name("Test CA").build()
name_hash = synta.digest("sha1", name_der)
key_hash = bytes(20)
serial = bytes([0x42])
spec = synta.OCSPCertIDSpec(
hash_algorithm_der=_SHA1_ALG,
issuer_name_hash=name_hash,
issuer_key_hash=key_hash,
serial=serial,
)
ocsp_der = synta.OCSPRequestBuilder().add_request(spec).build_tbs()
print(f" OCSPRequest DER: <{len(ocsp_der)} bytes>")
req = synta.OCSPRequest.from_der(ocsp_der)
assert len(req.request_list) == 1
cert_id = req.request_list[0]
assert cert_id.issuer_name_hash == name_hash
assert cert_id.issuer_key_hash == key_hash
assert cert_id.serial_number == int.from_bytes(serial, "big")
print(f" CertID: {cert_id!r} OK")
print(f" requestor_name: {req.requestor_name!r} (None for unsigned)")
print(f" request_extensions: {req.request_extensions!r}")
def demo_signed_ocsp_request():
section("OCSPRequestBuilder — signed request via build_tbs_inner + assemble")
spec = synta.OCSPCertIDSpec(
hash_algorithm_der=_SHA1_ALG,
issuer_name_hash=bytes(20),
issuer_key_hash=bytes(20),
serial=bytes([0x01]),
)
tbs_inner = synta.OCSPRequestBuilder().add_request(spec).build_tbs_inner()
print(f" TBSRequest DER: <{len(tbs_inner)} bytes>")
# Assemble with a dummy signature
sig = bytes(32)
ocsp_der = synta.OCSPRequestBuilder.assemble(tbs_inner, _SHA256_WITH_RSA_ALG, sig)
print(f" Signed OCSPRequest DER: <{len(ocsp_der)} bytes>")
req = synta.OCSPRequest.from_der(ocsp_der)
assert len(req.request_list) == 1
print(" signed request round-trip: OK")
def demo_requestor_name_request():
section("OCSPRequestBuilder — with requestorName")
# Build a Name DER and wrap it as a GeneralName [4] directoryName.
name_der = synta.NameBuilder().common_name("Requestor").build()
# directoryName [4] EXPLICIT Name: tag=0xa4, DER-encoded length, then Name DER
gn_der = bytes([0xa4]) + _der_length(len(name_der)) + name_der
spec = synta.OCSPCertIDSpec(
hash_algorithm_der=_SHA1_ALG,
issuer_name_hash=bytes(20),
issuer_key_hash=bytes(20),
serial=bytes([0x01]),
)
ocsp_der = (
synta.OCSPRequestBuilder()
.requestor_name(gn_der)
.add_request(spec)
.build_tbs()
)
req = synta.OCSPRequest.from_der(ocsp_der)
assert req.requestor_name is not None
print(f" requestorName present: <{len(req.requestor_name)} bytes> OK")
def main():
print("=" * 60)
print("Example 8: OCSP response and request parsing/building")
print("=" * 60)
demo_successful_response()
demo_trylater_response()
demo_all_status_values()
demo_to_der_roundtrip()
demo_pem_roundtrip()
demo_ocsp_builder()
demo_ocsp_request()
demo_signed_ocsp_request()
demo_requestor_name_request()
print("\nAll OCSP examples completed.")
if __name__ == "__main__":
main()
```