From 80affed32e8f80315b7db6ac8bc11b5e5cf218d8 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Fri, 14 Aug 2026 17:00:16 -0600 Subject: [PATCH] F-8207: map stapled OCSP revoked status to BasicReason.REVOKED --- .../jce/WolfCryptPKIXRevocationChecker.java | 39 ++++++--- .../WolfCryptPKIXRevocationCheckerTest.java | 84 +++++++++++++++++++ 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java index f1c3e11f..6de9a68f 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java @@ -330,16 +330,7 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker { "Failed to encode certificate", e); } catch (WolfCryptException e) { - /* A definitive OCSP "revoked" status must always be a hard fail, - * with BasicReason.REVOKED. Other codes SOFT_FAIL may suppress. */ - if (e.getCode() == WolfCryptError.OCSP_CERT_REVOKED.getCode()) { - throw new CertPathValidatorException( - "Certificate revoked (OCSP): " + e.getMessage(), e, - null, -1, BasicReason.REVOKED); - } - throw new CertPathValidatorException( - "OCSP check failed: " + e.getMessage(), e, - null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); + throw mapOcspException("OCSP check failed", e); } } @@ -503,12 +494,34 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker { "Failed to encode certificate", e); } catch (WolfCryptException e) { - throw new CertPathValidatorException( - "OCSP response check failed: " + e.getMessage(), e, - null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); + throw mapOcspException("OCSP response check failed", e); } } + /** + * Map a native OCSP exception to CertPathValidatorException. An OCSP + * "revoked" status must always be a hard fail with BasicReason.REVOKED. + * Other codes map to UNDETERMINED_REVOCATION_STATUS, which SOFT_FAIL may + * suppress. + * + * @param msgPrefix message prefix for non-revoked failures + * @param e native exception from OCSP check + * + * @return CertPathValidatorException for caller to throw + */ + private CertPathValidatorException mapOcspException(String msgPrefix, + WolfCryptException e) { + + if (e.getCode() == WolfCryptError.OCSP_CERT_REVOKED.getCode()) { + return new CertPathValidatorException( + "Certificate revoked (OCSP): " + e.getMessage(), e, + null, -1, BasicReason.REVOKED); + } + return new CertPathValidatorException( + msgPrefix + ": " + e.getMessage(), e, + null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS); + } + /** * Handle exception based on SOFT_FAIL option. * diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java index f621508f..2f79c284 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java @@ -55,6 +55,7 @@ import com.wolfssl.wolfcrypt.WolfSSLCertManager; import com.wolfssl.wolfcrypt.WolfCryptException; import com.wolfssl.wolfcrypt.WolfCryptError; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; +import com.wolfssl.wolfcrypt.test.Util; import com.wolfssl.provider.jce.WolfCryptProvider; import com.wolfssl.provider.jce.WolfCryptPKIXRevocationChecker; @@ -873,6 +874,89 @@ public class WolfCryptPKIXRevocationCheckerTest { cm.free(); } + /* DER encoded OCSP response for server-cert.der, status successful, signed + * by ca-cert.pem, generated with openssl ocsp. Contains no nextUpdate so + * it never expires. Only needs to pass native response status parsing, + * response verification itself is mocked below. */ + private static final String OCSP_RESPONSE_HEX = + "308202330a0100a082022c3082022806092b0601050507300101048202193082" + + "02153081fea18197308194310b30090603550406130255533110300e06035504" + + "080c074d6f6e74616e613110300e06035504070c07426f7a656d616e3111300f" + + "060355040a0c08536177746f6f746831133011060355040b0c0a436f6e73756c" + + "74696e673118301606035504030c0f7777772e776f6c6673736c2e636f6d311f" + + "301d06092a864886f70d0109011610696e666f40776f6c6673736c2e636f6d18" + + "0f32303236303831343232353035335a3051304f303a300906052b0e03021a05" + + "000414ff66218a6ec5866184259abad65539fb25512cdd0414278e671174c326" + + "1d3fed3363b3a4d81d30e5e8d50201018000180f323032363038313432323530" + + "35335a300d06092a864886f70d01010b0500038201010076d97a9999d345fc6f" + + "071b127d806865c588e0bea677757ec370396aea7020ef0db922251f8ba6bb45" + + "8abd4aeb310d9ff024b733a483ed8c8b7cfd116e003b5db9ab5d69f640973c93" + + "856562fef0d4eeef3115877c3867bcfbe5910b47fad0e5c9dfa1d487bc6048be" + + "7a0900a7505d9a2a59210921c359a95ebc94f12d2b8def4489a26a39253be4a7" + + "6084f4f42dfc46d800536ebbff8bc85111408e221b6d5e8462588994d4ca338b" + + "62d22bf0496dc53677f91892a6036040216851124f3abdb781d6c96a0358b0ad" + + "a97563122e9c1d8fd99d2cd38b78373f0ad4c841dc11727fdbeac44efd9a75da" + + "060e70d1c02774b5de05d3bd9a9fced3c73d65d3cc750d"; + + /* CertManager that simulates a definitive OCSP "revoked" status for a + * pre-loaded response by throwing WolfCryptException(OCSP_CERT_REVOKED) + * from CertManagerCheckOCSPResponse, like native wolfSSL does for a + * revoked certificate. */ + private static class RevokedOcspResponseCertManager + extends WolfSSLCertManager { + + @Override + public synchronized void CertManagerCheckOCSPResponse( + byte[] response, int responseSz, byte[] cert, int certSz) { + throw new WolfCryptException( + WolfCryptError.OCSP_CERT_REVOKED.getCode()); + } + } + + @Test + public void testPreloadedOcspRevokedUsesRevokedReason() throws Exception { + + if (!WolfCrypt.OcspEnabled()) { + /* Skip test if OCSP not compiled in */ + return; + } + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + WolfCryptPKIXRevocationChecker checker = + (WolfCryptPKIXRevocationChecker)cpv.getRevocationChecker(); + + FileInputStream fis = new FileInputStream(caCertDer); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate caCert = (X509Certificate)cf.generateCertificate(fis); + fis.close(); + + fis = new FileInputStream(serverCertDer); + X509Certificate serverCert = + (X509Certificate)cf.generateCertificate(fis); + fis.close(); + + /* Preload OCSP response so check() takes the stapled path */ + Map responses = + new HashMap(); + responses.put(serverCert, Util.h2b(OCSP_RESPONSE_HEX)); + checker.setOcspResponses(responses); + + WolfSSLCertManager cm = new RevokedOcspResponseCertManager(); + cm.CertManagerLoadCA(caCert); + checker.setCertManager(cm); + checker.init(false); + + try { + checker.check(serverCert, null); + fail("A revoked pre-loaded OCSP status must fail validation"); + } catch (CertPathValidatorException e) { + assertEquals("Revoked cert must fail with BasicReason.REVOKED", + BasicReason.REVOKED, e.getReason()); + } + + cm.free(); + } + @Test public void testRevocationCheckerCheckWithCertChain() throws Exception {