OCSP
Overview
The Online Certificate Status Protocol (RFC 6960) allows a client to query a responder for the revocation status of a specific certificate. This module covers the full client-side stack: building requests, decoding responses, verifying signatures, and reading per-certificate status.
HTTP transport is out of scope — the caller is responsible for sending the DER-encoded request and receiving the DER-encoded response.
Types provided:
| Type | Description |
|---|---|
[OcspCertId] | Certificate identifier (OCSP_CERTID*) built from a subject + issuer pair |
[OcspRequest] | OCSP request (OCSP_REQUEST*) |
[OcspResponse] | Top-level OCSP response (OCSP_RESPONSE*) |
[OcspBasicResp] | Signed inner response (OCSP_BASICRESP*) |
[OcspSingleResp] | Owned single-response entry (OCSP_SINGLERESP*) |
[BorrowedOcspSingleResp] | Borrowed view of a single-response entry; provides status() |
[SingleRespStatus] | Status result from BorrowedOcspSingleResp::status() |
[OcspResponseStatus] | Top-level response status enum |
[OcspCertStatus] | Per-certificate revocation status (Good / Revoked / Unknown) |
[OcspRevokeReason] | CRL revocation reason code enum |
[OcspSingleStatus] | Full per-certificate result with timestamps |
OcspCertId — Certificate Identifier
#![allow(unused)]
fn main() {
pub struct OcspCertId { /* OCSP_CERTID* */ }
impl OcspCertId {
/// Allocate a new, empty OCSP_CERTID (all fields zeroed).
pub fn new() -> Result<Self, ErrorStack>;
/// Build a cert ID from a subject certificate and its direct issuer.
///
/// Pass `None` for `digest` to use SHA-1 (the OCSP default, required by
/// most deployed responders).
pub fn from_cert(
digest: Option<&DigestAlg>,
subject: &X509,
issuer: &X509,
) -> Result<Self, ErrorStack>;
}
impl Clone for OcspCertId { /* OCSP_CERTID_dup */ }
}
OcspRequest
#![allow(unused)]
fn main() {
pub struct OcspRequest { /* OCSP_REQUEST* */ }
impl OcspRequest {
pub fn new() -> Result<Self, ErrorStack>;
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
/// Add a certificate ID (consumes `cert_id` — add0 semantics).
pub fn add_cert_id(&mut self, cert_id: OcspCertId) -> Result<(), ErrorStack>;
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack>;
}
}
OcspResponse
#![allow(unused)]
fn main() {
pub struct OcspResponse { /* OCSP_RESPONSE* */ }
impl OcspResponse {
/// Allocate a new, empty OCSP_RESPONSE (responder side).
pub fn new() -> Result<Self, ErrorStack>;
pub fn from_der(der: &[u8]) -> Result<Self, ErrorStack>;
pub fn to_der(&self) -> Result<Vec<u8>, ErrorStack>;
/// Top-level packet status (not per-certificate status).
pub fn status(&self) -> OcspResponseStatus;
/// Extract the signed inner response for signature verification and
/// per-certificate status lookup.
pub fn basic(&self) -> Result<OcspBasicResp, ErrorStack>;
/// Convenience: verify signature + find_status in one call.
pub fn verified_status(
&self,
store: &X509Store,
cert_id: &OcspCertId,
) -> Result<Option<OcspSingleStatus>, ErrorStack>;
}
}
OcspBasicResp
#![allow(unused)]
fn main() {
pub struct OcspBasicResp { /* OCSP_BASICRESP* */ }
impl OcspBasicResp {
/// Allocate a new, empty OCSP_BASICRESP (responder side).
pub fn new() -> Result<Self, ErrorStack>;
/// Verify the response signature against `store`.
///
/// Pass `flags = 0` for default behaviour.
pub fn verify(&self, store: &X509Store, flags: u64) -> Result<bool, ErrorStack>;
/// Number of SingleResponse entries.
pub fn count(&self) -> usize;
/// Get the SingleResponse at position `idx`.
///
/// Returns `None` if `idx` is out of range.
pub fn get_response(&self, idx: usize) -> Option<BorrowedOcspSingleResp<'_>>;
/// Find the index of the first SingleResponse matching `id`.
///
/// Returns `Some(idx)` or `None` if not found.
pub fn find_response(&self, id: &OcspCertId) -> Option<usize>;
/// Look up status for a specific certificate.
///
/// Returns `Ok(None)` if the certificate is not in this response.
pub fn find_status(&self, cert_id: &OcspCertId)
-> Result<Option<OcspSingleStatus>, ErrorStack>;
/// Validate the thisUpdate / nextUpdate time window of a SingleResponse.
///
/// `sec` — acceptable clock skew in seconds (typically 300).
/// `maxsec` — maximum age of nextUpdate in seconds (-1 = no limit).
pub fn check_validity(
&self,
cert_id: &OcspCertId,
sec: i64,
maxsec: i64,
) -> Result<bool, ErrorStack>;
}
}
BorrowedOcspSingleResp — Borrowed Single Response Entry
#![allow(unused)]
fn main() {
/// Borrowed OCSP_SINGLERESP* whose lifetime is tied to its parent OcspBasicResp.
pub struct BorrowedOcspSingleResp<'a> { /* ... */ }
impl BorrowedOcspSingleResp<'_> {
/// Extract revocation status, reason, and timestamps from this entry.
pub fn status(&self) -> Result<SingleRespStatus, ErrorStack>;
}
/// Status of a single certificate entry.
pub struct SingleRespStatus {
pub cert_status: OcspCertStatus,
pub reason: Option<OcspRevokeReason>,
pub this_update: Option<String>,
pub next_update: Option<String>,
pub revocation_time: Option<String>,
}
}
Obtain a BorrowedOcspSingleResp via [OcspBasicResp::get_response]:
let basic = resp.basic()?;
if let Some(entry) = basic.get_response(0) {
let s = entry.status()?;
println!("cert status: {:?}", s.cert_status);
}
Status Enums and Structs
#![allow(unused)]
fn main() {
/// Top-level OCSP response packet status (RFC 6960 §4.2.1).
pub enum OcspResponseStatus {
Successful, // 0 — server produced a response
MalformedRequest, // 1
InternalError, // 2
TryLater, // 3
SigRequired, // 5
Unauthorized, // 6
Unknown(i32),
}
/// Per-certificate revocation status from a SingleResponse.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspCertStatus {
Good, // V_OCSP_CERTSTATUS_GOOD = 0
Revoked, // V_OCSP_CERTSTATUS_REVOKED = 1
Unknown, // V_OCSP_CERTSTATUS_UNKNOWN = 2
}
/// CRL revocation reason codes (RFC 5280 §5.3.1).
/// None = no reason given (OCSP_REVOKED_STATUS_NOSTATUS = -1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OcspRevokeReason {
Unspecified, // 0
KeyCompromise, // 1
CaCompromise, // 2
AffiliationChanged, // 3
Superseded, // 4
CessationOfOperation,// 5
CertificateHold, // 6
RemoveFromCrl, // 8
PrivilegeWithdrawn, // 9
AaCompromise, // 10
Other(i32), // forward-compatibility guard
}
/// Full per-certificate result returned by OcspBasicResp::find_status.
pub struct OcspSingleStatus {
pub cert_status: OcspCertStatus,
pub reason: Option<OcspRevokeReason>, // Some only when Revoked
pub this_update: Option<String>, // "Apr 15 15:00:00 2026 GMT"
pub next_update: Option<String>,
pub revocation_time: Option<String>, // Some only when Revoked
}
}
Examples
Full Client Flow
use native_ossl::ocsp::{OcspCertId, OcspRequest, OcspResponse, OcspResponseStatus, OcspCertStatus};
use native_ossl::x509::{X509, X509Store};
// Load the end-entity cert and its issuer.
let ee_cert = X509::from_pem(&ee_pem)?;
let issuer = X509::from_pem(&issuer_pem)?;
// Build a certificate identifier (SHA-1 is the OCSP default).
let cert_id = OcspCertId::from_cert(None, &ee_cert, &issuer)?;
// Build the request.
let mut req = OcspRequest::new()?;
req.add_cert_id(cert_id.clone())?; // clone because we reuse cert_id below
let req_der = req.to_der()?;
// ... send req_der via HTTP POST to the OCSP responder URL
// ... receive resp_der from the HTTP response body
// Parse the response.
let resp = OcspResponse::from_der(&resp_der)?;
if resp.status() != OcspResponseStatus::Successful {
return Err(format!("OCSP error: {:?}", resp.status()).into());
}
// Verify the signature and look up the certificate status.
let mut store = X509Store::new()?;
store.add_cert(&issuer)?;
let status = resp.verified_status(&store, &cert_id)?;
match status {
None => println!("certificate not in OCSP response"),
Some(s) => match s.cert_status {
OcspCertStatus::Good => println!("certificate is good (valid until {:?})", s.next_update),
OcspCertStatus::Revoked => println!("REVOKED (reason {:?})", s.reason),
OcspCertStatus::Unknown => println!("responder does not know this certificate"),
},
}
Validate the Time Window
use native_ossl::ocsp::{OcspCertId, OcspResponse};
let cert_id = OcspCertId::from_cert(None, &ee_cert, &issuer)?;
let resp = OcspResponse::from_der(&resp_der)?;
let basic = resp.basic()?;
// Allow 5 minutes of clock skew; no limit on nextUpdate age.
if !basic.check_validity(&cert_id, 300, -1)? {
return Err("OCSP response is outside its validity window".into());
}
Design Notes
- HTTP transport is out of scope — the caller supplies the transport layer. The OCSP responder URL is found in the certificate’s Authority Information Access (AIA) extension.
add_cert_iduses add0 semantics — ownership of theOcspCertIdis transferred to the request. Useclone()before callingadd_cert_idif you need the cert ID afterwards (e.g. to pass it tofind_status).basic()allocates —OCSP_response_get1_basicallocates a newOCSP_BASICRESP; theOcspBasicRespowns it and frees on drop.basic_verifyflags — common values:OCSP_NOCERTS(skip bundled certs),OCSP_NOVERIFY(skip signature check),OCSP_NOCHAIN. Pass 0 for the default behaviour.- No
Cloneon Request/Response —OCSP_REQUESTandOCSP_RESPONSEhave noup_ref; useto_der+from_derfor a second copy. OcspCertIdisClone— implemented viaOCSP_CERTID_dup.- Responder-side constructors —
OcspBasicResp::new(),OcspCertId::new(), andOcspResponse::new()allocate empty objects for building responses from scratch on the responder side. OcspCertStatusis nowCopy— the revocation reason has been moved toOcspRevokeReasonand is returned separately inOcspSingleStatus::reason.BorrowedOcspSingleRespdoes not free — the pointer is owned by the parentOcspBasicResp;DroponOcspSingleRespis a no-op. The borrow lifetime enforces that the parent is not dropped while the view is live.get_responsevsfind_status—get_response(idx)+status()is the low-level index-based path;find_status(cert_id)is the higher-level cert-ID-based path and is preferred for most client-side code.