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)

Frequently asked questions

What is the CodeB OCSP responder?

The CodeB OCSP responder is an in-house RFC 6960 revocation service that every CodeB tenant runs at POST /csc/v2/ocsp. It answers CertID queries for the certificates the tenant issues and returns a BasicOCSPResponse signed by the tenant's own responder key using ECDSA-with-SHA-256. Any standard RFC 6960 client such as openssl, iText or pyHanko can query it without CSC-specific glue.

Which certificates does the CodeB OCSP responder cover?

The CodeB OCSP responder covers per-user signing certificates from the browser signer, the shared tenant-wide fallback signer, the tenant's RFC 3161 TSA responder certificate, and the PID / SD-JWT VC issuer certificate. The responder indexes every PFX or PEM file in the corresponding four folders under App_Data/<tenant>/.

Is the CodeB OCSP responder a qualified eIDAS trust service?

No. The CodeB OCSP responder is self-signed by the tenant and is not a qualified OCSP responder under Regulation (EU) 910/2014. It completes the local trust story for the in-tenant PKI at Advanced Electronic Signature (AdES) level; the page notes that QES scope would require a delegated responder model.

How does the browser signer use the CodeB OCSP responder?

The browser signer sign.html fetches OCSP responses for both the signer certificate and the TSA certificate right after the RFC 3161 timestamp returns. The CodeB OCSP responses are embedded as id-aa-ets-revocationValues inside PAdES-B-LT and PAdES-B-LTA envelopes, so relying parties can validate the signature offline even after the signer's certificate expires.

What does an unknown status from the CodeB OCSP responder mean?

An unknown status means the CodeB OCSP responder does not recognise the certificate's issuer and serial number in the tenant's PKI. This is deliberate: the responder returns unknown rather than falsely reporting good. Known certificates return good unless they are on the revocation list, in which case they return revoked with a revocation time.

How do I revoke a certificate for the CodeB OCSP responder?

Send POST /admin.ashx?action=csc-revoke-serial with an admin Bearer token and a JSON body containing the serial, an RFC 5280 CRLReason such as keyCompromise, and revoked_at. Revocations are stored in App_Data/<tenant>/csc-revoked-serials.json, which the CodeB OCSP responder re-reads when the file changes. There is no separate CRL to publish.

Does the CodeB OCSP responder require authentication, and what are its limits?

No Bearer token is required, since OCSP responders are open by long-standing convention. The CodeB OCSP responder shares the /csc.ashx per-IP bucket of 30 requests per minute per action; over the limit it returns HTTP 429 with Retry-After: 30 and a tryLater OCSP body.

How quickly are new certificates visible to the CodeB OCSP responder?

New certificates are picked up on the next request after the CodeB OCSP responder's cache refresh, which happens for requests arriving more than 60 seconds after the last load and on tenant restart. There is no separate publish-to-OCSP step. Responses are built fresh per request, so producedAt always reflects real time.