Skip to content

Commit e0730f3

Browse files
committed
JCE: fix PKIXCertPathValidator revocation status index and PKIXRevocationChecker mutable map for SunJCE interop
1 parent d98e199 commit e0730f3

File tree

4 files changed

+207
-15
lines changed

4 files changed

+207
-15
lines changed

src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -733,28 +733,56 @@ public TrustAnchor findTrustAnchor(PKIXParameters params,
733733
return anchorFound;
734734
}
735735

736+
/**
737+
* Throw CertPathValidatorException for undetermined revocation status.
738+
*
739+
* Used when revocation checking is enabled but no CRLs are available
740+
* and no PKIXRevocationChecker is configured to handle OCSP.
741+
*
742+
* @param message error message describing the revocation failure
743+
* @param certPath the CertPath being validated (for exception reporting)
744+
* @param certs list of certificates from certPath
745+
*
746+
* @throws CertPathValidatorException always thrown with
747+
* UNDETERMINED_REVOCATION_STATUS reason
748+
*/
749+
private void throwUndeterminedRevocationStatus(String message,
750+
CertPath certPath, List<X509Certificate> certs)
751+
throws CertPathValidatorException {
752+
753+
/* Report index of last cert in path (closest to trust anchor)
754+
* to match SunJCE behavior. */
755+
int failIndex = 0;
756+
if (certs != null && certs.size() > 1) {
757+
failIndex = certs.size() - 1;
758+
}
759+
throw new CertPathValidatorException(message, null, certPath,
760+
failIndex, BasicReason.UNDETERMINED_REVOCATION_STATUS);
761+
}
762+
736763
/**
737764
* Check if revocation has been enabled in PKIXParameters, and if so
738765
* find and load any CRLs in params.getCertStores().
739766
*
740767
* When a PKIXRevocationChecker has been registered via
741768
* addCertPathChecker(), that checker handles revocation checking. CRL
742769
* checking in the native CertManager is only enabled if:
743-
* - No PKIXRevocationChecker is present (default CRL behavior), OR
770+
* - No PKIXRevocationChecker is present (default CRL behavior), or
744771
* - PKIXRevocationChecker has PREFER_CRLS option set
745772
*
746773
* @param params parameters used to check if revocation is enabled
747-
* and if so load any CRLs available
774+
* and, if so load any CRLs available
748775
* @param cm WolfSSLCertManager to load CRLs into
749-
* @param targetCert peer/leaf cert used to find matching CRL
776+
* @param certPath the CertPath being validated (for exception reporting)
777+
* @param certs list of certificates from certPath
750778
* @param pathCheckers list of registered CertPathCheckers
751779
*
752780
* @throws CertPathValidatorException if error is encountered during
753781
* revocation checking or CRL loading
754782
*/
755783
private void checkRevocationEnabledAndLoadCRLs(
756784
PKIXParameters params, WolfSSLCertManager cm,
757-
X509Certificate targetCert,
785+
CertPath certPath, List<X509Certificate> certs,
758786
List<PKIXCertPathChecker> pathCheckers)
759787
throws CertPathValidatorException {
760788

@@ -810,12 +838,22 @@ private void checkRevocationEnabledAndLoadCRLs(
810838

811839
/* Enable CRL in native WolfSSLCertManager */
812840
cm.CertManagerEnableCRL(WolfCrypt.WOLFSSL_CRL_CHECK);
813-
814841
log("CRL support enabled in native WolfSSLCertManager");
815842

816843
stores = params.getCertStores();
817844
if (stores == null || stores.isEmpty()) {
818845
log("no CertStores in PKIXParameters to load CRLs");
846+
847+
/* If revocation is enabled but no CRLs and no
848+
* PKIXRevocationChecker to handle OCSP, we cannot determine
849+
* revocation status. Per RFC 5280, this should fail. */
850+
if (!hasRevocationChecker) {
851+
throwUndeterminedRevocationStatus(
852+
"Revocation checking enabled but no CRLs available " +
853+
"and no PKIXRevocationChecker configured for OCSP",
854+
certPath, certs);
855+
}
856+
819857
return;
820858
}
821859

@@ -850,7 +888,7 @@ private void checkRevocationEnabledAndLoadCRLs(
850888

851889
/* Create CRL selector to help match target X509Certificate */
852890
X509CRLSelector selector = new X509CRLSelector();
853-
selector.setCertificateChecking(targetCert);
891+
selector.setCertificateChecking(certs.get(0));
854892

855893
try {
856894
/* Find and load any matching CRLs */
@@ -868,6 +906,16 @@ private void checkRevocationEnabledAndLoadCRLs(
868906
}
869907

870908
log("loaded " + loadedCount + " CRLs into WolfSSLCertManager");
909+
910+
/* If no CRLs were loaded and no PKIXRevocationChecker is handling
911+
* OCSP, we cannot determine revocation status. */
912+
if (loadedCount == 0 && !hasRevocationChecker) {
913+
throwUndeterminedRevocationStatus(
914+
"Revocation checking enabled but no CRLs found in " +
915+
"CertStores and no PKIXRevocationChecker configured " +
916+
"for OCSP",
917+
certPath, certs);
918+
}
871919
}
872920
else {
873921
log("revocation not enabled in PKIXParameters");
@@ -1040,7 +1088,7 @@ public CertPathValidatorResult engineValidate(
10401088
* will try to find/verify CRL against trusted roots on load.
10411089
* Pass pathCheckers so we can skip CRL setup when a
10421090
* PKIXRevocationChecker is handling revocation via OCSP. */
1043-
checkRevocationEnabledAndLoadCRLs(pkixParams, cm, certs.get(0),
1091+
checkRevocationEnabledAndLoadCRLs(pkixParams, cm, certPath, certs,
10441092
pathCheckers);
10451093

10461094
/* Verify cert chain */

src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,11 +545,16 @@ public void setOcspResponses(Map<X509Certificate, byte[]> responses) {
545545
/**
546546
* Get pre-loaded OCSP responses.
547547
*
548+
* Returns the internal mutable map, not an unmodifiable copy.
549+
* JDK sun.security.validator.PKIXValidator.addResponses() expects
550+
* to be able to add OCSP responses to this map when using the internal
551+
* Validator API.
552+
*
548553
* @return Map of certificates to OCSP response bytes
549554
*/
550555
@Override
551556
public Map<X509Certificate, byte[]> getOcspResponses() {
552-
return Collections.unmodifiableMap(this.ocspResponses);
557+
return this.ocspResponses;
553558
}
554559

555560
/**

src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,146 @@ public void testAlgorithmConstraintsWithSHAVariant() throws Exception {
16751675
}
16761676
}
16771677

1678+
/**
1679+
* Test that revocation checking fails with UNDETERMINED_REVOCATION_STATUS
1680+
* when revocation is enabled but no CRLs are available and no
1681+
* PKIXRevocationChecker is registered for OCSP.
1682+
*/
1683+
@Test
1684+
public void testRevocationEnabledNoCRLsFailsWithUndeterminedStatus()
1685+
throws FileNotFoundException, KeyStoreException, IOException,
1686+
NoSuchAlgorithmException, CertificateException,
1687+
InvalidAlgorithmParameterException, NoSuchProviderException,
1688+
Exception {
1689+
1690+
KeyStore store = null;
1691+
CertificateFactory certFactory = null;
1692+
InputStream fis = null;
1693+
Certificate serverCert = null;
1694+
List<Certificate> certList = new ArrayList<>();
1695+
1696+
if (!WolfCrypt.CrlEnabled()) {
1697+
/* Native CRL not enabled, skip test */
1698+
System.out.println("CertPathValidator revocation status test " +
1699+
"skipped, CRL not compiled in");
1700+
return;
1701+
}
1702+
1703+
/* Use example KeyStore that verifies server-cert.der */
1704+
store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass);
1705+
if (store == null || store.size() != 1) {
1706+
throw new Exception("Error creating KeyStore");
1707+
}
1708+
1709+
certFactory = CertificateFactory.getInstance("X.509");
1710+
1711+
/* Import server-cert.der into Certificate object */
1712+
fis = new FileInputStream(serverCertDer);
1713+
serverCert = certFactory.generateCertificate(fis);
1714+
certList.add(serverCert);
1715+
fis.close();
1716+
1717+
/* Create PKIXParameters with trusted KeyStore */
1718+
PKIXParameters params = new PKIXParameters(store);
1719+
1720+
/* Enable revocation checking but DO NOT add any CRLs or
1721+
* PKIXRevocationChecker. This should cause validation to fail
1722+
* with UNDETERMINED_REVOCATION_STATUS. */
1723+
params.setRevocationEnabled(true);
1724+
params.setCertStores(null);
1725+
1726+
/* Validate cert chain, should fail */
1727+
CertPath path = certFactory.generateCertPath(certList);
1728+
CertPathValidator cpv =
1729+
CertPathValidator.getInstance("PKIX", provider);
1730+
1731+
try {
1732+
cpv.validate(path, params);
1733+
fail("Expected CertPathValidatorException with " +
1734+
"UNDETERMINED_REVOCATION_STATUS when revocation is enabled " +
1735+
"but no CRLs or OCSP checker is available");
1736+
1737+
} catch (CertPathValidatorException e) {
1738+
/* Expected - verify reason is UNDETERMINED_REVOCATION_STATUS */
1739+
assertEquals("Expected BasicReason.UNDETERMINED_REVOCATION_STATUS",
1740+
BasicReason.UNDETERMINED_REVOCATION_STATUS, e.getReason());
1741+
}
1742+
}
1743+
1744+
/**
1745+
* Test that revocation checking fails with UNDETERMINED_REVOCATION_STATUS
1746+
* when revocation is enabled and CertStores exist but contain no CRLs.
1747+
*/
1748+
@Test
1749+
public void testRevocationEnabledEmptyCertStoreFailsWithUndeterminedStatus()
1750+
throws FileNotFoundException, KeyStoreException, IOException,
1751+
NoSuchAlgorithmException, CertificateException,
1752+
InvalidAlgorithmParameterException, NoSuchProviderException,
1753+
Exception {
1754+
1755+
KeyStore store = null;
1756+
CertificateFactory certFactory = null;
1757+
InputStream fis = null;
1758+
Certificate serverCert = null;
1759+
List<Certificate> certList = new ArrayList<>();
1760+
CertStore emptyCertStore = null;
1761+
List<CertStore> certStores = null;
1762+
1763+
if (!WolfCrypt.CrlEnabled()) {
1764+
/* Native CRL not enabled, skip test */
1765+
System.out.println("CertPathValidator revocation status test " +
1766+
"skipped, CRL not compiled in");
1767+
return;
1768+
}
1769+
1770+
/* Use example KeyStore that verifies server-cert.der */
1771+
store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass);
1772+
if (store == null || store.size() != 1) {
1773+
throw new Exception("Error creating KeyStore");
1774+
}
1775+
1776+
certFactory = CertificateFactory.getInstance("X.509");
1777+
1778+
/* Import server-cert.der into Certificate object */
1779+
fis = new FileInputStream(serverCertDer);
1780+
serverCert = certFactory.generateCertificate(fis);
1781+
certList.add(serverCert);
1782+
fis.close();
1783+
1784+
/* Create PKIXParameters with trusted KeyStore */
1785+
PKIXParameters params = new PKIXParameters(store);
1786+
1787+
/* Enable revocation checking with empty CertStore (no CRLs).
1788+
* This should cause validation to fail with
1789+
* UNDETERMINED_REVOCATION_STATUS. */
1790+
params.setRevocationEnabled(true);
1791+
1792+
/* Create empty CertStore */
1793+
Collection<CRL> emptyCrls = new HashSet<>();
1794+
emptyCertStore = CertStore.getInstance("Collection",
1795+
new CollectionCertStoreParameters(emptyCrls));
1796+
certStores = new ArrayList<>();
1797+
certStores.add(emptyCertStore);
1798+
params.setCertStores(certStores);
1799+
1800+
/* Validate cert chain, should fail */
1801+
CertPath path = certFactory.generateCertPath(certList);
1802+
CertPathValidator cpv =
1803+
CertPathValidator.getInstance("PKIX", provider);
1804+
1805+
try {
1806+
cpv.validate(path, params);
1807+
fail("Expected CertPathValidatorException with " +
1808+
"UNDETERMINED_REVOCATION_STATUS when revocation is enabled " +
1809+
"but CertStore contains no CRLs");
1810+
1811+
} catch (CertPathValidatorException e) {
1812+
/* Expected - verify reason is UNDETERMINED_REVOCATION_STATUS */
1813+
assertEquals("Expected BasicReason.UNDETERMINED_REVOCATION_STATUS",
1814+
BasicReason.UNDETERMINED_REVOCATION_STATUS, e.getReason());
1815+
}
1816+
}
1817+
16781818
/**
16791819
* Test that zero-length cert paths are valid per RFC 5280. This occurs
16801820
* when CertPathBuilder determines the trust anchor itself is the target.

src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -319,13 +319,12 @@ public void testRevocationCheckerGetSetOcspResponses() throws Exception {
319319
assertEquals(1, responses.size());
320320
assertTrue(responses.containsKey(testCert));
321321

322-
/* Returned map should be unmodifiable */
323-
try {
324-
responses.put(testCert, new byte[] {0x04});
325-
fail("Expected UnsupportedOperationException");
326-
} catch (UnsupportedOperationException e) {
327-
/* expected */
328-
}
322+
/* Returned map must be mutable for compatibility with JDK
323+
* sun.security.validator.PKIXValidator.addResponses() which adds
324+
* OCSP responses directly to the map returned by getOcspResponses(). */
325+
responses.put(testCert, new byte[] {0x04});
326+
assertEquals(1, responses.size());
327+
assertArrayEquals(new byte[] {0x04}, responses.get(testCert));
329328

330329
/* Set null should clear to empty map */
331330
checker.setOcspResponses(null);

0 commit comments

Comments
 (0)