Companion service · RFC 6960 revocation

OCSP responder — RFC 6960 revocation for CodeB signing certs.

Every CodeB tenant runs an in-house RFC 6960 OCSP responder at POST /csc/v2/ocsp. It answers CertID queries for every certificate the tenant issues — per-user signing certs, the TSA responder cert, and the OIDC / VCI issuer certs — and returns a BasicOCSPResponse signed by the tenant's own responder key. The browser signer (sign.html) uses this endpoint to embed id-aa-ets-revocationValues inside every PAdES-B-LT and PAdES-B-LTA envelope, so relying parties can verify signatures offline after the signer's cert expires.

Scope reminder. This responder is self-signed by the tenant — it is not a qualified OCSP responder under Regulation (EU) 910/2014. It exists to complete the local trust story for the in-tenant PKI: the CodeB-issued certs point back to a CodeB-signed OCSP responder, and the whole chain is verifiable without reaching any external service.

POST /csc/v2/ocsp #

Full RFC 6960 wire protocol. No Bearer required — RFC 6960 §A.1 describes the OCSP request/response over HTTP and by long-standing convention responders are open. Host-level rate limits still apply (30 req/min per action per IP).

Request

Content-Type:   application/ocsp-request
Body:           DER-encoded OCSPRequest (RFC 6960 s4.1.1)
                (SEQUENCE tbsRequest, optional SEQUENCE optionalSignature)
                tbsRequest.requestList contains one or more Request
                    { CertID reqCert, extensions singleRequestExtensions? }

Response

Content-Type:   application/ocsp-response
Body:           DER-encoded OCSPResponse (RFC 6960 s4.2.1)
                { responseStatus INTEGER,
                  responseBytes  [0] EXPLICIT ResponseBytes OPTIONAL }
                ResponseBytes.responseType == id-pkix-ocsp-basic (1.3.6.1.5.5.7.48.1.1)
                ResponseBytes.response is the DER of BasicOCSPResponse.

Where BasicOCSPResponse is:

