REF-31: Verify revocation data before trusting it - #98
Merged
Merged
Conversation
The CRL was fetched from the distribution point named in the certificate and consulted for the serial number without checking who signed it or how current it was, and the OCSP trust anchor was whatever certificate the issuer access location returned. Both locations come from the certificate being checked, so neither the absence of a serial number in a list nor a completed OCSP path validation meant anything on its own. A CRL is now required to be issued by the CA that issued the certificate, to verify against that CA's public key, and to be current within the configured clock skew. A list without a nextUpdate is refused, since it cannot be told apart from one superseded long ago. The certificate fetched from the issuer access location is required to be the issuer of the certificate being checked, by name and by signature, before it is used as OCSP trust anchor or CRL signer. CRL, issuer certificate and OCSP responder locations are restricted to http and https. The OCES CAs serve revocation data over plain http, which is fine as it is signed, but a certificate must not be able to point the SP at file:, jar: or any other protocol the JVM supports. Those fetches had no timeouts and now have connect and read timeouts. The issuer certificate cache was keyed by URL and never revisited, so a CA replaced at the same location would fail every check until the process restarted. A cached certificate is now reused only while it still is the issuer. Tested against a locally minted CA served from MockServer: a certificate absent from a valid list is accepted, one listed in it is rejected, and a list signed by another key, a stale list, a list without nextUpdate and a substituted CA are all refused. bcpkix is added as a test dependency to mint that material.
mthiim
reviewed
Aug 26, 2026
…ion is inconclusive (e.g. OCSP responder not available), additional tests for KeyUsage and BasicConstraints.
# Conflicts: # oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java
mthiim
reviewed
Sep 1, 2026
Collaborator
There was a problem hiding this comment.
It seems there is no locking on this map. I don't know if there is in the surrounding code but being static makes it even more likely there could be a race condition here.
Collaborator
There was a problem hiding this comment.
(An easy fix is of course to use a ConcurrentMap assuming it doesn't need to synchronize with other things)
mthiim
approved these changes
Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes revocation data prove itself before the SP acts on it, and makes the two revocation paths agree on what they accept.
Problem
The CRL was fetched from the distribution point named in the certificate and consulted for the serial number, without checking who signed it or how current it was. The OCSP trust anchor was whatever certificate the issuer access location returned. Both locations come from the certificate being checked, so neither the absence of a serial number in a list nor a completed OCSP path validation meant anything on its own.
The two paths also disagreed. The OCSP path runs a full PKIX validation; the CRL path checked only the revocation list. Any exception from the OCSP path fell back to the CRL, so a certificate the OCSP path had rejected on its own merits could be accepted by the CRL path instead. Nothing downstream compensates: the assertion signature is verified against the certificate's key alone, so
CRLCheckeris the only place an IdP metadata certificate is validated at all.Changes
Revocation data is authenticated
thisUpdate/nextUpdateallowing for the configured clock skew. A list without anextUpdateis refused, since it cannot be told apart from one superseded long ago.cRLSign(RFC 5280 §4.2.1.3). The JDK's own CRL handling enforces this; this path fetches and verifies the list itself and so had to.keyCertSign.The two paths no longer disagree
ALGORITHM_CONSTRAINED, so a certificate rejected underjdk.certpath.disabledAlgorithmsis no longer accepted by the other path — relevant to[OIO-ALG-01].BasicReason.REVOKED) rather than by matching the text of the JDK's exception message.What this does and does not establish
Trust is derived, not configured: the anchor is the CA that demonstrably issued the certificate under check, and those certificates come from the configured IdP metadata. There is no configured root trust store, so this does not establish that the issuing CA is a legitimate OCES CA. Revocation checking is therefore only as trustworthy as the metadata the certificates came from, which is a separate open issue (metadata is not signature-verified against a pinned trust root, and
supportSelfSigneddisables TLS validation for the metadata fetch).Verification
mvn test→ 143 tests, 0 failures, and the full reactor builds.New
CRLCheckerLocalTestmints a CA, a certificate and CRLs withTestPkiUtiland serves them from the MockServer already used by the test suite, so the logic is testable without the live CA. 14 cases: a certificate absent from a valid list is accepted; one listed in it is rejected; a list signed by another key, a stale list, a list withoutnextUpdateand a substituted CA are refused; a CA lackingcRLSign, lackingkeyCertSign, lacking basic constraints, or past its validity period is not trusted; an expired and a not-yet-valid certificate are rejected; the CRL still answers when the responder cannot state a status, and does not answer for a certificate the OCSP path rejected.Every negative case was checked to fail with the corresponding check disabled, so none of them passes for an unrelated reason. The live-CA tests in
CRLCheckerTestpass, so the new checks also hold against real OCES data.Also in this PR
idp/pom.xmloverrides the Spring Boot parent's Lombok 1.18.16 with 1.18.46 and declares it inannotationProcessorPaths. The module did not compile on a current JDK: 1.18.16 predates the JDK 16 encapsulation of the compiler internals it uses, and since JDK 23 javac no longer runs annotation processors merely found on the classpath. Build fix only, unrelated to revocation checking.Dependency note
bcpkix-jdk15onis added at test scope, pinned to 1.59 to match thebcprov1.59 that OpenSAML already brings in; 1.64 fails at runtime against 1.59. If bcprov is bumped, bcpkix has to move with it.