Skip to content

REF-16: Send OCSP requests using POST from the library, and make CRLCheckerTest reliable - #90

Merged
thomasnymand merged 3 commits into
masterfrom
feature/REF-16-ocsp-force-post-in-tests
Aug 13, 2026
Merged

thomasnymand merged 3 commits into
masterfrom
feature/REF-16-ocsp-force-post-in-tests

Conversation

@thomasnymand

@thomasnymand thomasnymand commented Jul 6, 2026 •

Copy link
Copy Markdown
Collaborator

Sends OCSP requests using POST from the library itself, and fixes the CRLCheckerTest problems that uncovered the issue.

The problem

The JDK OCSP client uses the RFC 5019 GET form (request base64-encoded into the URL path) for requests of 255 characters or less. The NemLog-in OCSP responders answer HTTP 404 to that form, so every OCSP check fails with UNDETERMINED_REVOCATION_STATUS.

This is not test-only. In a deployment it means:

  • with the default configuration, the SP silently degrades to the CRL fallback (one warn per certificate),
  • with oiosaml.servlet.revocation.crl.check.enabled=false, all IdP certificates are dropped and login breaks.

Version detail: sun.security.provider.certpath.OCSP in JDK 8 and 11 always POSTs; the GET form and the com.sun.security.ocsp.useget switch exist from JDK 12 onwards (confirmed present in 21/25/26, absent in 11/8). USE_GET is a private static final boolean initialized in the class's static initializer from a system property only (System.getProperty, default true), so Security.setProperty has no effect and setting it after the first OCSP check is too late.

Two further problems in CRLCheckerTest:

  • testOcspCheckOnValidCertificate failed (expected: <1> but was: <0>) for the reason above.
  • The revoked tests passed for the wrong reason: with OCSP unreachable the result set was empty, which happened to satisfy assertEquals(0, …). The revoked certificate's real status was never exercised.

Changes

Force POST in the library, not in the tests. CRLChecker.configureOcspTransport sets com.sun.security.ocsp.useget=false, called from OIOSAML3Service.init so it runs before the first OCSP check in the JVM. It is gated by a new property oiosaml.servlet.revocation.ocsp.post.enabled (default true), plumbed through Constants → Configuration → DispatcherServlet. A value set explicitly by the deployer (-Dcom.sun.security.ocsp.useget=…) is left alone, and each branch logs what happened. POST support is mandatory for responders per RFC 6960, so forcing it is safe; the only cost is that GET responses are no longer HTTP cacheable for other OCSP users in the same JVM.

Drop the JVM global security properties. doOCSPCheck no longer sets ocsp.enable / ocsp.responderURL. Those published the responder of the certificate currently being checked as a JVM wide default, which leaked into every other PKIX validation in the container and raced with concurrent checks. Replaced with a per validation PKIXRevocationChecker (Java 8 API): setOcspResponder(URI) plus Option.NO_FALLBACK, so CRL fallback stays in checkCertificate where the OIOSAML configuration drives it.

Use a genuinely revoked certificate for TestConstants.REVOKED_CERTIFICATE. Verified against the live test CA: OCSP reports revoked and the serial is present in the issuing CRL. Validity 2026-07-06 .. 2029-07-05 (no near-term expiry).

Tag the revoked tests @Tag("integration") (both OCSP and CRL variants) since they depend on the live test CA revocation infrastructure. No group filtering is configured, so they still run by default; the tag allows excluding them offline via -DexcludedGroups=integration.

Remove the workaround from BaseServiceTest, so CRLCheckerTest now exercises the production path instead of a test-only property.

Verification

mvn -pl oiosaml test: the four CRLCheckerTest cases pass (present, not skipped). Re-running with -DargLine=-Dcom.sun.security.ocsp.useget=true makes testOcspCheckOnValidCertificate fail again, which confirms both that the responder really 404s on GET and that the deployer override is honoured. The only remaining failure is the unrelated OIOBPPUtilTest (JDK 26 JAXB incompatibility), which also fails on master.