BasicOCSPResponse ::= SEQUENCE {
  tbsResponseData      ResponseData,     -- who, what, when
  signatureAlgorithm   AlgorithmIdentifier,
  signature            BIT STRING,       -- ECDSA-with-SHA-256
  certs            [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL
}

ResponseData ::= SEQUENCE {
  version              [0] EXPLICIT INTEGER DEFAULT v1,
  responderID          CHOICE { byName [1] Name, byKey [2] KeyHash },
  producedAt           GeneralizedTime,
  responses            SEQUENCE OF SingleResponse,
  responseExtensions   [1] EXPLICIT Extensions OPTIONAL
}

SingleResponse ::= SEQUENCE {
  certID               CertID,
  certStatus           CHOICE { good [0] NULL, revoked [1] RevokedInfo, unknown [2] UnknownInfo },
  thisUpdate           GeneralizedTime,
  nextUpdate       [0] EXPLICIT GeneralizedTime OPTIONAL,
  singleExtensions [1] EXPLICIT Extensions OPTIONAL
}

Example

openssl ocsp \
  -reqin  request.ocsp \
  -respout response.ocsp \
  -url    https://phone.aloaha.com/csc/v2/ocsp \
  -noverify -text

What certificates are covered #

On startup and on cache miss, the responder scans four folders under App_Data/<tenant>/ and indexes every *.pfx or *.pem it finds:

  • csc-signer-certs/ — per-user EC P-256 signing certs (Phase 1b credentials from the browser signer)
  • signing-cert/ — the shared tenant-wide fallback signer (used when Csc:PerUserCerts=false)
  • tsa-cert/ — the tenant's RFC 3161 TSA responder cert (EKU id-kp-timeStamping)
  • vci/ — the PID / SD-JWT VC issuer cert used by vci.ashx

The index is keyed by the SHA-1 issuerNameHash + serialNumber tuple — the same CertID a requester sends. A miss returns certStatus = unknown per RFC 6960 §4.2.2. This is a deliberate choice: we would rather say "we don't know about that cert" than lie and say "good".

Refresh

The cache reloads on every request older than 60 seconds and on tenant restart. Newly-issued per-user signing certs are picked up on the next request; there is no separate "publish to OCSP" step. New TSA certs (from the auto-generated TSA issuer) are picked up the same way.

RFC 5019 lightweight profile #

Requests use SHA-1 CertID hashes per RFC 5019 for interoperability with off-the-shelf OCSP clients. The responder normalises algorithm mismatches (SHA-256, SHA-1) internally so a requester that follows either RFC 6960 or RFC 5019 gets a valid answer.

Nonce extension (RFC 6960 §4.4.1) is echoed when present; absent when the request does not carry one. Responses are freshly built per request (no caching of signed responses) so producedAt always reflects real time.

PAdES-B-LT use case

The browser signer fetches OCSP for both the signer cert and the TSA cert immediately after the RFC 3161 timestamp comes back, then embeds both as id-aa-ets-revocationValues.

  • Signer OCSP fetch
  • TSA OCSP fetch
  • Bundle into CMS unsignedAttrs
  • Relying party validates offline

Direct client use case

Any RFC 6960 client (openssl, xmlseclibs, iText, pyHanko) can point at the endpoint. It speaks the wire protocol; no CSC-specific glue required.

  • Build DER OCSPRequest
  • POST to /csc/v2/ocsp
  • Parse DER OCSPResponse
  • Trust chain: pin the tenant's OCSP responder cert

Responder key + certificate #

The responder signs with an EC P-256 key using ECDSA-with-SHA-256 (1.2.840.10045.4.3.2). The response's certs [0] optional element embeds the responder's own certificate so verifiers can chain-build offline.

Delegated signing

By default the responder is the tenant CA: ResponderID.byKey points at the tenant CA's SubjectPublicKeyInfo hash, and it signs directly. This matches RFC 6960 §4.2.2.2 delegated-responder rules trivially, because the CA and the responder key are the same key.

Operator note: a future release will let you point Ocsp:ResponderCert=<path> at a separate delegated responder cert (issued by the CA and carrying EKU id-kp-OCSPSigning) so day-to-day OCSP signing does not use the root CA key. The current setup is acceptable for AdES scope; QES scope would require the delegated model.

Auto-generation

If App_Data/<tenant>/signing-cert/tenant-ca.pfx is missing at startup, the tenant generates a fresh EC P-256 CA cert (5-year validity, BasicConstraints CA:TRUE critical, KeyUsage keyCertSign+cRLSign+digitalSignature) and uses that for both issuance and OCSP signing.

Where revocation state comes from #

Revocation is a flat file: App_Data/<tenant>/csc-revoked-serials.json. Each entry is {serial_hex, reason, revoked_at}. The responder consults this list before answering; a hit returns certStatus = revoked with a revocationTime; a miss returns good for known certs and unknown for anything the responder does not know about.

Revoke a cert

POST /admin.ashx?action=csc-revoke-serial
Authorization: Bearer <admin token>
Content-Type: application/json

{ "serial": "00edf255b159a07145",
  "reason": "keyCompromise",     /* RFC 5280 CRLReason */
  "revoked_at": "2026-07-27T18:31:19Z" }

The list is loaded lazily and re-read on modification-time change. There is no separate CRL to publish; the whole revocation surface is this one endpoint.

Reason codes

Per RFC 5280 §5.3.1: unspecified (0), keyCompromise (1), cACompromise (2), affiliationChanged (3), superseded (4), cessationOfOperation (5), certificateHold (6, reversible), privilegeWithdrawn (9), aACompromise (10).

Observability + rate limiting #

Every OCSP request emits diagnostic trace lines:

[CSC-OCSP-DIAG] tenant=<t> ct=application/ocsp-request len=76 issuerHash8=... serialHex=00edf255...
[CSC-OCSP-DIAG] tenant=<t> cache index=42 hit=1 status=good producedAt=2026-07-27T18:31:19Z
[METRIC] csc.ocsp.answered tenant=<t> status=good cert_source=csc-signer-certs

Metrics tags: status = good|revoked|unknown; cert_source = one of the four scanned directories. Rejections emit [METRIC] csc.ocsp.rejected reason=<badRequest|malformedRequest|internalError|tryLater|sigRequired|unauthorized> aligning with the six RFC 6960 responseStatus codes.

Rate limit

Same per-IP bucket as the rest of /csc.ashx: 30 requests per minute per action. Over-limit returns HTTP 429 with a Retry-After: 30 header and a tryLater OCSP body.

Standards + citations #

  • RFC 6960 — X.509 Internet Public Key Infrastructure Online Certificate Status Protocol (OCSP)
  • RFC 5019 — Lightweight OCSP Profile for High-Volume Environments
  • RFC 5280 — Internet X.509 PKI Profile (CRLReason, KeyUsage, BasicConstraints on the responder cert)
  • RFC 2560 — the earlier OCSP spec (obsoleted by RFC 6960; kept for interop notes)
  • ETSI EN 319 142-1 §5.3 — how id-aa-ets-revocationValues carries OCSP responses inside PAdES-B-LT
  • Regulation (EU) 910/2014 Article 26(f), 32 — long-term validation requirements this responder helps satisfy at the AdES level (not QES; see scope reminder above)