← Week 2: Revocation: CRL and OCSP

Day 11: OCSP Request Parsing

Phase 2 · June 27, 2026

← Week 2: Revocation: CRL and OCSP

Agenda (2–3 hours)

  • Read (45 min): RFC 6960 §4.1–4.2 (OCSP request and response ASN.1 definitions)
  • Study (30 min): CertID construction; how to extract from a raw request
  • Build (90 min): Parse an OCSP request in Rust; extract CertID
← Week 2: Revocation: CRL and OCSP

OCSP Request Wire Format (RFC 6960 §4.1)

OCSPRequest ::= SEQUENCE {
   tbsRequest            TBSRequest,
   optionalSignature [0] EXPLICIT Signature OPTIONAL
}

TBSRequest ::= SEQUENCE {
   version         [0] EXPLICIT INTEGER DEFAULT v1,
   requestorName   [1] EXPLICIT GeneralName OPTIONAL,
   requestList         SEQUENCE OF Request,
   requestExtensions [2] EXPLICIT Extensions OPTIONAL
}

Request ::= SEQUENCE {
   reqCert   CertID,
   singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL
}

CertID ::= SEQUENCE {
   hashAlgorithm   AlgorithmIdentifier,  -- typically SHA-1
   issuerNameHash  OCTET STRING,         -- Hash(issuer DN, DER-encoded)
   issuerKeyHash   OCTET STRING,         -- Hash(issuer public key BIT STRING value)
   serialNumber    CertificateSerialNumber
}
← Week 2: Revocation: CRL and OCSP

Receiving an OCSP Request (HTTP)

OCSP over HTTP: RFC 6960 §A.1

POST /ocsp HTTP/1.1
Content-Type: application/ocsp-request
Content-Length: <n>

<DER-encoded OCSPRequest>

Or GET (smaller requests):

GET /ocsp/<base64url(OCSPRequest DER)> HTTP/1.1

Your Axum handler receives the raw DER bytes and needs to:

  1. Parse the OCSPRequest
  2. Extract the CertID(s) from requestList
  3. Look up each serial in the cert store
  4. Build and return an OCSPResponse
← Week 2: Revocation: CRL and OCSP

Parsing with x509-parser

x509-parser has OCSP support in the extensions module:

use x509_parser::ocsp::{OcspRequest, OcspSingleRequest};

pub fn parse_ocsp_request(der: &[u8]) -> anyhow::Result<Vec<CertId>> {
    let (_, req) = OcspRequest::from_der(der)
        .map_err(|e| anyhow::anyhow!("OCSP parse error: {e:?}"))?;

    let cert_ids = req.tbs_request
        .request_list
        .iter()
        .map(|r| CertId {
            serial_hex: hex::encode(r.req_cert.serial_number.as_ref()),
            issuer_key_hash: r.req_cert.issuer_key_hash.to_vec(),
            issuer_name_hash: r.req_cert.issuer_name_hash.to_vec(),
        })
        .collect();

    Ok(cert_ids)
}
← Week 2: Revocation: CRL and OCSP

Matching CertID to Your Store

The CertID doesn't contain the issuer's DN directly — it contains a hash of the issuer DN and issuer key. Your responder must compute the same hash from the CA cert and compare.

pub fn certid_matches_ca(cert_id: &CertId, ca: &X509Certificate) -> bool {
    // Hash the issuer's subject DN (DER-encoded)
    let name_hash = sha1(ca.subject().as_raw());
    // Hash the issuer's public key (bit string value, no tag/length)
    let key_hash = sha1(ca.public_key().subject_public_key.as_ref());

    cert_id.issuer_name_hash == name_hash &&
    cert_id.issuer_key_hash  == key_hash
}

Use sha1 from the sha1 crate (SHA-1 is required here by RFC 6960 for CertID).

← Week 2: Revocation: CRL and OCSP

Challenge Assignment

Implement parse_ocsp_request() in revoke.rs.

To test it, you need to generate a real OCSP request:

# Generate an OCSP request for one of your toy-pki certs
openssl ocsp -issuer intermediate/ca.crt \
             -cert issued/0001.pem \
             -reqout /tmp/ocsp_req.der

Write a test that:

  1. Reads /tmp/ocsp_req.der
  2. Parses it with your function
  3. Asserts the extracted serial matches cert 0001's serial
  4. Asserts certid_matches_ca() returns true for the intermediate CA
← Week 2: Revocation: CRL and OCSP

Resources

  • RFC 6960 §4.1: OCSPRequest ASN.1
  • RFC 6960 §A.1: HTTP transport for OCSP
  • x509-parser ocsp module: docs.rs/x509-parser — OcspRequest
  • sha1 crate: docs.rs/sha1
  • openssl-ocsp(1): -reqout flag to save request DER