OCSP stapling lets a TLS server deliver a signed certificate-status response during a handshake. A client that would otherwise make an online OCSP request may avoid that request. The saving depends on the client’s revocation policy: not every browser performs an OCSP lookup for every connection, and not every certificate issuer still operates an OCSP service. Stapling is one transport for status evidence, not a replacement for certificate validation.

Deliver evidence without transferring trust

The server obtains a response from the issuer or an authorized responder and caches it for eligible handshakes. The client still needs to check the response signature, signer authority, certificate identifier, status, and acceptable time bounds. A server cannot make a response trustworthy merely by attaching it. A good OCSP status also does not prove hostname coverage, certificate validity, or the complete trust chain.

The wire representation depends on the TLS version. TLS 1.2 uses the status-request extension and a CertificateStatus handshake message. TLS 1.3 places the status response in an extension of the corresponding Certificate entry. Calling either mechanism a generic certificate_status extension obscures that distinction. Ordinary network attackers cannot simply strip authenticated handshake messages while leaving the Finished checks valid; availability and revocation failures require a more precise threat model.

Check support before enabling anything

An operator must first confirm that the issuer supplies usable status responses and that the deployed TLS server and clients support the desired policy. Let’s Encrypt ended its OCSP service on August 6, 2025 and moved to CRLs. A missing staple on a certificate without OCSP support is therefore not automatically a broken refresh job. Check current issuer documentation instead of copying an unconditional “enable stapling everywhere” checklist.

For an issuer that supports it, refresh before expiry and after certificate rotation. Inspect the certificate association, freshness and actual edge response, not just cache presence. Different processes can hold different caches. A cached good response can remain acceptable while newer revocation information exists; stapling does not provide instantaneous revocation knowledge.

When it breaks

Missing, expired, invalid, and revoked responses are different conditions. Clients may reject, consult another mechanism, or apply a documented soft-fail policy. A missing response does not universally trigger a direct CA request, and an invalid supplied response does not universally become a harmless delay. Describe the behavior of the specific validator rather than promising that every handshake continues.

The TLS Feature extension, often called Must-Staple, can require status evidence when enforced by a supporting client. It does not force clients without that support to enforce the policy. The server must be prepared for cold starts, renewal, and responder outages before relying on such a requirement. An outage does not instantly invalidate a still-acceptable cached response, but the remaining freshness window bounds that protection.

Clock problems can change acceptance decisions. thisUpdate and nextUpdate describe status timing, while producedAt describes response production. Actual validators have age and skew rules; some valid protocol responses omit nextUpdate. Keep those implementation details explicit. Do not conclude that a Boolean labeled “signature valid” demonstrates real cryptographic verification.

OCSP delivery, client validation, optional versus required missing-response policy, cached revocation lag, TLS representation and synthetic-model limits.
OCSP evidence delivery: mechanism and evidence boundaries.

Check it quickly

Save this as stapling_policy.py and run python3 stapling_policy.py with Python 3.9 or newer. The standalone model uses synthetic post-verification inputs, requires both time bounds, and deliberately applies strict rejection to supplied invalid evidence. It has no network, certificate parser, signature implementation, TLS server, or browser.

"""Post-verification policy model. Booleans are fixtures, not cryptography."""
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone

@dataclass(frozen=True)
class Response:
    cert_id: str
    status: str
    signature_ok: bool
    signer_authorized: bool
    this_update: datetime
    next_update: datetime

def decide(response, now, cert_id, require_staple):
    if response is None:
        return "reject-missing" if require_staple else "consult-other-policy"
    if response.cert_id != cert_id:
        return "reject-wrong-certificate"
    if not (response.signature_ok and response.signer_authorized):
        return "reject-untrusted"
    if not response.this_update <= now < response.next_update:
        return "reject-time"
    if response.status == "revoked":
        return "reject-revoked"
    if response.status != "good":
        return "reject-unknown"
    return "status-good-only"

now = datetime(2026, 10, 15, 12, tzinfo=timezone.utc)
good = Response("synthetic-cert", "good", True, True,
                now - timedelta(hours=1), now + timedelta(hours=1))
cases = [
    (good, False, "status-good-only"),
    (None, False, "consult-other-policy"),
    (None, True, "reject-missing"),
    (replace(good, status="revoked"), False, "reject-revoked"),
    (replace(good, status="unknown"), False, "reject-unknown"),
    (replace(good, signature_ok=False), False, "reject-untrusted"),
    (replace(good, signer_authorized=False), False, "reject-untrusted"),
    (replace(good, cert_id="another-cert"), False, "reject-wrong-certificate"),
    (replace(good, next_update=now), False, "reject-time"),
    (replace(good, this_update=now + timedelta(minutes=1)), False, "reject-time"),
]
for response, required, expected in cases:
    actual = decide(response, now, "synthetic-cert", required)
    assert actual == expected, (actual, expected)
    print(actual)
assert decide(good, now + timedelta(hours=2), "synthetic-cert", False) == "reject-time"
print("PASS: 11 explicit policy cases; no certificate or TLS validation performed")

Eleven checks distinguish an acceptable status from missing, revoked, unknown, wrong-certificate, unauthorized-signer, bad-signature, expired, and future-dated inputs. The final time shift demonstrates expiration under this model’s strict interval. An optional missing response returns consult-other-policy, which is not an acceptance result and does not claim that a fetch occurred.

The success result is deliberately status-good-only: the rest of certificate validation remains outside this function. Retained Python 3.12.14 records report successful runs of these exact fences; this source review did not rerun them. Assertions must stay enabled, without -O or PYTHONOPTIMIZE. Live validation still needs the actual issuer, signed response, TLS delivery, client policy, renewal, and refresh failure.

Sources

  • RFC 6960, OCSP response authority, certificate binding, status and time semantics, checked 2026-10-15.
  • RFC 6066, TLS 1.2 status-request and CertificateStatus messages, checked 2026-10-15.
  • RFC 8446, TLS 1.3 certificate-entry status extension and authenticated transcript, checked 2026-10-15.
  • RFC 7633, TLS Feature requirements and client support, checked 2026-10-15.
  • Let’s Encrypt OCSP retirement, issuer ended OCSP on August 6 2025, checked 2026-10-15.
  • Python assert statement, optimization removes assertions and their expressions, checked 2026-10-18.