Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

TypeDescription
[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_id uses add0 semantics — ownership of the OcspCertId is transferred to the request. Use clone() before calling add_cert_id if you need the cert ID afterwards (e.g. to pass it to find_status).
  • basic() allocatesOCSP_response_get1_basic allocates a new OCSP_BASICRESP; the OcspBasicResp owns it and frees on drop.
  • basic_verify flags — common values: OCSP_NOCERTS (skip bundled certs), OCSP_NOVERIFY (skip signature check), OCSP_NOCHAIN. Pass 0 for the default behaviour.
  • No Clone on Request/ResponseOCSP_REQUEST and OCSP_RESPONSE have no up_ref; use to_der + from_der for a second copy.
  • OcspCertId is Clone — implemented via OCSP_CERTID_dup.
  • Responder-side constructorsOcspBasicResp::new(), OcspCertId::new(), and OcspResponse::new() allocate empty objects for building responses from scratch on the responder side.
  • OcspCertStatus is now Copy — the revocation reason has been moved to OcspRevokeReason and is returned separately in OcspSingleStatus::reason.
  • BorrowedOcspSingleResp does not free — the pointer is owned by the parent OcspBasicResp; Drop on OcspSingleResp is a no-op. The borrow lifetime enforces that the parent is not dropped while the view is live.
  • get_response vs find_statusget_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.