OCSP Responder Framework
ocspresponder is an RFC 6960 compliant OCSP Responder framework written in Python 3.5+. It provides a foundation for building an OCSP responder service, leveraging the ocspbuilder and asn1crypto libraries for cryptographic operations, and using Bottle for the HTTP server. It is currently in an alpha development stage (version 0.5.0) and is not recommended for production use.
Warnings
- breaking The `ocspresponder` library is currently in 'Alpha' status and explicitly marked 'Don't use for production yet' on its PyPI page. Its API or internal workings may change significantly.
- gotcha Major Certificate Authorities (e.g., Let's Encrypt) are phasing out OCSP support in favor of Certificate Revocation Lists (CRLs) due to privacy concerns and operational simplicity. While `ocspresponder` provides OCSP functionality, the broader ecosystem's shift may impact its long-term utility.
- gotcha The library requires you to implement custom functions for `retrieve_certificate_status` and `retrieve_issuer_certificate`. These functions are critical for the responder's operation and must securely access and manage your certificate revocation data.
- gotcha OCSP requests are typically sent over plain HTTP and can be vulnerable to interception and modification if not properly secured (e.g., through OCSP stapling or secure transport). Attackers could alter responses or block them, potentially leading to clients accepting revoked certificates.
Install
-
pip install ocspresponder
Imports
- OCSPResponder
from ocspresponder import OCSPResponder
- CertificateStatus
from ocspresponder import CertificateStatus
Quickstart
import os
from datetime import datetime, timedelta
from typing import Optional
from ocspresponder import OCSPResponder, CertificateStatus
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from cryptography import x509
from ocspbuilder import OCSPResponseBuilder
# NOTE: This quickstart uses dummy certificates and keys for demonstration.
# In a real scenario, these would be loaded from secure storage.
# Also, ocspresponder is currently alpha; not for production use.
# Generate dummy CA and OCSP responder certificates/keys for demonstration
def generate_dummy_certs():
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
ca_subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My CA"),
x509.NameAttribute(NameOID.COMMON_NAME, u"My Root CA"),
])
ca_cert = (
x509.CertificateBuilder()
.subject_name(ca_subject)
.issuer_name(ca_subject)
.public_key(ca_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.utcnow())
.not_valid_after(datetime.utcnow() + timedelta(days=365))
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.sign(ca_key, hashes.SHA256())
)
ocsp_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
ocsp_subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My OCSP Responder"),
x509.NameAttribute(NameOID.COMMON_NAME, u"ocsp.example.com"),
])
ocsp_cert = (
x509.CertificateBuilder()
.subject_name(ocsp_subject)
.issuer_name(ca_subject)
.public_key(ocsp_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.utcnow())
.not_valid_after(datetime.utcnow() + timedelta(days=90))
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
.add_extension(x509.ExtendedKeyUsage([x509.ExtendedKeyUsageOID.OCSP_SIGNING]), critical=True)
.sign(ca_key, hashes.SHA256())
)
return ca_cert, ca_key, ocsp_cert, ocsp_key
CA_CERT, CA_KEY, OCSP_CERT, OCSP_KEY = generate_dummy_certs()
# Example data store for certificate statuses
# In a real application, this would be a database or other persistent store.
dummy_cert_db = {
12345: (CertificateStatus.good, None, CA_CERT.public_bytes(serialization.Encoding.PEM)),
67890: (CertificateStatus.revoked, datetime.utcnow() - timedelta(days=5), CA_CERT.public_bytes(serialization.Encoding.PEM))
}
# Custom function to validate a certificate serial number
def validate_cert_status(serial: int) -> (CertificateStatus, Optional[datetime]):
status, revoked_at, _ = dummy_cert_db.get(serial, (CertificateStatus.unknown, None, None))
return status, revoked_at
# Custom function to retrieve the issuer certificate for a given serial
def get_issuer_certificate(serial: int) -> Optional[bytes]:
# In a real scenario, you'd find the actual issuer of the certificate
# identified by 'serial'. For this dummy example, we return the CA_CERT.
_, _, issuer_cert_pem = dummy_cert_db.get(serial, (None, None, None))
return issuer_cert_pem
# Instantiate the OCSP Responder
responder = OCSPResponder(
issuer_cert=CA_CERT.public_bytes(serialization.Encoding.PEM),
ocsp_cert=OCSP_CERT.public_bytes(serialization.Encoding.PEM),
ocsp_key=OCSP_KEY.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
),
retrieve_certificate_status=validate_cert_status,
retrieve_issuer_certificate=get_issuer_certificate
)
print("OCSPResponder instantiated successfully.")
print("This is a framework. To run a server, you would integrate this into an HTTP server (e.g., Bottle).")
print("For example, with Bottle:")
print("from bottle import run, request, post")
print("@post('/ocsp')")
print("def ocsp_service():")
print(" return responder.handle_ocsp_request(request.body.read())")
print("run(host='localhost', port=8080)")