# 33. `example_acme_rfc8737.py` — ACME TLS-ALPN-01 Extension (RFC 8737)
[← Example index](index.md) · [`example_acme_rfc8737.py` on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_acme_rfc8737.py)
Bindings: `synta.acme.AcmeAuthorization`, `synta.acme.ID_PE_ACME_IDENTIFIER`,
`synta.oids.PE_ACME_IDENTIFIER`, `synta.digest`.
- Compute the RFC 8737 §3 key authorization digest: concatenate the ACME
token and the base64url-encoded JWK thumbprint, then SHA-256 the result
using `synta.digest("sha256", ...)`.
- Construct an `AcmeAuthorization` from the 32-byte digest; verify
`hex_digest`, `len`, and `bytes()` conversion.
- Perform a DER encoding and decoding round-trip via `to_der()` /
`from_der()`; confirm the OCTET STRING TLV is 34 bytes (2 header + 32 value).
- Look up the `id-pe-acmeIdentifier` OID (1.3.6.1.5.5.7.1.31) via both
`acme.ID_PE_ACME_IDENTIFIER` and `oids.PE_ACME_IDENTIFIER`.
- Show how the extension value is embedded in a validation certificate per
RFC 8737 §3: the `extnValue` OCTET STRING wraps `auth.to_der()`; the
extension MUST be marked critical and the certificate MUST NOT be served
outside of acme-tls/1 ALPN negotiation.
## Source
```python
#!/usr/bin/env python3
"""
Example: ACME TLS-ALPN-01 Extension (RFC 8737)
Demonstrates:
- Computing the key authorization digest as specified in RFC 8737 §3
- Constructing an AcmeAuthorization value from a 32-byte SHA-256 digest
- DER encoding and decoding round-trip
- Looking up the id-pe-acmeIdentifier OID (1.3.6.1.5.5.7.1.31)
- How the extension value would sit inside a validation certificate
Run with:
python3 examples/example_acme_rfc8737.py
Import note: this example uses only synta — no external crypto library is needed.
The synta.digest() function provides SHA-256 hashing.
"""
from __future__ import annotations
import synta
import synta.acme as acme
import synta.oids as oids
def section(title: str) -> None:
print(f"\n{'─' * 60}\n{title}\n{'─' * 60}")
# ── Step 1: Simulate ACME protocol inputs ─────────────────────────────────────
#
# RFC 8737 §3 defines the key authorization string as:
# keyAuthorization = token || '.' || base64url(thumbprint(accountKey))
#
# For demonstration purposes we use fixed ASCII strings that mimic the
# format of a real ACME token and base64url-encoded JWK thumbprint. In a
# real ACME client, `token` comes from the challenge object returned by the
# ACME server, and `thumbprint` is the SHA-256 of the canonicalized JWK.
# 43-character URL-safe base64 token (no padding, as produced by ACME servers)
DEMO_TOKEN = "LoqXcYV8q5ONbJQxbmR7SCkF3nLudld73GnNwqiTvjU"
# Simulated base64url-encoded JWK thumbprint (43 characters, no padding)
DEMO_THUMBPRINT_B64URL = "XNpDJCeS4be1RGe8XCfv_BnFjFm8Hm-u5IfmE3QUIA"
def demo_compute_digest() -> bytes:
section("Step 1 — Compute key authorization digest (RFC 8737 §3)")
# RFC 8737 §3 key authorization construction:
# keyAuthorization = token || '.' || base64url(SHA-256(accountKey JWK))
# The thumbprint MUST use base64url without padding per RFC 7638.
key_auth = f"{DEMO_TOKEN}.{DEMO_THUMBPRINT_B64URL}"
print(f" keyAuthorization string : {key_auth!r}")
print(f" length : {len(key_auth)} characters")
# SHA-256(keyAuthorization) — the 32-byte value that goes into the extension
digest_bytes = synta.digest("sha256", key_auth.encode("ascii"))
print(f" SHA-256 digest (hex) : {digest_bytes.hex()}")
print(f" digest length : {len(digest_bytes)} bytes")
assert len(digest_bytes) == 32, "SHA-256 digest must be 32 bytes"
return digest_bytes
def demo_construct_authorization(digest_bytes: bytes) -> acme.AcmeAuthorization:
section("Step 2 — Construct AcmeAuthorization from digest")
auth = acme.AcmeAuthorization(digest_bytes)
print(f" repr : {repr(auth)}")
print(f" hex_digest : {auth.hex_digest}")
print(f" len : {len(auth)} bytes")
raw = bytes(auth)
assert len(raw) == 32
assert raw == digest_bytes
print(f" bytes(auth) matches digest input : OK")
return auth
def demo_der_round_trip(auth: acme.AcmeAuthorization) -> None:
section("Step 3 — DER encoding and decoding round-trip")
# to_der() returns the OCTET STRING TLV: tag 0x04, length 0x20, then 32 bytes
der = auth.to_der()
print(f" DER (hex) : {der.hex()}")
print(f" DER length : {len(der)} bytes (= 2 bytes TLV + 32 bytes value)")
assert len(der) == 34
# Parse the DER back into an AcmeAuthorization
auth2 = acme.AcmeAuthorization.from_der(der)
print(f" from_der repr : {repr(auth2)}")
assert auth == auth2, "round-trip produced different value"
print(f" Round-trip : OK")
# from_der also accepts raw OctetString DER written by the encoder directly
enc = synta.Encoder(synta.Encoding.DER)
enc.encode_octet_string(bytes(auth))
raw_der = enc.finish()
auth3 = acme.AcmeAuthorization.from_der(raw_der)
assert auth3 == auth
print(f" from_der via Encoder : OK")
def demo_oid_constant() -> None:
section("Step 4 — OID lookup: id-pe-acmeIdentifier")
# synta.acme exports the OID as an ObjectIdentifier
oid = acme.ID_PE_ACME_IDENTIFIER
print(f" acme.ID_PE_ACME_IDENTIFIER : {oid}")
assert str(oid) == "1.3.6.1.5.5.7.1.31"
# The same OID is available in synta.oids under PE_ACME_IDENTIFIER
oid2 = oids.PE_ACME_IDENTIFIER
print(f" oids.PE_ACME_IDENTIFIER : {oid2}")
assert str(oid2) == "1.3.6.1.5.5.7.1.31"
print(f" OID arc: id-pkix.id-pe.31 (id-pe-acmeIdentifier)")
print(f" OID assertion : OK")
def demo_extension_note(auth: "acme.AcmeAuthorization") -> None:
section("Step 5 — How to embed in a validation certificate (note)")
# RFC 8737 §3 specifies that the ACME validation certificate MUST:
# (a) include a dNSName SAN equal to the domain being validated,
# (b) include the acmeIdentifier extension marked critical,
# (c) set extnValue to the DER encoding of Authorization.
#
# The extension's extnValue OCTET STRING wraps the Authorization DER,
# so the actual bytes placed in the certificate are auth.to_der().
ext_value_der = auth.to_der()
print(
" Per RFC 8737 §3, the validation certificate must carry:\n"
" - SAN: exactly one dNSName equal to the domain under validation\n"
" - Extension OID: 1.3.6.1.5.5.7.1.31 (id-pe-acmeIdentifier)\n"
" - Critical: TRUE (must be marked critical)\n"
" - extnValue: the DER encoding of the Authorization OCTET STRING"
)
print(f"\n extnValue content (hex) : {ext_value_der.hex()}")
print(f" extnValue length : {len(ext_value_der)} bytes")
# Show how to decode the extension value from a parsed certificate:
# (assuming cert.get_extension_value_der("1.3.6.1.5.5.7.1.31") returns it)
recovered = acme.AcmeAuthorization.from_der(ext_value_der)
assert recovered == auth
print(f" Decode from cert extnValue : OK")
print(
"\n Note: the certificate MUST NOT be served outside of acme-tls/1\n"
" ALPN negotiation (RFC 8737 §3 / RFC 7301)."
)
def main() -> None:
print("=== ACME TLS-ALPN-01 Extension Example (RFC 8737) ===")
digest_bytes = demo_compute_digest()
auth = demo_construct_authorization(digest_bytes)
demo_der_round_trip(auth)
demo_oid_constant()
demo_extension_note(auth)
print("\nAll steps completed successfully.")
if __name__ == "__main__":
main()
```