Certificate Revocation via OCSP
TLS (Transport Layer Security) is fundamentally based on trust. Operating systems ship with a trusted set of certificates known as root certificate authorities (CAs) which are controlled by organizations that operating system vendors trust. These root certificates are self-signed, meaning they are cryptographically signed by themselves, so your trust in them is essentially political and based on the reputation of the issuing organization.
These root CAs are responsible for signing intermediate certificates, which in turn sign other intermediate certificates or end-entity (leaf) certificates, creating a hierarchical chain of trust. When a client connects to a server via TLS, the server presents its certificate chain, and the client must validate both the cryptographic integrity and the trustworthiness of this chain.
Certificates can become compromised or invalid for various reasons:
- Private key compromise: If a server's private key is stolen, the certificate must be revoked immediately
- Domain ownership changes: When a domain is sold or transferred
- CA compromise: If a certificate authority is compromised, all certificates it issued may need revocation
- Regulatory requirements: Legal or compliance requirements may mandate certificate revocation
- Certificate errors: Incorrect information in certificates (wrong domain, organization, etc.)
Without proper revocation mechanisms, compromised certificates would remain valid until their natural expiration date, which could be years away.
Certificate Revocation Methods
There are two primary methods for checking certificate revocation status:
Certificate Revocation Lists (CRL)
CRLs are periodically published lists of revoked certificates. They contain:
- Serial numbers of revoked certificates
- Revocation dates
- Revocation reasons
- Next update time
| Advantages | Disadvantages |
|---|---|
| Simple to implement and cache | Can become large and unwieldy |
| No real-time network requests required | Limited update frequency (typically daily or weekly) |
| Works offline once downloaded | No real-time revocation status |
| Privacy concerns (reveals all revoked certificates) |
Online Certificate Status Protocol (OCSP)
OCSP provides real-time certificate status checking. When a client needs to verify a certificate, it sends a request to the OCSP responder (usually operated by the CA) and receives an immediate response.
| Advantages | Disadvantages |
|---|---|
| Real-time revocation status | Requires network connectivity |
| Smaller bandwidth usage (only checks specific certificates) | Can introduce latency |
| More privacy-friendly (only reveals the specific certificate being checked) | OCSP responders can be unavailable |
| Immediate revocation capability | Privacy concerns (OCSP responder learns which sites users visit) |
Understanding OCSP in Detail
OCSP Protocol Overview
OCSP operates over HTTP/HTTPS and uses ASN.1 encoding. The protocol consists of two main message types:
- OCSP Request: Contains the certificate to be checked and optional extensions
- OCSP Response: Contains the status of the certificate and is signed by the responder
OCSP Request Structure
An OCSP request contains:
- Protocol Version: Usually version 1
- Service Request: Always "OCSP"
- Request List: One or more certificate status requests
- Optional Extensions: Nonce, response types, etc.
OCSP Response Structure
An OCSP response contains:
- Response Status: Success, malformed, internal error, etc.
- Response Type: Usually "basic"
- Response Data: The actual certificate status information
- Signature: Cryptographic signature of the response
Certificate Status Values
OCSP responses can indicate one of three statuses:
- Good: Certificate is valid and not revoked
- Revoked: Certificate has been revoked
- Unknown: Responder cannot determine the status
OCSP Stapling (TLS Certificate Status Request)
OCSP stapling is an optimization that addresses the privacy and performance concerns of traditional OCSP. Instead of clients querying OCSP responders directly, the web server queries the OCSP responder on behalf of its clients and "staples" the response to the TLS handshake.
How OCSP Stapling Works
- The web server periodically queries the OCSP responder for its own certificate status
- The server caches the signed OCSP response
- During TLS handshakes, the server includes the cached OCSP response in the Certificate Status Request extension
- Clients can verify the certificate status without making additional network requests
Benefits of OCSP Stapling
- Privacy: OCSP responder doesn't learn about individual client connections
- Performance: Eliminates additional network round-trips for clients
- Reliability: Reduces dependency on OCSP responder availability
- Scalability: Reduces load on OCSP responders
OCSP Stapling Requirements
- The client must include a Certificate Status Request extension in its ClientHello
- The server must have a valid OCSP response for its certificate
- The OCSP response must be recent (typically within 7 days)
- The server must be configured to staple OCSP responses
Security Considerations
Privacy Concerns
Traditional OCSP has significant privacy implications:
- Surveillance: OCSP responders can track which sites users visit
- Correlation: Multiple requests can be correlated to identify users
- ISP Monitoring: Network operators can monitor OCSP traffic
OCSP stapling addresses these concerns by keeping OCSP queries server-side.
Performance and Reliability
OCSP introduces several performance considerations:
- Latency: Additional network round-trips can slow down connections
- Availability: OCSP responders can be unavailable or slow
- Timeout Handling: Clients must handle OCSP timeouts gracefully
- Caching: Proper caching strategies are essential
Security Best Practices
- Use OCSP Stapling: Implement stapling to improve privacy and performance
- Soft Fail: Don't fail connections if OCSP is unavailable
- Proper Timeouts: Set appropriate timeouts for OCSP requests
- Fallback Mechanisms: Have fallback options when OCSP fails
- Monitor OCSP Health: Monitor OCSP responder availability
Implementation Guide
Let's implement a comprehensive certificate validation system that checks both the chain of trust and revocation status using OCSP. This implementation will be more robust and include proper error handling.
High-Level Design
Our implementation will follow this structure:
def validate_certificate(cert: bytes, check_revocation: bool = True):
"""
Comprehensive certificate validation including chain of trust and OCSP revocation checking.
Args:
cert: Certificate bytes in DER format
check_revocation: Whether to perform OCSP revocation checking
Returns:
dict: Validation results with detailed status information
"""
result = {
'valid': False,
'chain_valid': False,
'revocation_valid': None,
'errors': [],
'warnings': []
}
# Step 1: Validate chain of trust
try:
result['chain_valid'] = verify_chain_of_trust(cert)
if not result['chain_valid']:
result['errors'].append('Certificate chain validation failed')
except Exception as e:
result['errors'].append(f'Chain validation error: {str(e)}')
# Step 2: Check revocation status if requested
if check_revocation and result['chain_valid']:
try:
result['revocation_valid'] = verify_ocsp_revocation(cert)
if not result['revocation_valid']:
result['errors'].append('Certificate is revoked or OCSP check failed')
except Exception as e:
result['warnings'].append(f'OCSP check failed: {str(e)}')
# Soft fail: don't invalidate certificate if OCSP fails
# Overall validation result
result['valid'] = result['chain_valid'] and (result['revocation_valid'] is not False)
return result
Certificate Information Extraction
First, let's create robust functions to extract certificate information:
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtensionOID
from cryptography.hazmat.backends import default_backend
import requests
import logging
def get_certificate_info(cert_bytes: bytes) -> dict:
"""
Extract comprehensive information from a certificate.
Args:
cert_bytes: Certificate in DER format
Returns:
dict: Certificate information including subject, issuer, OCSP URL, etc.
"""
try:
cert = x509.load_der_x509_certificate(cert_bytes, default_backend())
info = {
'subject': get_common_name(cert.subject),
'issuer': get_common_name(cert.issuer),
'serial_number': cert.serial_number,
'not_before': cert.not_valid_before,
'not_after': cert.not_valid_after,
'ocsp_url': get_ocsp_url(cert),
'crl_url': get_crl_url(cert),
'is_self_signed': cert.subject == cert.issuer,
'key_usage': get_key_usage(cert),
'extended_key_usage': get_extended_key_usage(cert)
}
return info
except Exception as e:
logging.error(f"Error extracting certificate info: {e}")
raise
def get_common_name(name):
"""Extract common name from X509 name."""
try:
return name.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
except IndexError:
return str(name)
def get_ocsp_url(cert):
"""Extract OCSP URL from certificate Authority Information Access extension."""
try:
aia = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS)
for access_description in aia.value:
if access_description.access_method._name == 'OCSP':
url = access_description.access_location.value
# Convert HTTPS to HTTP for OCSP (common practice)
return url.replace('https://', 'http://')
except x509.extensions.ExtensionNotFound:
pass
return None
def get_crl_url(cert):
"""Extract CRL URL from certificate CRL Distribution Points extension."""
try:
crl_dp = cert.extensions.get_extension_for_oid(ExtensionOID.CRL_DISTRIBUTION_POINTS)
for dp in crl_dp.value:
for name in dp.full_name:
if name.type == x509.DNSName:
return f"http://{name.value}"
except x509.extensions.ExtensionNotFound:
pass
return None
def get_key_usage(cert):
"""Extract key usage from certificate."""
try:
ku = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE)
return [flag for flag, value in ku.value.__dict__.items() if value]
except x509.extensions.ExtensionNotFound:
return []
def get_extended_key_usage(cert):
"""Extract extended key usage from certificate."""
try:
eku = cert.extensions.get_extension_for_oid(ExtensionOID.EXTENDED_KEY_USAGE)
return [usage._name for usage in eku.value]
except x509.extensions.ExtensionNotFound:
return []
Chain of Trust Validation
Enhanced chain validation with better error handling:
from OpenSSL import crypto
import tempfile
import os
def verify_chain_of_trust(cert_bytes: bytes, trusted_certs: list = None) -> bool:
"""
Verify the certificate chain of trust.
Args:
cert_bytes: Certificate to validate in DER format
trusted_certs: List of trusted certificates (intermediates and roots)
Returns:
bool: True if chain is valid, False otherwise
"""
try:
# Load the certificate to validate
cert = crypto.load_certificate(crypto.FILETYPE_ASN1, cert_bytes)
# Create certificate store
store = crypto.X509Store()
# Add trusted certificates to store
if trusted_certs:
for trusted_cert_bytes in trusted_certs:
try:
trusted_cert = crypto.load_certificate(crypto.FILETYPE_ASN1, trusted_cert_bytes)
store.add_cert(trusted_cert)
except Exception as e:
logging.warning(f"Failed to add trusted certificate: {e}")
# Create store context and verify
store_ctx = crypto.X509StoreContext(store, cert)
result = store_ctx.verify_certificate()
# None means verification succeeded
return result is None
except Exception as e:
logging.error(f"Chain validation error: {e}")
return False
def load_certificate_chain(cert_file_path: str) -> dict:
"""
Load and parse a certificate chain from a PEM file.
Args:
cert_file_path: Path to PEM file containing certificate chain
Returns:
dict: Dictionary mapping subject names to certificate information
"""
certs = {}
try:
with open(cert_file_path, 'rb') as f:
pem_data = f.read()
# Parse multiple certificates from PEM
for cert_num, cert_type, cert_bytes in pem.unarmor(pem_data, multiple=True):
if cert_type == 'CERTIFICATE':
cert_info = get_certificate_info(cert_bytes)
certs[cert_info['subject']] = {
'bytes': cert_bytes,
'info': cert_info
}
return certs
except Exception as e:
logging.error(f"Error loading certificate chain: {e}")
raise
OCSP Revocation Checking
Comprehensive OCSP implementation with proper error handling and timeouts:
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
import struct
import time
class OCSPChecker:
def __init__(self, timeout: int = 10, max_retries: int = 3):
self.timeout = timeout
self.max_retries = max_retries
self.session = requests.Session()
def check_certificate_revocation(self, cert_bytes: bytes, issuer_bytes: bytes) -> dict:
"""
Check certificate revocation status via OCSP.
Args:
cert_bytes: Certificate to check in DER format
issuer_bytes: Issuer certificate in DER format
Returns:
dict: OCSP response with status and details
"""
try:
# Load certificates
cert = x509.load_der_x509_certificate(cert_bytes, default_backend())
issuer = x509.load_der_x509_certificate(issuer_bytes, default_backend())
# Get OCSP URL
ocsp_url = get_ocsp_url(cert)
if not ocsp_url:
return {
'status': 'unknown',
'reason': 'No OCSP URL found in certificate',
'valid': False
}
# Build OCSP request
ocsp_request = self._build_ocsp_request(cert, issuer)
# Send request with retries
response = self._send_ocsp_request(ocsp_url, ocsp_request)
# Parse response
return self._parse_ocsp_response(response)
except Exception as e:
logging.error(f"OCSP check failed: {e}")
return {
'status': 'error',
'reason': str(e),
'valid': False
}
def _build_ocsp_request(self, cert: x509.Certificate, issuer: x509.Certificate) -> bytes:
"""Build OCSP request for certificate validation."""
from cryptography.hazmat.primitives.asymmetric import utils
# Create OCSP request
builder = x509.ocsp.OCSPRequestBuilder()
builder = builder.add_certificate(
cert.public_bytes(serialization.Encoding.DER),
issuer.public_bytes(serialization.Encoding.DER),
hashes.SHA256()
)
# Add nonce for replay protection
nonce = os.urandom(16)
builder = builder.add_extension(
x509.ocsp.OCSPNonce(nonce),
critical=False
)
return builder.build().public_bytes(serialization.Encoding.DER)
def _send_ocsp_request(self, url: str, request_data: bytes) -> requests.Response:
"""Send OCSP request with retry logic."""
headers = {
'Content-Type': 'application/ocsp-request',
'Accept': 'application/ocsp-response'
}
for attempt in range(self.max_retries):
try:
response = self.session.post(
url,
data=request_data,
headers=headers,
timeout=self.timeout
)
if response.status_code == 200:
return response
else:
logging.warning(f"OCSP request failed with status {response.status_code}")
except requests.exceptions.RequestException as e:
logging.warning(f"OCSP request attempt {attempt + 1} failed: {e}")
if attempt == self.max_retries - 1:
raise
raise Exception(f"OCSP request failed after {self.max_retries} attempts")
def _parse_ocsp_response(self, response: requests.Response) -> dict:
"""Parse OCSP response and extract status information."""
try:
ocsp_response = x509.ocsp.load_der_ocsp_response(response.content)
# Check response status
if ocsp_response.response_status != x509.ocsp.OCSPResponseStatus.SUCCESSFUL:
return {
'status': 'error',
'reason': f'OCSP response status: {ocsp_response.response_status}',
'valid': False
}
# Get certificate status
cert_status = ocsp_response.certificate_status
if isinstance(cert_status, x509.ocsp.OCSPCertStatus.GOOD):
return {
'status': 'good',
'reason': 'Certificate is valid and not revoked',
'valid': True,
'this_update': ocsp_response.this_update,
'next_update': ocsp_response.next_update
}
elif isinstance(cert_status, x509.ocsp.OCSPCertStatus.REVOKED):
return {
'status': 'revoked',
'reason': f'Certificate revoked at {cert_status.revocation_time}',
'valid': False,
'revocation_time': cert_status.revocation_time,
'revocation_reason': cert_status.revocation_reason
}
else:
return {
'status': 'unknown',
'reason': 'Certificate status unknown',
'valid': False
}
except Exception as e:
logging.error(f"Error parsing OCSP response: {e}")
return {
'status': 'error',
'reason': f'Failed to parse OCSP response: {e}',
'valid': False
}
def verify_ocsp_revocation(cert_bytes: bytes, cert_store: dict = None) -> bool:
"""
Verify certificate revocation status through the entire chain.
Args:
cert_bytes: Certificate to check
cert_store: Dictionary of certificates in the chain
Returns:
bool: True if no certificates are revoked, False otherwise
"""
if not cert_store:
cert_store = load_certificate_chain('./cc_certs/PT.pem')
ocsp_checker = OCSPChecker()
cert_info = get_certificate_info(cert_bytes)
# Check each certificate in the chain
current_cert_bytes = cert_bytes
current_cert_info = cert_info
while True:
# Get issuer certificate
issuer_name = current_cert_info['issuer']
if issuer_name not in cert_store:
logging.warning(f"Issuer certificate '{issuer_name}' not found in store")
return False
issuer_cert = cert_store[issuer_name]
issuer_bytes = issuer_cert['bytes']
# Check revocation status
ocsp_result = ocsp_checker.check_certificate_revocation(
current_cert_bytes,
issuer_bytes
)
if not ocsp_result['valid']:
logging.error(f"Certificate '{current_cert_info['subject']}' revocation check failed: {ocsp_result['reason']}")
return False
# Stop if we've reached a self-signed certificate
if current_cert_info['is_self_signed']:
break
# Move to next certificate in chain
current_cert_bytes = issuer_bytes
current_cert_info = issuer_cert['info']
return True
Complete Implementation Example
Here's a complete example showing how to use the enhanced certificate validation system:
def main():
"""Example usage of the certificate validation system."""
import json
# Load certificate store
try:
cert_store = load_certificate_chain('./cc_certs/PT.pem')
print(f"Loaded {len(cert_store)} certificates from store")
except Exception as e:
print(f"Failed to load certificate store: {e}")
return
# Example: Validate a specific certificate
# Replace with actual certificate bytes
example_cert_subject = 'Cartão de Cidadão 001'
if example_cert_subject in cert_store:
cert_bytes = cert_store[example_cert_subject]['bytes']
# Perform comprehensive validation
result = validate_certificate(cert_bytes, check_revocation=True)
# Print results
print("\nCertificate Validation Results:")
print(f"Overall Valid: {result['valid']}")
print(f"Chain Valid: {result['chain_valid']}")
print(f"Revocation Valid: {result['revocation_valid']}")
if result['errors']:
print("\nErrors:")
for error in result['errors']:
print(f" - {error}")
if result['warnings']:
print("\nWarnings:")
for warning in result['warnings']:
print(f" - {warning}")
# Print detailed certificate information
cert_info = cert_store[example_cert_subject]['info']
print(f"\nCertificate Details:")
print(f" Subject: {cert_info['subject']}")
print(f" Issuer: {cert_info['issuer']}")
print(f" Valid From: {cert_info['not_before']}")
print(f" Valid Until: {cert_info['not_after']}")
print(f" OCSP URL: {cert_info['ocsp_url']}")
print(f" CRL URL: {cert_info['crl_url']}")
print(f" Is Self-Signed: {cert_info['is_self_signed']}")
else:
print(f"Certificate '{example_cert_subject}' not found in store")
if __name__ == "__main__":
main()
Real-World Implementation Considerations
Most modern web servers support OCSP stapling. Here are configuration examples:
Apache Configuration
# Enable OCSP stapling SSLUseStapling on SSLStaplingCache shmcb:/var/run/ocsp(128000) SSLStaplingResponderTimeout 5 SSLStaplingReturnResponderErrors off SSLStaplingResponseMaxAge 86400
Nginx Configuration
# Enable OCSP stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /path/to/trusted_certs.pem; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s;
OpenSSL Configuration
# Test OCSP stapling with OpenSSL openssl s_client -connect example.com:443 -status -servername example.com
Browser Behavior
Different browsers handle OCSP differently:
- Chrome: Uses OCSP stapling when available, falls back to CRL
- Firefox: Similar to Chrome, with configurable OCSP settings
- Safari: Uses OCSP stapling and has strict OCSP requirements
- Edge: Follows Windows certificate validation policies
Conclusion
Certificate revocation via OCSP is a critical component of a secure PKI infrastructure. While it has some limitations and privacy concerns, OCSP stapling addresses many of these issues and provides a robust solution for real-time certificate validation.
When implementing OCSP in production systems, remember to:
- Use OCSP stapling whenever possible
- Implement proper error handling and fallback mechanisms
- Monitor system health and performance
- Follow security best practices
- Keep up with evolving standards and recommendations
The implementation provided in this tutorial serves as a foundation for building robust certificate validation systems. Adapt it to your specific requirements and always test thoroughly in your environment.