Skip to content

Improve resilient OCSP certificate revocation checker - #140

Open
madislm wants to merge 5 commits into
web-eid:v4from
madislm:AUT-2981
Open

madislm wants to merge 5 commits into
web-eid:v4from
madislm:AUT-2981

Conversation

@madislm

@madislm madislm commented Oct 2, 2026

Copy link
Copy Markdown

AUT-2981

Signed-off-by: Madis Jaagup Laurson <madisjaagup.laurson@nortal.com>

@madislm madislm changed the title Improve resilient OCSP certificate revocation checker- #138 Improve resilient OCSP certificate revocation checker Oct 5, 2026
@madislm
madislm force-pushed the AUT-2981 branch 4 times, most recently from 5a45d6d to 56d68ae Compare October 5, 2026 12:17
Signed-off-by: Madis Jaagup Laurson <madisjaagup.laurson@nortal.com>
Comment thread src/main/java/eu/webeid/ocsp/protocol/OcspResponseValidator.java
@madislm
madislm force-pushed the AUT-2981 branch 4 times, most recently from b313adf to 1a97a19 Compare October 12, 2026 11:10
Comment thread src/main/java/eu/webeid/ocsp/protocol/OcspResponseValidator.java Outdated
Comment thread src/main/java/eu/webeid/ocsp/exceptions/UserCertificateOCSPException.java Outdated
Comment thread src/main/java/eu/webeid/ocsp/README.md Outdated
Comment thread src/main/java/eu/webeid/ocsp/README.md Outdated
Comment thread src/main/java/eu/webeid/ocsp/README.md Outdated
Comment thread src/main/java/eu/webeid/ocsp/protocol/OcspResponseValidator.java

// It is guaranteed that if the signature verification succeeds, then the origin and challenge
// have been implicitly and correctly verified without the need to implement any additional checks.
authTokenSignatureValidator.validate(token.algorithm(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous revocation check before token signature validation ordering was deliberate.

Certificate validation is the "admission gate" for token validation. The incoming token is attacker-controlled, including the certificate’s public key and its parameters. Until the certificate chain is validated, those parameters must be considered untrusted.

Validating the chain first establishes that an accepted issuer endorsed that key. Validity and revocation checks then establish whether it still remains eligible for authentication. Only afterward should we use it to process the token signature.

Moving signature verification earlier lets the caller direct cryptographic processing with an unauthorized key. Rejecting the certificate afterward cannot undo expensive or unsafe operations already performed. For example, an attacker can submit a fabricated certificate with an unusually large RSA public exponent, forcing expensive token signature verification before certificate validation rejects the untrusted key.

Please restore the original ordering.

@mrts mrts Oct 15, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, while at it, please add a comment that the ordering is deliberate and explain shortly why, based on the previous comment.

}
final boolean hasDigitalSignature = keyUsage.length > KEY_USAGE_DIGITAL_SIGNATURE_BIT_INDEX
&& keyUsage[KEY_USAGE_DIGITAL_SIGNATURE_BIT_INDEX];
// RFC 5280 section 4.2.1.12 also permits Non Repudiation for OCSP signing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not necessary to specially bring up Non Repudiation here. The comment was only useful in the context of our discussions:

Suggested change
// RFC 5280 section 4.2.1.12 also permits Non Repudiation for OCSP signing.

Comment on lines +55 to +58
/**
* The flag controls whether Key Usage must be present. If present, Key Usage must include
* Digital Signature or Non Repudiation and must not include Certificate Signing.
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick, let's add a proper docstring:

Suggested change
/**
* The flag controls whether Key Usage must be present. If present, Key Usage must include
* Digital Signature or Non Repudiation and must not include Certificate Signing.
*/
/**
* Validates that the Key Usage extension permits OCSP response signing.
*
* <p>When present, the extension must include Digital Signature or Non Repudiation,
* consistent with RFC 5280 section 4.2.1.12.
*
* @param certificate the OCSP responder certificate whose Key Usage is checked
* @param isKeyUsageRequired whether the Key Usage extension must be present;
* if false, an absent extension is accepted, but a present extension is still validated
* @throws OCSPCertificateException if the required extension is absent or its usages fail these checks
* @throws NullPointerException if {@code certificate} is null
* @see <a href="https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.12">RFC 5280, section 4.2.1.12</a>
*/

}
// The first certificate is the responder certificate, other certificates, if given, are the certificate's chain.
final X509CertificateHolder responderCert = basicResponse.getCerts()[0];
OcspResponseValidator.validateResponseSignature(basicResponse, responderCert, ocspService.getAccessLocation());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like with AuthTokenValidatorImpl.validateToken() below, we should run validateResponderCertificate() before validateResponseSignature(). The certificate attached to an OCSP response is untrusted input, its key should reach the response signature verifier only after pinning or issuer verification succeeds.

