← Week 2: Revocation: CRL and OCSP

Day 12: OCSP Response Construction

Phase 2 · June 28, 2026

← Week 2: Revocation: CRL and OCSP

Agenda (2–3 hours)

  • Read (45 min): RFC 6960 §4.2 (OCSPResponse ASN.1); der crate encoding guide
  • Build (120 min): Implement build_ocsp_response() using the der crate
  • Verify (30 min): Parse the response with openssl and x509-parser
← Week 2: Revocation: CRL and OCSP

OCSP Response ASN.1 (RFC 6960 §4.2)

OCSPResponse ::= SEQUENCE {
   responseStatus     OCSPResponseStatus,  -- successful(0), etc.
   responseBytes  [0] EXPLICIT ResponseBytes OPTIONAL
}

ResponseBytes ::= SEQUENCE {
   responseType   OBJECT IDENTIFIER,  -- id-pkix-ocsp-basic
   response       OCTET STRING        -- DER of BasicOCSPResponse
}

BasicOCSPResponse ::= SEQUENCE {
   tbsResponseData   ResponseData,
   signatureAlgorithm AlgorithmIdentifier,
   signature         BIT STRING,
   certs         [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL
}

ResponseData ::= SEQUENCE {
   version         [0] EXPLICIT INTEGER DEFAULT v1,
   responderID         ResponderID,
   producedAt          GeneralizedTime,
   responses           SEQUENCE OF SingleResponse,
   responseExtensions  [1] EXPLICIT Extensions OPTIONAL
}

SingleResponse ::= SEQUENCE {
   certID     CertID,
   certStatus CertStatus,  -- good | revoked | unknown
   thisUpdate GeneralizedTime,
   nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
   singleExtensions [1] EXPLICIT Extensions OPTIONAL
}
← Week 2: Revocation: CRL and OCSP

Building with the der Crate

The der crate provides Rust types for encoding ASN.1/DER:

use der::{Encode, Sequence};
use der::asn1::{GeneralizedTime, OctetString, BitString, Null};

// For a "good" cert status:
// CertStatus ::= CHOICE { good [0] IMPLICIT NULL, revoked ..., unknown ... }
// "good" is encoded as context tag 0 + NULL = 0x80 0x00

For a toy PKI responder, you can build the simpler parts manually using
der::Encode on structs you define, or hand-encode the DER bytes for the
cert status field which is awkward due to CHOICE + IMPLICIT tagging.

← Week 2: Revocation: CRL and OCSP

Practical Approach: Use ocsp Crate

The ocsp crate (0.2.x) provides higher-level OCSP types built on der:

ocsp = "0.2"
use ocsp::response::{
    BasicOcspResponse, OcspResponse, OcspResponseStatus,
    ResponderId, SingleResponse, CertStatus,
};

// Build a BasicOCSPResponse for a "good" cert
let single = SingleResponse::new(
    cert_id,
    CertStatus::Good,
    this_update,
    Some(next_update),
)?;

If the ocsp crate API is too unstable: hand-construct the DER using der.
Both are learning opportunities. Pick one and document why.

← Week 2: Revocation: CRL and OCSP

Signing the Response

The BasicOCSPResponse must be signed by the CA (or a delegated responder).

// The tbsResponseData bytes must be signed
let tbs_der = tbs_response_data.to_der()?;
let signature = issuer_key.sign(&tbs_der)?; // use ring or rcgen signing

// Pack into BasicOCSPResponse
let basic = BasicOCSPResponse {
    tbs_response_data,
    signature_algorithm: AlgorithmIdentifier { oid: ECDSA_P256_SHA256_OID, .. },
    signature: BitString::new(0, signature)?,
    certs: Some(vec![issuer_cert_der]),  // include CA cert for responder verification
};
← Week 2: Revocation: CRL and OCSP

Challenge Assignment

Implement build_ocsp_response() in revoke.rs:

pub fn build_ocsp_response(
    cert_id: &CertId,
    status: &RevocationStatus,
    issuer: &Ca,
) -> anyhow::Result<Vec<u8>>  // returns DER bytes

Test it:

  1. Build a "good" response, verify with: openssl ocsp -respin resp.der -text
  2. Build a "revoked" response (with reason=keyCompromise), verify revocation fields
  3. Parse your response with x509-parser's OCSP support and assert fields match
← Week 2: Revocation: CRL and OCSP

Resources

  • RFC 6960 §4.2: OCSPResponse and BasicOCSPResponse ASN.1
  • der crate: docs.rs/der — Encode, Sequence, OctetString
  • ocsp crate: docs.rs/ocsp — if available and stable enough
  • openssl-ocsp(1): -respin to inspect a response DER file