Notes

  • com.sun.security.ocsp.useget is an OpenJDK/Oracle-internal property, reliable on HotSpot-derived JVMs and JVM-global in scope. It is applied whenever oiosaml.servlet.revocation.ocsp.post.enabled is true, also when OIOSAML's own OCSP checking is disabled, because applying it lazily would risk being too late to take effect.
  • Follow-up worth its own issue: build the OCSP request in the library (BouncyCastle is already on the classpath), POST it with our own timeouts, and hand the DER response to PKIXRevocationChecker.setOcspResponses(…) so the JDK still does all signature and validity verification. That would remove the dependency on a JDK-internal property entirely and give us control over OCSP timeouts.
  • The revocation tests remain live-network dependent by nature; the integration tag makes that explicit. A fully hermetic (MockServer-based) rewrite remains a possible follow-up.

…ach the responder

The JDK OCSP client uses the RFC 5019 GET form (request base64-encoded in the
URL path) for small requests. The NemLog-in test OCSP responder at
ca1.cti-gov.dk returns HTTP 404 for that GET form (it only serves POST), so
CRLCheckerTest's OCSP validation failed with UNDETERMINED_REVOCATION_STATUS
and dropped the otherwise-valid certificate.

Set com.sun.security.ocsp.useget=false in BaseServiceTest.beforeAll so the JDK
POSTs the OCSP request (request in the body) instead. Verified end-to-end that
this makes the responder return "good" and the certificate validate.
…n tests

Replace TestConstants.REVOKED_CERTIFICATE with a certificate that is actually
revoked at the NemLog-in test CA (verified: OCSP reports "revoked" and the
serial is present in the issuing CRL; valid 2026-07-06 .. 2029-07-05). The
revoked OCSP/CRL tests now pass because the certificate is genuinely revoked,
rather than incidentally due to an unreachable responder.

Tag both testOcspCheckOnRevokedCertificate and testCrlCheckOnRevokedCertificate
with @tag("integration") since they depend on the live test CA revocation
infrastructure. No group filtering is configured, so they still run by default;
the tag allows excluding them from offline runs via -DexcludedGroups=integration.
@thomasnymand
thomasnymand requested a review from mthiim August 7, 2026 10:08
…ests

The JDK OCSP client uses the RFC 5019 GET form (request base64-encoded into the
URL path) for requests of 255 characters or less, from JDK 12 onwards. The
NemLog-in OCSP responders answer HTTP 404 to that form, so every OCSP check
fails with UNDETERMINED_REVOCATION_STATUS: the SP silently degrades to the CRL
fallback, or drops the IdP certificates entirely when CRL checking is disabled
as well. Java 8 and 11 always POST and are unaffected. Forcing POST in the test
base class only hid this from the test suite, it did not fix deployments.

Set com.sun.security.ocsp.useget=false from OIOSAML3Service.init instead, gated
by the new configuration property oiosaml.servlet.revocation.ocsp.post.enabled
(default true). The JDK reads the property once, when
sun.security.provider.certpath.OCSP is initialized, so it has to be set before
the first OCSP check in the JVM. A value set explicitly by the deployer is left
alone. POST support is mandatory for responders per RFC 6960, so forcing it is
safe, the only cost is that GET responses are no longer HTTP cacheable for other
OCSP users in the same JVM.

Also replace the JVM global "ocsp.enable" and "ocsp.responderURL" security
properties with a per validation PKIXRevocationChecker. The old code published
the responder of the certificate being checked as a JVM wide default, which
leaked into every other PKIX validation in the container and raced with
concurrent checks. NO_FALLBACK keeps CRL fallback where it belongs, in
checkCertificate, driven by the OIOSAML configuration.

With the library doing this, BaseServiceTest no longer needs to set the system
property, so CRLCheckerTest now exercises the production path. Verified: the
four CRLCheckerTest cases pass, and re-running with
-DargLine=-Dcom.sun.security.ocsp.useget=true makes the valid certificate OCSP
test fail again, confirming both the 404 on GET and that the deployer override
is honoured.
@thomasnymand thomasnymand changed the title REF-16: Make CRLCheckerTest reliable (force OCSP POST, use a truly revoked cert, tag integration tests) REF-16: Send OCSP requests using POST from the library, and make CRLCheckerTest reliable Aug 13, 2026
@thomasnymand
thomasnymand force-pushed the feature/REF-16-ocsp-force-post-in-tests branch from d41bbb3 to 5bbfa6a Compare August 13, 2026 09:34

@mthiim mthiim left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved

@thomasnymand
thomasnymand merged commit f93bc0e into master Aug 13, 2026
2 checks passed
@thomasnymand
thomasnymand deleted the feature/REF-16-ocsp-force-post-in-tests branch August 13, 2026 10:29
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.

2 participants