diff --git a/README_JCE.md b/README_JCE.md index 57cc709b..432d367c 100644 --- a/README_JCE.md +++ b/README_JCE.md @@ -888,6 +888,10 @@ revoked, validation will fail. The difference only affects behavior when one method succeeds and the other would have failed (e.g., OCSP unreachable but CRL available). +A `PKIXRevocationChecker` added with `addCertPathChecker()` applies +irregardless of if `setRevocationEnabled()` is set, so `PREFER_CRLS` with CRLs +in the `CertStore` list performs CRL checking even when revocation is disabled. + #### Indirect CRL Not Supported Native wolfSSL does not support indirect CRLs. An indirect CRL is a CRL signed diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java index 55a925a8..4ee8f212 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java @@ -73,7 +73,8 @@ import java.security.cert.CertificateException; * validation will not return PolicyNode in CertPathValidatorResult * * Revocation checking is supported via: - * - CRL: If PKIXParameters.isRevocationEnabled() is true and appropriate + * - CRL: If PKIXParameters.isRevocationEnabled() is true, or a + * PKIXRevocationChecker with PREFER_CRLS is registered, and appropriate * CRLs have been loaded into CertStore Set * - OCSP: via getRevocationChecker() which returns a * WolfCryptPKIXRevocationChecker supporting OCSP and options @@ -839,25 +840,71 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { CertPath certPath, List certs) throws CertPathValidatorException { - /* Report index of last cert in path (closest to trust anchor) - * to match SunJCE behavior. */ - int failIndex = 0; - if (certs != null && certs.size() > 1) { - failIndex = certs.size() - 1; - } throw new CertPathValidatorException(message, null, certPath, - failIndex, BasicReason.UNDETERMINED_REVOCATION_STATUS); + lastCertIndex(certs), BasicReason.UNDETERMINED_REVOCATION_STATUS); } /** - * Check if revocation has been enabled in PKIXParameters, and if so - * find and load any CRLs in params.getCertStores(). + * Index of cert closest to the trust anchor. * - * When a PKIXRevocationChecker is registered via addCertPathChecker(), - * that checker handles revocation checking. CRL checking in the native - * CertManager is only enabled if: - * - No PKIXRevocationChecker is present (default CRL behavior), or - * - PKIXRevocationChecker has PREFER_CRLS option set + * @param certs certificate list from the CertPath + * + * @return index of the last cert, or 0 for a single-cert path + */ + private static int lastCertIndex(List certs) { + + if (certs != null && certs.size() > 1) { + return certs.size() - 1; + } + + return 0; + } + + /** + * Disable native CRL checking after a PREFER_CRLS checker found no CRL + * to load, so wolfSSL doesn't fail chain validation on a missing CRL. With + * NO_FALLBACK set, revocation is undetermined, which fails validation + * unless SOFT_FAIL is set. Without NO_FALLBACK, the OCSP result the + * checker already produced in check() propogates. + * + * @param revChecker the registered PREFER_CRLS checker + * @param noFallback true if the checker has NO_FALLBACK set + * @param cm WolfSSLCertManager with CRL checking enabled + * @param certPath the CertPath being validated, for exception reporting + * @param failIndex index of the cert to report + * + * @throws CertPathValidatorException if revocation is undetermined and + * SOFT_FAIL is not set, or native CRL checking cannot be disabled + */ + private void handleMissingCrl(WolfCryptPKIXRevocationChecker revChecker, + boolean noFallback, WolfSSLCertManager cm, CertPath certPath, + int failIndex) throws CertPathValidatorException { + + if (noFallback) { + revChecker.handleMissingCrlRevocation(certPath, failIndex); + } + else { + log("no CRL loaded, PREFER_CRLS checker falls back to OCSP"); + } + + try { + cm.CertManagerDisableCRL(); + } + catch (WolfCryptException e) { + throw new CertPathValidatorException("Failed to disable CRL " + + "checking in native WolfSSLCertManager", e); + } + } + + /** + * Check if CRL checking is wanted and, if so, find and load any CRLs in + * params.getCertStores(). + * + * CRL checking in the native CertManager is enabled when + * PKIXParameters.isRevocationEnabled() is true, or when a + * PKIXRevocationChecker with PREFER_CRLS is registered, which applies + * irregardless of the revocation flag. A registered checker without + * PREFER_CRLS handles revocation itself via OCSP. * * @param params parameters used to check if revocation is enabled and, * if so load any CRLs available @@ -877,10 +924,13 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { int i = 0; int loadedCount = 0; int certCount = 0; + int failIndex = lastCertIndex(certs); List stores = null; Collection crls = null; boolean hasRevocationChecker = false; boolean preferCrls = false; + boolean noFallback = false; + WolfCryptPKIXRevocationChecker revChecker = null; if (params == null || cm == null) { throw new CertPathValidatorException( @@ -894,13 +944,14 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { for (PKIXCertPathChecker checker : pathCheckers) { if (checker instanceof WolfCryptPKIXRevocationChecker) { hasRevocationChecker = true; - WolfCryptPKIXRevocationChecker revChecker = - (WolfCryptPKIXRevocationChecker)checker; + revChecker = (WolfCryptPKIXRevocationChecker)checker; Set options = revChecker.getOptions(); - if (options != null && options.contains( - PKIXRevocationChecker.Option.PREFER_CRLS)) { - preferCrls = true; + if (options != null) { + preferCrls = options.contains( + PKIXRevocationChecker.Option.PREFER_CRLS); + noFallback = options.contains( + PKIXRevocationChecker.Option.NO_FALLBACK); } break; } @@ -913,18 +964,24 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { return; } - if (params.isRevocationEnabled()) { - log("revocation enabled in PKIXParameters, checking for CRLs " + - "to load"); + if (params.isRevocationEnabled() || preferCrls) { + log("revocation enabled or PREFER_CRLS checker registered, " + + "checking for CRLs to load"); if (!WolfCrypt.CrlEnabled()) { throw new CertPathValidatorException( - "Revocation enabled in PKIXParameters but native " + - "wolfCrypt CRL not compiled in"); + "CRL checking requested but native wolfCrypt CRL not " + + "compiled in"); } /* Enable CRL in native WolfSSLCertManager */ - cm.CertManagerEnableCRL(WolfCrypt.WOLFSSL_CRL_CHECK); + try { + cm.CertManagerEnableCRL(WolfCrypt.WOLFSSL_CRL_CHECK); + } + catch (WolfCryptException e) { + throw new CertPathValidatorException("Failed to enable CRL " + + "checking in native WolfSSLCertManager", e); + } log("CRL support enabled in native WolfSSLCertManager"); stores = params.getCertStores(); @@ -940,6 +997,10 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { "and no PKIXRevocationChecker configured for OCSP", certPath, certs); } + else { + handleMissingCrl(revChecker, noFallback, cm, certPath, + failIndex); + } return; } @@ -990,22 +1051,32 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { } } catch (CertStoreException e) { throw new CertPathValidatorException(e); + } catch (WolfCryptException e) { + throw new CertPathValidatorException( + "Failed to load CRL into native WolfSSLCertManager", e); } log("loaded " + loadedCount + " CRLs into WolfSSLCertManager"); /* If no CRLs were loaded and no PKIXRevocationChecker is handling * OCSP, we cannot determine revocation status. */ - if (loadedCount == 0 && !hasRevocationChecker) { - throwUndeterminedRevocationStatus( - "Revocation checking enabled but no CRLs found in " + - "CertStores and no PKIXRevocationChecker configured " + - "for OCSP", - certPath, certs); + if (loadedCount == 0) { + if (!hasRevocationChecker) { + throwUndeterminedRevocationStatus( + "Revocation checking enabled but no CRLs found in " + + "CertStores and no PKIXRevocationChecker configured " + + "for OCSP", + certPath, certs); + } + else { + handleMissingCrl(revChecker, noFallback, cm, certPath, + failIndex); + } } } else { - log("revocation not enabled in PKIXParameters"); + log("revocation not enabled in PKIXParameters and no PREFER_CRLS" + + "checker registered"); } } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java index 6de9a68f..9523d5b9 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java @@ -24,6 +24,7 @@ package com.wolfssl.provider.jce; import java.net.URI; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertPath; import java.security.cert.CertPathValidatorException; import java.security.cert.CertPathValidatorException.BasicReason; import java.security.cert.Extension; @@ -543,6 +544,27 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker { } } + /** + * Fail closed when a PREFER_CRLS/NO_FALLBACK checker has no CRL source. + * + * check() runs before CRLs are loaded and cannot see whether a CRL source + * is configured, so the validator calls this after CRL setup. With OCSP + * suppressed by NO_FALLBACK and no CRL to check, revocation status is + * undetermined. Honors SOFT_FAIL via handleException(). + * + * @param certPath the CertPath being validated, for exception reporting + * @param index index of the cert whose revocation status is undetermined + * + * @throws CertPathValidatorException if SOFT_FAIL is not set + */ + void handleMissingCrlRevocation(CertPath certPath, int index) + throws CertPathValidatorException { + handleException(new CertPathValidatorException( + "PREFER_CRLS with NO_FALLBACK selected but no CRL source is " + + "available, revocation status cannot be determined", null, + certPath, index, BasicReason.UNDETERMINED_REVOCATION_STATUS)); + } + /** * Set OCSP responder URI override. * diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java index 027684e1..67a8d074 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java @@ -33,6 +33,7 @@ import org.junit.BeforeClass; import java.util.List; import java.util.ArrayList; +import java.util.EnumSet; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -41,6 +42,10 @@ import java.io.File; import java.io.InputStream; import java.io.IOException; import java.io.FileInputStream; +import java.util.Arrays; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.nio.charset.StandardCharsets; import java.io.FileNotFoundException; import java.security.Security; import java.security.Provider; @@ -57,6 +62,7 @@ import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorResult; import java.security.cert.PKIXParameters; +import java.security.cert.PKIXRevocationChecker; import java.security.cert.PKIXCertPathChecker; import java.security.cert.PKIXCertPathValidatorResult; import java.security.cert.CertificateException; @@ -97,6 +103,7 @@ public class WolfCryptPKIXCertPathValidatorTest { protected static String caCertDer = null; /* ca-cert.der */ protected static String caEccCertDer = null; /* ca-ecc-cert.der */ protected static String crlDer = null; /* crl.der */ + protected static String crlRevoked = null; /* crl.revoked */ /* RSA-based cert chain with intermediates: * server/peer: server-int-cert.pem/der @@ -208,6 +215,8 @@ public class WolfCryptPKIXCertPathValidatorTest { crlDer = certPre.concat("examples/certs/crl/crl.der"); + crlRevoked = + certPre.concat("examples/certs/crl/crl.revoked"); } /** @@ -314,6 +323,312 @@ public class WolfCryptPKIXCertPathValidatorTest { checkPKIXCertPathValidatorResult(result, caCert, certPubKey); } + /* Build a single server-cert path validated against the RSA CA anchor. */ + private CertPath singleServerCertPath(CertificateFactory certFactory) + throws Exception { + + List certList = new ArrayList<>(); + InputStream fis = new FileInputStream(serverCertDer); + certList.add(certFactory.generateCertificate(fis)); + fis.close(); + return certFactory.generateCertPath(certList); + } + + /** + * PREFER_CRLS with NO_FALLBACK and no CRL source must fail closed with + * UNDETERMINED_REVOCATION_STATUS. + */ + @Test + public void testPreferCrlsNoFallbackWithoutCrlFailsClosed() + throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(false); + params.addCertPathChecker(rc); + + try { + cpv.validate(path, params); + fail("Validation should fail closed with no CRL source"); + } catch (CertPathValidatorException e) { + assertEquals(BasicReason.UNDETERMINED_REVOCATION_STATUS, + e.getReason()); + } + } + + /** + * With SOFT_FAIL, the missing-CRL determination is soft, so validation + * completes instead of failing closed. The soft-fail exception is not + * asserted here because addCertPathChecker() clones rc, so it lands on + * the clone rather than rc. + */ + @Test + public void testPreferCrlsNoFallbackWithoutCrlSoftFailPasses() + throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = + CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(false); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * SOFT_FAIL with revocation enabled but no CRL source must also complete. + * Revocation turns on the native CRL check, so the missing-CRL path must + * disable it under SOFT_FAIL to avoid a hard CRL_MISSING failure. + */ + @Test + public void testPreferCrlsNoFallbackRevocationEnabledSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println("CertPathValidator revocation status test " + + "skipped, CRL not compiled in"); + return; + } + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * Load a CRL from a file. Some CRL files carry a PEM text dump ahead + * of the PEM block, which not every CertificateFactory skips, so start + * at the PEM header when one is present. + */ + private CRL crlFromFile(CertificateFactory certFactory, String path) + throws Exception { + + byte[] data = Files.readAllBytes(Paths.get(path)); + int begin = new String(data, StandardCharsets.US_ASCII) + .indexOf("-----BEGIN X509 CRL-----"); + + if (begin > 0) { + data = Arrays.copyOfRange(data, begin, data.length); + } + return certFactory.generateCRL(new ByteArrayInputStream(data)); + } + + /** + * Validate the single server cert with a PREFER_CRLS/NO_FALLBACK checker + * and the given CRL supplied through a CertStore. + */ + private void validatePreferCrlsNoFallbackWithCrl(String crlPath, + boolean revocationEnabled) throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK)); + + /* CRL files are issued by ca-cert.der, the root for server-cert */ + Collection crls = new HashSet<>(); + crls.add(crlFromFile(certFactory, crlPath)); + List certStores = new ArrayList<>(); + certStores.add(CertStore.getInstance("Collection", + new CollectionCertStoreParameters(crls))); + + PKIXParameters params = new PKIXParameters(store); + params.setCertStores(certStores); + params.setRevocationEnabled(revocationEnabled); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * Validate a server cert with a PREFER_CRLS checker that allows OCSP + * fallback, SOFT_FAIL set, and no CRL source. + */ + private void validatePreferCrlsFallbackSoftFailWithoutCrl( + boolean revocationEnabled) throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(revocationEnabled); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * PREFER_CRLS with fallback allowed and no CRL source must not fail on + * a missing CRL, revocation is then determined by OCSP alone. With + * SOFT_FAIL an unreachable OCSP responder is not fatal, so validation + * should complete successfully. + */ + @Test + public void testPreferCrlsFallbackWithoutCrlSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS fallback test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsFallbackSoftFailWithoutCrl(false); + } + + /** + * PREFER_CRLS with fallback allowed, SOFT_FAIL, no CRL source, and + * revocation enabled must not fail on the missing CRL, revocation is + * then determined only by OCSP. + */ + @Test + public void testPreferCrlsFallbackEnabledWithoutCrlSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS fallback test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsFallbackSoftFailWithoutCrl(true); + } + + /** + * A PREFER_CRLS/NO_FALLBACK checker with CRLs actually loaded must still + * validate a non-revoked cert. The missing-CRL fail-closed path must not + * fire when a CRL source is present. + */ + @Test + public void testPreferCrlsNoFallbackWithCrlValidates() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS with CRL test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsNoFallbackWithCrl(crlDer, true); + } + + /** + * A checker added with addCertPathChecker() applies irrespective of + * setRevocationEnabled(), so PREFER_CRLS with a CRL in the CertStores is + * a supported CRL-only setup even with revocation disabled. + */ + @Test + public void testPreferCrlsNoFallbackRevocationDisabledWithCrlValidates() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS with CRL test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsNoFallbackWithCrl(crlDer, false); + } + + /** + * PREFER_CRLS with NO_FALLBACK, revocation disabled, and a CRL in the + * CertStores that revokes the server cert must fail validation, proving + * the supplied CRL is checked and not just ignored. + */ + @Test + public void testPreferCrlsNoFallbackRevocationDisabledRevokedFails() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS with CRL test skipped, CRL not compiled in"); + return; + } + + try { + validatePreferCrlsNoFallbackWithCrl(crlRevoked, false); + fail("Revoked cert should fail with revocation disabled and " + + "PREFER_CRLS"); + + } catch (CertPathValidatorException e) { + /* expected */ + } + } + /** * Test that setting the target cert constraints with * PKIXParameters.setTargetCertConstraints() passes with correct cert @@ -2049,6 +2364,96 @@ public class WolfCryptPKIXCertPathValidatorTest { } } + /** + * PREFER_CRLS/NO_FALLBACK with revocation enabled and a CertStore that + * loads no matching CRL (loadedCount == 0) must fail closed. + */ + @Test + public void testPreferCrlsNoFallbackEmptyCertStoreFailsClosed() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println("CertPathValidator revocation status test " + + "skipped, CRL not compiled in"); + return; + } + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + /* Non-empty store list holding no matching CRL drives loadedCount 0 */ + List certStores = new ArrayList<>(); + certStores.add(CertStore.getInstance("Collection", + new CollectionCertStoreParameters(new HashSet()))); + params.setCertStores(certStores); + + try { + cpv.validate(path, params); + fail("Expected UNDETERMINED_REVOCATION_STATUS"); + } catch (CertPathValidatorException e) { + assertEquals(BasicReason.UNDETERMINED_REVOCATION_STATUS, + e.getReason()); + } + } + + /** + * Same as above with SOFT_FAIL, validation must complete because the + * missing-CRL path disables the native CRL check. + */ + @Test + public void testPreferCrlsNoFallbackEmptyCertStoreSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println("CertPathValidator revocation status test " + + "skipped, CRL not compiled in"); + return; + } + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + List certStores = new ArrayList<>(); + certStores.add(CertStore.getInstance("Collection", + new CollectionCertStoreParameters(new HashSet()))); + params.setCertStores(certStores); + + cpv.validate(path, params); + } + /** * Test that zero-length cert paths are valid per RFC 5280. This occurs * when CertPathBuilder determines the trust anchor itself is the target.