OpenJDK follows this order as well. It verifies the delegated responder certificate using the issuer’s trusted key before verifying the response signature. This prevents unauthorized, attacker-chosen key parameters from driving response signature verification.

You can check the OpenJDK OCSPResponse and related code for other inspiration as well while we are improving OCSP code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, while at it, please add a comment that the ordering is deliberate and explain shortly why.

throw new OCSPCertificateException("Certificate " + certificate.getSubjectX500Principal() +
" Key Usage extension does not contain Digital Signature or Non Repudiation, which is required for OCSP response signing");
}
if (keyUsage.length > KEY_USAGE_KEY_CERT_SIGN_BIT_INDEX && keyUsage[KEY_USAGE_KEY_CERT_SIGN_BIT_INDEX]) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I see that the Certificate Signing prohibition logically belongs to the "not CA" part in validateBasicConstraintsNotCA(), see the comment at validateBasicConstraintsNotCA().

private static final int KEY_USAGE_NON_REPUDIATION_BIT_INDEX = 1;
private static final int KEY_USAGE_KEY_CERT_SIGN_BIT_INDEX = 5;
private static final String ERROR_PREFIX = "Certificate status update time check failed: ";

@mrts mrts Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a docstring:

Suggested change
/**
* Validates that the responder certificate is not a CA and does not include Certificate Signing.
*
* <p>For a non-CA certificate, RFC 5280 section 4.2.1.9 prohibits the Certificate Signing
* bit in Key Usage, including when the Basic Constraints extension is absent.
*
* @param certificate the OCSP responder certificate to validate
* @throws OCSPCertificateException if the certificate is a CA or asserts Certificate Signing
* @throws NullPointerException if {@code certificate} is null
* @see <a href="https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.9">RFC 5280, section 4.2.1.9</a>
*/

if (certificate.getBasicConstraints() >= 0) {
throw new OCSPCertificateException("Certificate " + certificate.getSubjectX500Principal() +
" must not be a CA certificate (Basic Constraints CA:TRUE is not allowed for OCSP responder)");
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move the "non-CA" Certificate Signing in Key Usage here for consistency:

Suggested change
}
}
final boolean[] keyUsage = certificate.getKeyUsage();
if (keyUsage != null && keyUsage.length > KEY_USAGE_KEY_CERT_SIGN_BIT_INDEX
&& keyUsage[KEY_USAGE_KEY_CERT_SIGN_BIT_INDEX]) {
throw new OCSPCertificateException("Certificate " + certificate.getSubjectX500Principal() +
" Key Usage extension contains Certificate Signing, which is not allowed for a non-CA OCSP responder certificate");

* @param doesSupportNonce true if the service supports the OCSP protocol nonce extension
* @param maxThisUpdateAge the maximum age of the OCSP response's {@code thisUpdate} time, must be greater than zero
* @param maxNextUpdateAge the maximum age of the OCSP response's {@code nextUpdate} time, must be greater than zero
* @param isKeyUsageRequired true if the responder certificate must have the Key Usage extension with Digital Signature

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param isKeyUsageRequired true if the responder certificate must have the Key Usage extension with Digital Signature
* @param isKeyUsageRequired true if the responder certificate must have the Key Usage extension

The two custom checker options and `withoutUserCertificateRevocationCheck()` are mutually exclusive. Combining them causes `build()` to throw `IllegalArgumentException`. Custom checker implementations must support concurrent validation calls.

Platform OCSP networking and response freshness are controlled by the JDK provider; see [Platform OCSP timeouts](#platform-ocsp-timeouts). The v3 API builder-level OCSP client, timeout, designated-service and response-age options are no longer available; use the custom OCSP checker below when those controls are needed. Its timing constants do not configure the platform checker.

@mrts mrts Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a section about OCSP responder revocation limitation (add link to this section also to Contents):

Suggested change
## OCSP responder revocation check limitation
The bundled custom OCSP checker and OpenJDK 17's OCSP implementation do not check delegated responder certificate revocation, even when `id-pkix-ocsp-nocheck` is absent. A revoked responder with a compromised key can therefore produce signed responses that are accepted. Applications requiring responder revocation checking must enforce it separately or supply a `CertificateRevocationChecker` that does so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants