From ddc68f4f7c0f82d3270a34ca7781be7e851bb40f Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Fri, 13 Feb 2026 14:44:06 -0700 Subject: [PATCH 1/7] JCE: add jdk.certpath.disabledAlgorithms enforcement to CertPathBuilder and fix trust anchor key constraint checking in CertPathValidator --- .../jce/WolfCryptPKIXCertPathBuilder.java | 313 +++++++- .../jce/WolfCryptPKIXCertPathValidator.java | 55 ++ .../WolfCryptPKIXCertPathBuilderTest.java | 735 ++++++++++++++++++ .../WolfCryptPKIXCertPathValidatorTest.java | 174 +++++ 4 files changed, 1241 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java index 17d317f0..57627613 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java @@ -39,6 +39,8 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.cert.TrustAnchor; import java.security.cert.CertPathBuilderSpi; @@ -115,6 +117,170 @@ private void sanitizeCertPathParameters(CertPathParameters params) } } + /** + * Check certificate against disabled algorithms constraints from security + * property jdk.certpath.disabledAlgorithms. + * + * Validates both the signature algorithm and public key algorithm/size + * against the disabled algorithms list. + * + * @param cert certificate to check + * + * @throws CertPathBuilderException if algorithm is disabled or key size is + * too small + */ + private void checkAlgorithmConstraints(X509Certificate cert) + throws CertPathBuilderException { + + String sigAlg = null; + PublicKey pubKey = null; + String propertyName = "jdk.certpath.disabledAlgorithms"; + + if (cert == null) { + throw new CertPathBuilderException( + "X509Certificate is null when checking algorithm constraints"); + } + + /* Check signature algorithm against disabled list */ + sigAlg = cert.getSigAlgName(); + if (WolfCryptUtil.isAlgorithmDisabled(sigAlg, propertyName)) { + log("Algorithm constraints check failed on signature algorithm: " + + sigAlg); + throw new CertPathBuilderException( + "Algorithm constraints check failed on signature algorithm: " + + sigAlg); + } + + /* Check public key algorithm and size against constraints */ + pubKey = cert.getPublicKey(); + if (!WolfCryptUtil.isKeyAllowed(pubKey, propertyName)) { + log("Algorithm constraints check failed on public key: " + + pubKey.getAlgorithm()); + throw new CertPathBuilderException( + "Algorithm constraints check failed on public key: " + + pubKey.getAlgorithm()); + } + } + + /** + * Check trust anchor against disabled algorithms constraints from + * security property jdk.certpath.disabledAlgorithms. + * + * Handles both trust anchors with certificates and those with only a + * public key (no cert). Only checks the public key algorithm and size, + * not the signature algorithm, since trust anchors are self-signed and + * their signatures are inherently trusted (match SunJCE behavior). + * + * @param anchor trust anchor to check + * + * @throws CertPathBuilderException if key algorithm is disabled or key + * size is too small + */ + private void checkTrustAnchorConstraints(TrustAnchor anchor) + throws CertPathBuilderException { + + PublicKey pubKey = null; + String propertyName = "jdk.certpath.disabledAlgorithms"; + + if (anchor == null) { + throw new CertPathBuilderException( + "TrustAnchor is null when checking trust anchor constraints"); + } + + X509Certificate cert = anchor.getTrustedCert(); + if (cert != null) { + pubKey = cert.getPublicKey(); + } + else { + pubKey = anchor.getCAPublicKey(); + } + + if (pubKey == null) { + throw new CertPathBuilderException( + "Trust anchor has no public key to check algo constraints"); + } + + if (!WolfCryptUtil.isKeyAllowed(pubKey, propertyName)) { + log("Algorithm constraints check failed on trust " + + "anchor public key: " + pubKey.getAlgorithm()); + throw new CertPathBuilderException( + "Algorithm constraints check failed on trust " + + "anchor public key: " + pubKey.getAlgorithm()); + } + } + + /** + * Check if certificate uses a disabled algorithm. + * + * Same checks as checkAlgorithmConstraints() but returns boolean + * instead of throwing, for use in filtering intermediate certificates. + * + * @param cert certificate to check + * + * @return true if certificate uses a disabled algorithm, false if allowed + */ + private boolean hasDisabledAlgorithm(X509Certificate cert) { + + try { + checkAlgorithmConstraints(cert); + return false; + } + catch (CertPathBuilderException e) { + return true; + } + } + + /** + * Check that each certificate in the path was signed by a key that meets + * algorithm constraints. Walks the chain from target toward trust anchor, + * checking each signer's public key against + * jdk.certpath.disabledAlgorithms. + * + * @param path list of certificates (target at index 0) + * @param anchor trust anchor that signed the last cert in path + * + * @throws CertPathBuilderException if a signer key is disabled + */ + private void checkSignerKeyConstraints( + List path, TrustAnchor anchor) + throws CertPathBuilderException { + + String propertyName = "jdk.certpath.disabledAlgorithms"; + + for (int i = 0; i < path.size(); i++) { + + PublicKey signerKey = null; + + if (i < path.size() - 1) { + /* Signer is the next cert in the path */ + signerKey = path.get(i + 1).getPublicKey(); + } + else { + /* Last cert in path is signed by trust anchor */ + X509Certificate anchorCert = anchor.getTrustedCert(); + if (anchorCert != null) { + signerKey = anchorCert.getPublicKey(); + } + else { + signerKey = anchor.getCAPublicKey(); + } + } + + if (signerKey != null && + !WolfCryptUtil.isKeyAllowed(signerKey, propertyName)) { + + String certName = + path.get(i).getSubjectX500Principal().getName(); + log("Algorithm constraints check failed on signer key for: " + + certName + ", signer key algorithm: " + + signerKey.getAlgorithm()); + throw new CertPathBuilderException( + "Algorithm constraints check failed on signer key for: " + + certName); + } + } + } + /** * Find the target certificate based on the X509CertSelector in params. * @@ -545,15 +711,22 @@ private static class NativeChainResult { * * Searches through provided CertStores for CA certificates * (basicConstraints >= 0) and returns them as DER-encoded byte arrays. - * Trust anchor certificates are excluded from the results. + * Certificates are excluded if they are trust anchors, use disabled + * algorithms per jdk.certpath.disabledAlgorithms, or are not valid at the + * requested validation date (when checkDateInJava is true). * * @param certStores list of CertStores to search * @param anchors set of trust anchors to exclude + * @param checkDateInJava true to filter out certificates not valid at + * validationDate + * @param validationDate date to check validity against, only used when + * checkDateInJava is true * * @return list of DER-encoded intermediate certificates */ private List collectIntermediateCertificates( - List certStores, Set anchors) { + List certStores, Set anchors, + boolean checkDateInJava, Date validationDate) { List intermediatesDer = new ArrayList<>(); @@ -572,7 +745,8 @@ private List collectIntermediateCertificates( for (Certificate c : certs) { if (c instanceof X509Certificate) { X509Certificate x509 = (X509Certificate) c; - /* Skip if it's a trust anchor */ + + /* Skip if trust anchor */ boolean isTrustAnchor = false; for (TrustAnchor anchor : anchors) { X509Certificate ac = anchor.getTrustedCert(); @@ -581,11 +755,32 @@ private List collectIntermediateCertificates( break; } } - if (!isTrustAnchor) { - intermediatesDer.add(x509.getEncoded()); - log("collected intermediate: " + - x509.getSubjectX500Principal().getName()); + + /* Skip adding if cert uses disabled algorithm */ + if (isTrustAnchor || hasDisabledAlgorithm(x509)) { + continue; + } + + /* If requested, check validity at the validation date + * in Java. Needed when a custom validation date is set + * but native X509_STORE check_time propagation is not + * supported by linked native wolfSSL vesrion. */ + if (checkDateInJava) { + try { + x509.checkValidity(validationDate); + } catch (CertificateExpiredException | + CertificateNotYetValidException e) { + log("skipping intermediate not valid at " + + "requested date: " + + x509.getSubjectX500Principal().getName()); + continue; + } } + + /* Convert to DER and add to list */ + intermediatesDer.add(x509.getEncoded()); + log("collected intermediate: " + + x509.getSubjectX500Principal().getName()); } } @@ -619,6 +814,7 @@ private NativeChainResult buildAndVerifyPathNative( int maxPathLength = 0; Date validationDate = null; WolfSSLX509StoreCtx storeCtx = null; + boolean checkDateInJava = false; log("building and verifying path using native WOLFSSL_X509_STORE"); @@ -632,32 +828,68 @@ private NativeChainResult buildAndVerifyPathNative( maxPathLength = params.getMaxPathLength(); validationDate = params.getDate(); + /* Determine if we need to validate cert dates in Java. Needed when a + * custom date is set but the native wolfSSL X509_STORE check_time + * propagation is not supported by linked version of wolfSSL. */ + if ((validationDate != null) && + !WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) { + checkDateInJava = true; + } + + if (checkDateInJava) { + log("validating cert dates in Java, X509_STORE check_time " + + "not supported"); + + /* Validate target cert date */ + try { + targetCert.checkValidity(validationDate); + } catch (CertificateExpiredException | + CertificateNotYetValidException e) { + throw new CertPathBuilderException("Target certificate not " + + "valid at requested date " + validationDate + ": " + + targetCert.getSubjectX500Principal().getName(), e); + } + + /* Skip trust anchors not valid at requested date */ + Set validAnchors = new HashSet<>(); + for (TrustAnchor anchor : anchors) { + X509Certificate ac = anchor.getTrustedCert(); + if (ac == null) { + /* No cert, keep anchor (can't check date without cert) */ + validAnchors.add(anchor); + continue; + } + try { + ac.checkValidity(validationDate); + validAnchors.add(anchor); + } catch (CertificateExpiredException | + CertificateNotYetValidException e) { + log("trust anchor not valid at requested date, skipping: " + + ac.getSubjectX500Principal().getName()); + } + } + if (validAnchors.isEmpty()) { + throw new CertPathBuilderException( + "No trust anchors valid at requested date:" + + validationDate); + } + anchors = validAnchors; + } + try { storeCtx = new WolfSSLX509StoreCtx(); - /* If a custom validation date is specified, we skip date - * validation when adding certificates, then set the custom - * verification time for chain verification. - * - * This allows for testing/use of expired certs if desired, - * or validating against a specific date. + /* If a custom validation date is specified and native X509_STORE + * check_time is supported, skip date validation when adding certs, + * then set the custom verify time for chain verification. * * SunJCE only validates expiration dates on chain verification, * not cert loading, so our default behavior already does some - * extra validation here in the case when a custom date is not - * set. */ - if (validationDate != null) { - if (!WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) { - throw new CertPathBuilderException( - "PKIXBuilderParameters.setDate() requires " + - "a wolfSSL version that supports X509_STORE " + - "check_time propagation (> 5.8.4)"); - } + * extra validation here in the case when a custom date not set. */ + if ((validationDate != null) && !checkDateInJava) { log("using custom validation date: " + validationDate); - storeCtx.setFlags( - WolfSSLX509StoreCtx.WOLFSSL_NO_CHECK_TIME); - storeCtx.setVerificationTime( - validationDate.getTime() / 1000); + storeCtx.setFlags(WolfSSLX509StoreCtx.WOLFSSL_NO_CHECK_TIME); + storeCtx.setVerificationTime(validationDate.getTime() / 1000); } /* Add trust anchors to the store */ @@ -680,9 +912,10 @@ private NativeChainResult buildAndVerifyPathNative( } } - /* Collect all intermediate certificates from CertStores */ - List intermediatesDer = - collectIntermediateCertificates(certStores, anchors); + /* Collect all intermediate certificates from CertStores, filtering + * disabled algorithms and date-invalid certs when needed */ + List intermediatesDer = collectIntermediateCertificates( + certStores, anchors, checkDateInJava, validationDate); /* Convert target cert to DER */ byte[] targetDer; @@ -698,8 +931,7 @@ private NativeChainResult buildAndVerifyPathNative( byte[][] intermediatesArray = null; if (!intermediatesDer.isEmpty()) { int size = intermediatesDer.size(); - intermediatesArray = - intermediatesDer.toArray(new byte[size][]); + intermediatesArray = intermediatesDer.toArray(new byte[size][]); } /* Build and verify the chain */ @@ -734,9 +966,9 @@ private NativeChainResult buildAndVerifyPathNative( log("native chain building returned " + fullChain.size() + " certificate(s)"); - /* Chain includes target->intermediates->trust anchor. - * We need to separate the trust anchor from the path. - * The last cert in the chain should match a trust anchor. */ + /* Chain includes target->intermediates->trust anchor. Separate + * trust anchor from the path. Last cert in chain should match a + * trust anchor */ if (fullChain.isEmpty()) { throw new CertPathBuilderException( "Native chain building returned empty chain"); @@ -752,7 +984,7 @@ private NativeChainResult buildAndVerifyPathNative( anchorCert.getSubjectX500Principal().equals( lastCert.getSubjectX500Principal())) { - /* Verify it's the same cert (compare encoded) */ + /* Verify same cert (compare encoded) */ try { if (Arrays.equals(anchorCert.getEncoded(), lastCert.getEncoded())) { @@ -931,6 +1163,9 @@ public CertPathBuilderResult engineBuild(CertPathParameters params) log("target certificate is a trust anchor, " + "returning empty path"); + /* Check trust anchor public key constraints */ + checkTrustAnchorConstraints(anchor); + try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -946,12 +1181,18 @@ public CertPathBuilderResult engineBuild(CertPathParameters params) } } + /* Check target cert algorithm constraints before building */ + checkAlgorithmConstraints(targetCert); + /* Build and verify path using wolfSSL X509_STORE */ NativeChainResult result = buildAndVerifyPathNative( targetCert, pkixParams); path = result.path; trustAnchor = result.trustAnchor; + /* Check that each signer key meets constraints. */ + checkSignerKeyConstraints(path, trustAnchor); + try { /* Convert path to CertPath object */ CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -964,7 +1205,7 @@ public CertPathBuilderResult engineBuild(CertPathParameters params) log("successfully built path with " + path.size() + " certificate(s)"); - /* PolicyNode not returned, certificate policies are not supported */ + /* PolicyNode not returned, cert policies not supported */ return new PKIXCertPathBuilderResult(certPath, trustAnchor, null, targetCert.getPublicKey()); } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java index 921a1d44..e9cffdb2 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java @@ -397,6 +397,53 @@ private void checkAlgorithmConstraints(X509Certificate cert, } } + /** + * Check trust anchor against disabled algorithms constraints from + * security property jdk.certpath.disabledAlgorithms. + * + * Handle both trust anchors with certificates and those with only a + * public key (no cert). + * + * @param anchor trust anchor to check + * + * @throws CertPathValidatorException if key algorithm is disabled or key + * size is too small + */ + private void checkTrustAnchorConstraints(TrustAnchor anchor) + throws CertPathValidatorException { + + PublicKey pubKey = null; + String propertyName = "jdk.certpath.disabledAlgorithms"; + + if (anchor == null) { + throw new CertPathValidatorException( + "TrustAnchor is null when checking trust anchor constraints"); + } + + X509Certificate cert = anchor.getTrustedCert(); + if (cert != null) { + pubKey = cert.getPublicKey(); + } + else { + pubKey = anchor.getCAPublicKey(); + } + + if (pubKey == null) { + throw new CertPathValidatorException( + "Trust anchor has no public key to check against " + + "algo constraints"); + } + + if (!WolfCryptUtil.isKeyAllowed(pubKey, propertyName)) { + log("Algo constraints check failed on trust anchor public key: " + + pubKey.getAlgorithm()); + throw new CertPathValidatorException( + "Algo constraints check failed on trust anchor public key: " + + pubKey.getAlgorithm(), null, null, -1, + BasicReason.ALGORITHM_CONSTRAINED); + } + } + /** * Check X509Certificate against constraints or settings inside * PKIXParameters. @@ -1022,6 +1069,9 @@ public CertPathValidatorResult engineValidate( log("Zero-length cert path, returning trust anchor: " + anchorCert.getSubjectX500Principal().getName()); + /* Check trust anchor public key constraints */ + checkTrustAnchorConstraints(anchor); + return new PKIXCertPathValidatorResult(anchor, null, anchorCert.getPublicKey()); } @@ -1099,6 +1149,11 @@ public CertPathValidatorResult engineValidate( trustAnchor = findTrustAnchor( pkixParams, certs.get(certs.size() - 1)); + /* Check trust anchor public key constraints */ + if (trustAnchor != null) { + checkTrustAnchorConstraints(trustAnchor); + } + } finally { /* Free native WolfSSLCertManager resources */ cm.free(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java index 65fce67c..949ec3ba 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java @@ -65,6 +65,7 @@ import java.security.cert.CertStore; import java.security.cert.CollectionCertStoreParameters; import java.lang.IllegalArgumentException; +import java.util.Calendar; import java.util.Date; import com.wolfssl.wolfcrypt.WolfCrypt; @@ -3672,5 +3673,739 @@ public void testExpiredCertsFailWithoutCustomDate() "Failed to add certificate")); } } + + /** + * Test that building a cert path fails when the signature algorithm + * (SHA256) is in the disabled algorithms list. + */ + @Test + public void testAlgorithmConstraintsRejectsSignatureAlgo() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + Collection certCollection = new ArrayList<>(); + + /* Save original security property value */ + origProperty = Security.getProperty( + "jdk.certpath.disabledAlgorithms"); + + try { + /* Load KeyStore with CA cert as trust anchor */ + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + /* Load server cert (uses SHA256withRSA) */ + serverCert = loadCertFromFile(serverCertDer); + certCollection.add(serverCert); + + /* Set SHA256 as disabled algorithm */ + Security.setProperty("jdk.certpath.disabledAlgorithms", "SHA256"); + + /* Create CertStore with target cert */ + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + /* Create PKIXBuilderParameters */ + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + /* Set target cert selector */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + /* Build cert path, should fail */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for SHA256 " + + "disabled algorithm"); + } catch (CertPathBuilderException e) { + /* Expected exception */ + assertNotNull("Exception message should not be null", + e.getMessage()); + assertTrue( + "Exception should mention algorithm constraints, got: " + + e.getMessage(), e.getMessage().contains( + "Algorithm constraints")); + } + + } finally { + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that building a cert path fails when the key algorithm (RSA) is in + * the disabled algorithms list. + */ + @Test + public void testAlgorithmConstraintsRejectsKeyAlgo() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + Collection certCollection = new ArrayList<>(); + + /* Save original security property value */ + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); + + try { + /* Load KeyStore with CA cert as trust anchor */ + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + /* Load server cert (uses RSA public key) */ + serverCert = loadCertFromFile(serverCertDer); + certCollection.add(serverCert); + + /* Set RSA as disabled algorithm */ + Security.setProperty("jdk.certpath.disabledAlgorithms", "RSA"); + + /* Create CertStore with target cert */ + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + /* Create PKIXBuilderParameters */ + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + /* Set target cert selector */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + /* Build cert path, should fail */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for " + + "RSA disabled algorithm"); + } catch (CertPathBuilderException e) { + /* Expected exception */ + assertNotNull("Exception message should not be null", + e.getMessage()); + assertTrue( + "Exception should mention algorithm constraints, got: " + + e.getMessage(), e.getMessage().contains( + "Algorithm constraints")); + } + + } finally { + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that building a cert path fails when the RSA key size is smaller + * than the minimum specified in disabled algorithms. + */ + @Test + public void testAlgorithmConstraintsRejectsKeySize() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + Collection certCollection = new ArrayList<>(); + + /* Save original security property value */ + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); + + try { + /* Load KeyStore with CA cert as trust anchor */ + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + /* Load server cert (uses 2048-bit RSA key) */ + serverCert = loadCertFromFile(serverCertDer); + certCollection.add(serverCert); + + /* Set minimum RSA key size to 4096 bits, which will + * reject our 2048-bit certificate */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 4096"); + + /* Create CertStore with target cert */ + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + /* Create PKIXBuilderParameters */ + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + /* Set target cert selector */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + /* Build cert path - should fail */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for " + + "RSA key size constraint"); + } catch (CertPathBuilderException e) { + /* Expected exception */ + assertNotNull("Exception message should not be null", + e.getMessage()); + assertTrue( + "Exception should mention algorithm constraints, got: " + + e.getMessage(), e.getMessage().contains( + "Algorithm constraints")); + } + + } finally { + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that building a cert path fails when SHA-256 (hyphenated variant) + * is in the disabled algorithms list, which should match SHA256withRSA + * via decomposition. + */ + @Test + public void testAlgorithmConstraintsRejectsAlgoVariant() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + Collection certCollection = new ArrayList<>(); + + /* Save original security property value */ + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); + + try { + /* Load KeyStore with CA cert as trust anchor */ + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + /* Load server cert (uses SHA256withRSA) */ + serverCert = loadCertFromFile(serverCertDer); + certCollection.add(serverCert); + + /* Set SHA-256 (hyphenated) as disabled algorithm */ + Security.setProperty("jdk.certpath.disabledAlgorithms", "SHA-256"); + + /* Create CertStore with target cert */ + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + /* Create PKIXBuilderParameters */ + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + /* Set target cert selector */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + /* Build cert path, should fail */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for " + + "SHA-256 disabled algorithm variant"); + } catch (CertPathBuilderException e) { + /* Expected exception */ + assertNotNull("Exception message should not be null", + e.getMessage()); + assertTrue("Exception should mention algorithm " + + "constraints, got: " + e.getMessage(), + e.getMessage().contains("Algorithm constraints")); + } + + } finally { + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that building a cert path fails when trust anchor's public key size + * violates the disabled algorithms constraint, even when the target cert + * is the trust anchor. + */ + @Test + public void testTrustAnchorKeyConstraintsRejectsKeySize() + throws Exception { + + String origProperty = null; + CertificateFactory cf = null; + X509Certificate caCert = null; + TrustAnchor anchor = null; + Set anchors = null; + FileInputStream fis = null; + + /* Save original security property value */ + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); + + try { + /* Load CA cert (uses 2048-bit RSA key) */ + cf = CertificateFactory.getInstance("X.509"); + fis = new FileInputStream(caCertDer); + caCert = (X509Certificate)cf.generateCertificate(fis); + fis.close(); + + /* Set minimum RSA key size to 4096 bits */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 4096"); + + /* Setup trust anchor with CA cert */ + anchor = new TrustAnchor(caCert, null); + anchors = new HashSet(); + anchors.add(anchor); + + /* Create PKIXBuilderParameters with CA cert as + * both trust anchor and target */ + PKIXBuilderParameters params = + new PKIXBuilderParameters(anchors, new X509CertSelector()); + params.setRevocationEnabled(false); + + /* Set target to CA cert itself (trust anchor) */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(caCert); + params.setTargetCertConstraints(selector); + + /* Add CA cert to CertStore so it can be found */ + Collection certCollection = new ArrayList<>(); + certCollection.add(caCert); + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + params.addCertStore(certStore); + + /* Build cert path, should fail due to trust + * anchor key size constraint */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for " + + "trust anchor RSA key size constraint"); + } catch (CertPathBuilderException e) { + /* Expected exception */ + assertNotNull("Exception message should not be null", + e.getMessage()); + assertTrue("Exception should mention algorithm constraints, " + + "got: " + e.getMessage(), + e.getMessage().contains("Algorithm constraints")); + } + + } finally { + /* Close any open file streams */ + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + /* Ignore close errors */ + } + } + + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that setDate() with a date within cert validity succeeds regardless + * of native check_time support. + * + * Uses currently-valid certs and sets a custom date that is also within + * their validity period. Exercises the Java date validation fallback when + * native check_time propagation is not supported. + */ + @Test + public void testSetDateFallbackSucceedsWithValidDate() + throws Exception { + + KeyStore store = null; + X509Certificate serverCert = null; + X509Certificate caCert = null; + Collection certCollection = + new ArrayList<>(); + + /* Load KeyStore with CA cert as trust anchor */ + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + /* Load CA and server certs */ + caCert = loadCertFromFile(caCertDer); + serverCert = loadCertFromFile(serverCertDer); + certCollection.add(serverCert); + + /* Create CertStore with target cert */ + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + /* Create PKIXBuilderParameters with custom date set + * to yesterday (within cert validity period) */ + PKIXBuilderParameters params = new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + params.setDate(new Date(System.currentTimeMillis() - 86400000L)); + + /* Set target cert selector */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + /* Build cert path - should succeed with both native + * check_time and Java fallback paths */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + assertNotNull("CertPathBuilderResult should not be null", result); + checkPKIXCertPathBuilderResult(result, caCert, + serverCert.getPublicKey()); + } + + /** + * Test that setDate() with a date after cert expiry fails regardless of + * native check_time support. + * + * Uses currently-valid certs but sets a custom date far in the future + * which is after their expiry. When native check_time is supported, + * the native builder rejects. When not supported, the Java fallback date + * check rejects the target cert before native building. + */ + @Test + public void testSetDateFallbackRejectsExpiredDate() + throws Exception { + + KeyStore store = null; + X509Certificate serverCert = null; + Collection certCollection = + new ArrayList<>(); + + /* Load KeyStore with CA cert as trust anchor */ + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + /* Load server cert */ + serverCert = loadCertFromFile(serverCertDer); + certCollection.add(serverCert); + + /* Create CertStore with target cert */ + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + /* Create PKIXBuilderParameters with custom date set + * to 100 years from now (after any test cert's validity) */ + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.YEAR, 100); + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + params.setDate(cal.getTime()); + + /* Set target cert selector */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + /* Build cert path, should fail with both native + * check_time and Java fallback paths */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for date after " + + "cert expiry"); + } catch (CertPathBuilderException e) { + /* Expected */ + assertNotNull("Exception message should not be null", + e.getMessage()); + } + } + + /** + * Test that building a cert path with intermediate chain fails when + * key size constraint disables the signer's key. Uses the RSA + * intermediate chain (server -> int2 -> int1 -> root) with + * RSA keySize < 4096, so all 2048-bit signer keys are rejected. + */ + @Test + public void testSignerKeyConstraintsRejectsSmallKey() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + X509Certificate int2Cert = null; + X509Certificate int1Cert = null; + Collection certCollection = new ArrayList<>(); + + origProperty = Security.getProperty( + "jdk.certpath.disabledAlgorithms"); + + try { + store = createKeyStoreFromFile( + jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + serverCert = loadCertFromFile(intRsaServerCertDer); + int2Cert = loadCertFromFile(intRsaInt2CertDer); + int1Cert = loadCertFromFile(intRsaInt1CertDer); + certCollection.add(serverCert); + certCollection.add(int2Cert); + certCollection.add(int1Cert); + + /* Disable RSA keys smaller than 4096 bits, + * all test certs use 2048-bit RSA */ + Security.setProperty( + "jdk.certpath.disabledAlgorithms", + "RSA keySize < 4096"); + + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + CertPathBuilder cpb = + CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for " + + "RSA keySize < 4096 constraint"); + } catch (CertPathBuilderException e) { + assertNotNull( + "Exception message should not be null", + e.getMessage()); + assertTrue( + "Exception should mention algorithm " + + "constraints, got: " + e.getMessage(), + e.getMessage().contains( + "Algorithm constraints")); + } + + } finally { + if (origProperty != null) { + Security.setProperty( + "jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty( + "jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that intermediate filtering prevents chain building when + * intermediates use a disabled signature algorithm. Uses the RSA + * intermediate chain (server -> int2 -> int1 -> root) and disables + * SHA256 so intermediates are filtered out, preventing native + * chain building from finding a valid path. + */ + @Test + public void testDisabledAlgoFiltersIntermediates() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + X509Certificate int2Cert = null; + X509Certificate int1Cert = null; + Collection certCollection = new ArrayList<>(); + + origProperty = Security.getProperty( + "jdk.certpath.disabledAlgorithms"); + + try { + store = createKeyStoreFromFile( + jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + serverCert = loadCertFromFile(intRsaServerCertDer); + int2Cert = loadCertFromFile(intRsaInt2CertDer); + int1Cert = loadCertFromFile(intRsaInt1CertDer); + certCollection.add(serverCert); + certCollection.add(int2Cert); + certCollection.add(int1Cert); + + /* Disable SHA256, which is used in all cert + * signatures (SHA256withRSA). Target cert check + * should catch this before intermediates are + * even considered. */ + Security.setProperty( + "jdk.certpath.disabledAlgorithms", "SHA256"); + + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + CertPathBuilder cpb = + CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for " + + "SHA256 disabled with intermediate chain"); + } catch (CertPathBuilderException e) { + assertNotNull( + "Exception message should not be null", + e.getMessage()); + assertTrue( + "Exception should mention algorithm " + + "constraints, got: " + e.getMessage(), + e.getMessage().contains( + "Algorithm constraints")); + } + + } finally { + if (origProperty != null) { + Security.setProperty( + "jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty( + "jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that algorithm constraints allow a valid chain when + * the disabled algorithms list does not conflict with the + * chain's algorithms. Uses the RSA intermediate chain with + * only MD2 disabled (which none of the test certs use). + */ + @Test + public void testAlgorithmConstraintsAllowValidChain() + throws Exception { + + String origProperty = null; + KeyStore store = null; + X509Certificate serverCert = null; + X509Certificate int2Cert = null; + X509Certificate int1Cert = null; + X509Certificate caCert = null; + Collection certCollection = new ArrayList<>(); + + origProperty = Security.getProperty( + "jdk.certpath.disabledAlgorithms"); + + try { + store = createKeyStoreFromFile( + jksCaServerRSA2048, keyStorePass); + assertNotNull("KeyStore should not be null", store); + + caCert = loadCertFromFile(caCertDer); + serverCert = loadCertFromFile(intRsaServerCertDer); + int2Cert = loadCertFromFile(intRsaInt2CertDer); + int1Cert = loadCertFromFile(intRsaInt1CertDer); + certCollection.add(serverCert); + certCollection.add(int2Cert); + certCollection.add(int1Cert); + + /* Set disabled algorithms to MD2 only, which is + * not used by any cert in the chain. Chain should + * build successfully. */ + Security.setProperty( + "jdk.certpath.disabledAlgorithms", "MD2"); + + CertStore certStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(certCollection)); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(store, null); + params.setRevocationEnabled(false); + params.addCertStore(certStore); + + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(serverCert); + params.setTargetCertConstraints(selector); + + CertPathBuilder cpb = + CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + assertNotNull( + "CertPathBuilderResult should not be null", + result); + checkPKIXCertPathBuilderResult( + result, caCert, serverCert.getPublicKey()); + + } finally { + if (origProperty != null) { + Security.setProperty( + "jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty( + "jdk.certpath.disabledAlgorithms", ""); + } + } + } } 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 14491033..4059883f 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java @@ -1928,5 +1928,179 @@ public void testZeroLengthCertPathAnchorNoCertificate() e.getMessage().contains("no certificate")); } } + + /** + * Test that trust anchor public key constraints are enforced when + * validating a normal (non-empty) cert path. + */ + @Test + public void testTrustAnchorKeyConstraintsOnNormalPath() + throws Exception { + + String origProperty = null; + CertificateFactory cf = null; + X509Certificate caCert = null; + X509Certificate clientCert = null; + TrustAnchor anchor = null; + Set anchors = null; + PKIXParameters params = null; + CertPath path = null; + CertPathValidator validator = null; + FileInputStream fis = null; + + /* Save original security property value */ + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); + + try { + /* Load wolfSSL CA cert (2048-bit RSA key) */ + cf = CertificateFactory.getInstance("X.509"); + fis = new FileInputStream(caCertDer); + try { + caCert = (X509Certificate)cf.generateCertificate(fis); + } finally { + fis.close(); + } + + /* Load wolfSSL client cert (2048-bit RSA key) */ + fis = new FileInputStream(clientCertDer); + try { + clientCert = (X509Certificate)cf.generateCertificate(fis); + } finally { + fis.close(); + } + + /* Set minimum RSA key size to 4096 bits. The client + * cert (in the path) will fail first since it is + * checked before the trust anchor. Use a constraint + * that only affects key size to verify trust anchor + * checking also works in normal path flow. */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 4096"); + + /* Setup trust anchor with CA cert */ + anchor = new TrustAnchor(caCert, null); + anchors = new HashSet(); + anchors.add(anchor); + + params = new PKIXParameters(anchors); + params.setRevocationEnabled(false); + + /* Create CertPath with client cert */ + List certList = new ArrayList(); + certList.add(clientCert); + path = cf.generateCertPath(certList); + + /* Validate - should fail because key size is too + * small (either path cert or trust anchor) */ + validator = CertPathValidator.getInstance("PKIX", "wolfJCE"); + + try { + validator.validate(path, params); + fail("Expected CertPathValidatorException " + + "for RSA key size constraint"); + } catch (CertPathValidatorException cpve) { + assertEquals( + "Expected ALGORITHM_CONSTRAINED", + BasicReason.ALGORITHM_CONSTRAINED, cpve.getReason()); + } + + } finally { + /* Close any open file streams */ + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + /* Ignore close errors */ + } + } + + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + + /** + * Test that trust anchor public key constraints are enforced when + * validating a zero-length cert path (trust anchor is the target + * certificate). + */ + @Test + public void testTrustAnchorKeyConstraintsOnZeroLengthPath() + throws Exception { + + String origProperty = null; + CertificateFactory cf = null; + X509Certificate caCert = null; + TrustAnchor anchor = null; + Set anchors = null; + PKIXParameters params = null; + CertPath emptyPath = null; + CertPathValidator validator = null; + FileInputStream fis = null; + + /* Save original security property value */ + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); + + try { + /* Load CA cert (2048-bit RSA key) */ + cf = CertificateFactory.getInstance("X.509"); + fis = new FileInputStream(caCertDer); + caCert = (X509Certificate)cf.generateCertificate(fis); + fis.close(); + + /* Set minimum RSA key size to 4096 bits */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 4096"); + + /* Setup trust anchor with CA cert */ + anchor = new TrustAnchor(caCert, null); + anchors = new HashSet(); + anchors.add(anchor); + + params = new PKIXParameters(anchors); + params.setRevocationEnabled(false); + + /* Create zero-length (empty) CertPath */ + emptyPath = cf.generateCertPath(new ArrayList()); + + /* Validate - should fail because trust anchor + * key size is too small */ + validator = CertPathValidator.getInstance("PKIX", "wolfJCE"); + + try { + validator.validate(emptyPath, params); + fail("Expected CertPathValidatorException for trust anchor " + + "RSA key size constraint on zero-length path"); + } catch (CertPathValidatorException cpve) { + assertEquals("Expected ALGORITHM_CONSTRAINED", + BasicReason.ALGORITHM_CONSTRAINED, cpve.getReason()); + } + + } finally { + /* Close any open file streams */ + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + /* Ignore close errors */ + } + } + + /* Restore original security property */ + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } + else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } } From 64c62acc579e1bd633f99649c70d7025b1aeee47 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Fri, 13 Feb 2026 16:02:00 -0700 Subject: [PATCH 2/7] JCE: allow CertPathBuilder to work without CertStores provided --- .../jce/WolfCryptPKIXCertPathBuilder.java | 61 +++++++++------- .../WolfCryptPKIXCertPathBuilderTest.java | 71 +++++++++++++++++++ 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java index 57627613..d84ac26b 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java @@ -284,8 +284,11 @@ private void checkSignerKeyConstraints( /** * Find the target certificate based on the X509CertSelector in params. * - * Searches through all CertStores for a certificate matching the - * target constraints. + * Searches for a certificate matching the target constraints in the + * following order: + * 1. Directly set in the selector via setCertificate() + * 2. In CertStores attached to the parameters + * 3. Among trust anchor certificates * * @param params PKIXBuilderParameters containing target selector * @@ -302,8 +305,7 @@ private X509Certificate findTargetCertificate(PKIXBuilderParameters params) List certStores = null; if (params == null) { - throw new CertPathBuilderException( - "PKIXBuilderParameters is null"); + throw new CertPathBuilderException("PKIXBuilderParameters is null"); } selector = params.getTargetCertConstraints(); @@ -319,7 +321,8 @@ private X509Certificate findTargetCertificate(PKIXBuilderParameters params) } x509Selector = (X509CertSelector)selector; - log("searching for target certificate with selector: " + x509Selector); + log("searching for target certificate with selector: " + + x509Selector); /* Check if target cert is directly set in selector */ targetCert = x509Selector.getCertificate(); @@ -330,32 +333,42 @@ private X509Certificate findTargetCertificate(PKIXBuilderParameters params) /* Search through CertStores for matching certificate */ certStores = params.getCertStores(); - if (certStores == null || certStores.isEmpty()) { - throw new CertPathBuilderException( - "No CertStores provided in PKIXBuilderParameters"); - } + if (certStores != null) { + for (CertStore store : certStores) { + try { + Collection certs = + store.getCertificates(x509Selector); - for (CertStore store : certStores) { - try { - Collection certs = - store.getCertificates(x509Selector); - - if (certs != null && !certs.isEmpty()) { - /* Return first matching certificate */ - targetCert = (X509Certificate) certs.iterator().next(); - log("found target certificate: " + - targetCert.getSubjectX500Principal().getName()); - return targetCert; + if (certs != null && !certs.isEmpty()) { + targetCert = (X509Certificate)certs.iterator().next(); + log("found target certificate in CertStore: " + + targetCert.getSubjectX500Principal().getName()); + return targetCert; + } + + } catch (CertStoreException e) { + log("error searching CertStore: " + e.getMessage()); + /* Continue to next store */ } + } + } - } catch (CertStoreException e) { - log("error searching CertStore: " + e.getMessage()); - /* Continue to next store */ + /* Search trust anchor certificates as fallback */ + Set anchors = params.getTrustAnchors(); + if (anchors != null) { + for (TrustAnchor anchor : anchors) { + X509Certificate anchorCert = anchor.getTrustedCert(); + if ((anchorCert != null) && x509Selector.match(anchorCert)) { + + log("found target certificate in trust anchors: " + + anchorCert.getSubjectX500Principal().getName()); + return anchorCert; + } } } throw new CertPathBuilderException( - "Target certificate not found in CertStores"); + "Target certificate not found matching selector"); } /** diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java index 949ec3ba..de71dc70 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java @@ -66,6 +66,7 @@ import java.security.cert.CollectionCertStoreParameters; import java.lang.IllegalArgumentException; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import com.wolfssl.wolfcrypt.WolfCrypt; @@ -4407,5 +4408,75 @@ public void testAlgorithmConstraintsAllowValidChain() } } } + + /** + * Test that building a cert path with no CertStores fails gracefully with + * CertPathBuilderException instead of throwing an early error about + * missing CertStores. Uses a non-matching subject selector so the target + * cert cannot be found. + */ + @Test + public void testBuildWithNoCertStoresFailsGracefully() + throws Exception { + + X509Certificate caCert = null; + TrustAnchor anchor = null; + + caCert = loadCertFromFile(caCertDer); + anchor = new TrustAnchor(caCert, null); + + /* Selector with non-matching subject, no CertStores */ + X509CertSelector selector = new X509CertSelector(); + selector.setSubject("CN=NonExistent, O=NoOrg, C=US"); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(Collections.singleton(anchor), selector); + params.setRevocationEnabled(false); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException when target cert " + + "not found"); + } catch (CertPathBuilderException e) { + /* Expected: should fail because target cert can't be found, + * not because of missing CertStores */ + assertNotNull("Exception message should not be null", + e.getMessage()); + assertTrue("Exception should mention target not found, got: " + + e.getMessage(), e.getMessage().contains("not found")); + } + } + + /** + * Test that building a cert path with no CertStores succeeds when the + * target cert matches a trust anchor. The builder should find the target + * among trust anchors as a fallback. + */ + @Test + public void testBuildWithNoCertStoresFindsAnchor() + throws Exception { + + X509Certificate caCert = null; + TrustAnchor anchor = null; + + caCert = loadCertFromFile(caCertDer); + anchor = new TrustAnchor(caCert, null); + + /* Selector matching the trust anchor cert, no CertStores */ + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(caCert); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(Collections.singleton(anchor), selector); + params.setRevocationEnabled(false); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + assertNotNull("CertPathBuilderResult should not be null", result); + checkPKIXCertPathBuilderResult(result, caCert, caCert.getPublicKey()); + } } From bd25f66502f87489331139e9f5bc6f4f59ee0578 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Fri, 13 Feb 2026 16:30:34 -0700 Subject: [PATCH 3/7] JCE: throw InvalidAlgorithmParameterException for non-X509CertSelector target --- .../jce/WolfCryptPKIXCertPathBuilder.java | 11 ++++- .../WolfCryptPKIXCertPathBuilderTest.java | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java index d84ac26b..7cc6a5d8 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java @@ -115,6 +115,14 @@ private void sanitizeCertPathParameters(CertPathParameters params) throw new InvalidAlgorithmParameterException( "params not of type PKIXBuilderParameters"); } + + /* Target constraints must be X509CertSelector if set */ + PKIXBuilderParameters pkixParams = (PKIXBuilderParameters)params; + CertSelector selector = pkixParams.getTargetCertConstraints(); + if ((selector != null) && !(selector instanceof X509CertSelector)) { + throw new InvalidAlgorithmParameterException( + "Target certificate constraints must be X509CertSelector"); + } } /** @@ -317,7 +325,8 @@ private X509Certificate findTargetCertificate(PKIXBuilderParameters params) if (!(selector instanceof X509CertSelector)) { throw new CertPathBuilderException( - "Target certificate constraints must be X509CertSelector"); + "Target certificate constraints must be " + + "X509CertSelector"); } x509Selector = (X509CertSelector)selector; diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java index de71dc70..5b73a103 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java @@ -62,6 +62,7 @@ import java.security.cert.CertificateException; import java.security.cert.TrustAnchor; import java.security.cert.X509CertSelector; +import java.security.cert.CertSelector; import java.security.cert.CertStore; import java.security.cert.CollectionCertStoreParameters; import java.lang.IllegalArgumentException; @@ -4478,5 +4479,47 @@ public void testBuildWithNoCertStoresFindsAnchor() assertNotNull("CertPathBuilderResult should not be null", result); checkPKIXCertPathBuilderResult(result, caCert, caCert.getPublicKey()); } + + /** + * Test that building with a non-X509CertSelector target constraint + * throws InvalidAlgorithmParameterException. + */ + @Test + public void testNonX509CertSelectorThrowsInvalidAlgParam() + throws Exception { + + X509Certificate caCert = loadCertFromFile(caCertDer); + TrustAnchor anchor = new TrustAnchor(caCert, null); + + /* Custom CertSelector that is not X509CertSelector */ + CertSelector oddSelector = new CertSelector() { + public boolean match(Certificate cert) { + return false; + } + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + }; + + PKIXBuilderParameters params = new PKIXBuilderParameters( + Collections.singleton(anchor), oddSelector); + params.setRevocationEnabled(false); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected InvalidAlgorithmParameterException for " + + "non-X509CertSelector"); + } catch (InvalidAlgorithmParameterException e) { + /* Expected */ + assertNotNull("Exception message should not be null", + e.getMessage()); + } + } } From 6b316a412e97c3f21a2340bc9578891e7d1f5446 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Mon, 16 Feb 2026 16:28:04 -0700 Subject: [PATCH 4/7] JCE: remove Java date verification from CertPathBuilder, defer to CertPathValidator --- .../jce/WolfCryptPKIXCertPathBuilder.java | 127 ++--- .../WolfCryptPKIXCertPathBuilderTest.java | 434 ++++++++++++++---- 2 files changed, 371 insertions(+), 190 deletions(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java index 7cc6a5d8..7f1cf9c8 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java @@ -39,8 +39,6 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.cert.TrustAnchor; import java.security.cert.CertPathBuilderSpi; @@ -733,22 +731,16 @@ private static class NativeChainResult { * * Searches through provided CertStores for CA certificates * (basicConstraints >= 0) and returns them as DER-encoded byte arrays. - * Certificates are excluded if they are trust anchors, use disabled - * algorithms per jdk.certpath.disabledAlgorithms, or are not valid at the - * requested validation date (when checkDateInJava is true). + * Certificates are excluded if they are trust anchors or use disabled + * algorithms per jdk.certpath.disabledAlgorithms. * * @param certStores list of CertStores to search * @param anchors set of trust anchors to exclude - * @param checkDateInJava true to filter out certificates not valid at - * validationDate - * @param validationDate date to check validity against, only used when - * checkDateInJava is true * * @return list of DER-encoded intermediate certificates */ private List collectIntermediateCertificates( - List certStores, Set anchors, - boolean checkDateInJava, Date validationDate) { + List certStores, Set anchors) { List intermediatesDer = new ArrayList<>(); @@ -783,22 +775,6 @@ private List collectIntermediateCertificates( continue; } - /* If requested, check validity at the validation date - * in Java. Needed when a custom validation date is set - * but native X509_STORE check_time propagation is not - * supported by linked native wolfSSL vesrion. */ - if (checkDateInJava) { - try { - x509.checkValidity(validationDate); - } catch (CertificateExpiredException | - CertificateNotYetValidException e) { - log("skipping intermediate not valid at " + - "requested date: " + - x509.getSubjectX500Principal().getName()); - continue; - } - } - /* Convert to DER and add to list */ intermediatesDer.add(x509.getEncoded()); log("collected intermediate: " + @@ -836,7 +812,6 @@ private NativeChainResult buildAndVerifyPathNative( int maxPathLength = 0; Date validationDate = null; WolfSSLX509StoreCtx storeCtx = null; - boolean checkDateInJava = false; log("building and verifying path using native WOLFSSL_X509_STORE"); @@ -850,65 +825,15 @@ private NativeChainResult buildAndVerifyPathNative( maxPathLength = params.getMaxPathLength(); validationDate = params.getDate(); - /* Determine if we need to validate cert dates in Java. Needed when a - * custom date is set but the native wolfSSL X509_STORE check_time - * propagation is not supported by linked version of wolfSSL. */ - if ((validationDate != null) && - !WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) { - checkDateInJava = true; - } - - if (checkDateInJava) { - log("validating cert dates in Java, X509_STORE check_time " + - "not supported"); - - /* Validate target cert date */ - try { - targetCert.checkValidity(validationDate); - } catch (CertificateExpiredException | - CertificateNotYetValidException e) { - throw new CertPathBuilderException("Target certificate not " + - "valid at requested date " + validationDate + ": " + - targetCert.getSubjectX500Principal().getName(), e); - } - - /* Skip trust anchors not valid at requested date */ - Set validAnchors = new HashSet<>(); - for (TrustAnchor anchor : anchors) { - X509Certificate ac = anchor.getTrustedCert(); - if (ac == null) { - /* No cert, keep anchor (can't check date without cert) */ - validAnchors.add(anchor); - continue; - } - try { - ac.checkValidity(validationDate); - validAnchors.add(anchor); - } catch (CertificateExpiredException | - CertificateNotYetValidException e) { - log("trust anchor not valid at requested date, skipping: " + - ac.getSubjectX500Principal().getName()); - } - } - if (validAnchors.isEmpty()) { - throw new CertPathBuilderException( - "No trust anchors valid at requested date:" + - validationDate); - } - anchors = validAnchors; - } - try { storeCtx = new WolfSSLX509StoreCtx(); - /* If a custom validation date is specified and native X509_STORE - * check_time is supported, skip date validation when adding certs, - * then set the custom verify time for chain verification. - * - * SunJCE only validates expiration dates on chain verification, - * not cert loading, so our default behavior already does some - * extra validation here in the case when a custom date not set. */ - if ((validationDate != null) && !checkDateInJava) { + /* If a custom validation date is specified, set NO_CHECK_TIME so + * expired certs can be loaded into the store, then set the custom + * verify time. Native code clears NO_CHECK_TIME on the ctx when + * USE_CHECK_TIME is set, so chain verification still validates + * dates against the custom time. */ + if (validationDate != null) { log("using custom validation date: " + validationDate); storeCtx.setFlags(WolfSSLX509StoreCtx.WOLFSSL_NO_CHECK_TIME); storeCtx.setVerificationTime(validationDate.getTime() / 1000); @@ -916,7 +841,6 @@ private NativeChainResult buildAndVerifyPathNative( /* Add trust anchors to the store */ for (TrustAnchor anchor : anchors) { - X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert != null) { byte[] anchorDer; @@ -934,10 +858,10 @@ private NativeChainResult buildAndVerifyPathNative( } } - /* Collect all intermediate certificates from CertStores, filtering - * disabled algorithms and date-invalid certs when needed */ + /* Collect all intermediate certificates from CertStores, + * filtering disabled algorithms */ List intermediatesDer = collectIntermediateCertificates( - certStores, anchors, checkDateInJava, validationDate); + certStores, anchors); /* Convert target cert to DER */ byte[] targetDer; @@ -1206,11 +1130,28 @@ public CertPathBuilderResult engineBuild(CertPathParameters params) /* Check target cert algorithm constraints before building */ checkAlgorithmConstraints(targetCert); - /* Build and verify path using wolfSSL X509_STORE */ - NativeChainResult result = buildAndVerifyPathNative( - targetCert, pkixParams); - path = result.path; - trustAnchor = result.trustAnchor; + if (pkixParams.getDate() != null && + !WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) { + /* Native builder can't handle custom dates on this wolfSSL version + * (check_time not supported in X509_STORE). Fall back to Java + * chain building. Date validation deferred to CertPathValidator. */ + log("using Java chain building, native check_time not supported"); + path = buildPath(targetCert, pkixParams); + trustAnchor = findPathTrustAnchor(path, + pkixParams.getTrustAnchors()); + + /* Check algorithm constraints for all certs in path (native path + * filters during collectIntermediateCerts) */ + for (X509Certificate cert : path) { + checkAlgorithmConstraints(cert); + } + } else { + /* Build and verify path using wolfSSL X509_STORE */ + NativeChainResult result = + buildAndVerifyPathNative(targetCert, pkixParams); + path = result.path; + trustAnchor = result.trustAnchor; + } /* Check that each signer key meets constraints. */ checkSignerKeyConstraints(path, trustAnchor); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java index 5b73a103..f308b3f0 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java @@ -57,7 +57,10 @@ import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderResult; import java.security.cert.CertPathBuilderException; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorException; import java.security.cert.PKIXBuilderParameters; +import java.security.cert.PKIXParameters; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.CertificateException; import java.security.cert.TrustAnchor; @@ -3517,11 +3520,6 @@ public void testExpiredCertsWithCustomValidationDate() CertPathBuilderException, NoSuchAlgorithmException, NoSuchProviderException { - Assume.assumeTrue( - "X509_STORE check_time support not available in " + - "this wolfSSL version", - WolfSSLX509StoreCtx.isStoreCheckTimeSupported()); - /* Load certs separately for result assertions below */ X509Certificate rootCert = loadCertFromPEM(EXPIRED_ROOT_PEM); @@ -4059,22 +4057,18 @@ public void testTrustAnchorKeyConstraintsRejectsKeySize() } /** - * Test that setDate() with a date within cert validity succeeds regardless - * of native check_time support. + * Test that setDate() with a date within cert validity succeeds. * * Uses currently-valid certs and sets a custom date that is also within - * their validity period. Exercises the Java date validation fallback when - * native check_time propagation is not supported. + * their validity period. */ @Test - public void testSetDateFallbackSucceedsWithValidDate() - throws Exception { + public void testSetDateSucceedsWithValidDate() throws Exception { KeyStore store = null; X509Certificate serverCert = null; X509Certificate caCert = null; - Collection certCollection = - new ArrayList<>(); + Collection certCollection = new ArrayList<>(); /* Load KeyStore with CA cert as trust anchor */ store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); @@ -4101,8 +4095,7 @@ public void testSetDateFallbackSucceedsWithValidDate() selector.setCertificate(serverCert); params.setTargetCertConstraints(selector); - /* Build cert path - should succeed with both native - * check_time and Java fallback paths */ + /* Build cert path - should succeed with custom date */ CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); CertPathBuilderResult result = cpb.build(params); @@ -4112,22 +4105,22 @@ public void testSetDateFallbackSucceedsWithValidDate() } /** - * Test that setDate() with a date after cert expiry fails regardless of - * native check_time support. + * Test that setDate() with a date after cert expiry fails. * * Uses currently-valid certs but sets a custom date far in the future - * which is after their expiry. When native check_time is supported, - * the native builder rejects. When not supported, the Java fallback date - * check rejects the target cert before native building. + * which is after their expiry. The native builder rejects with the + * custom verification time. */ @Test - public void testSetDateFallbackRejectsExpiredDate() - throws Exception { + public void testSetDateRejectsExpiredDate() throws Exception { + + Assume.assumeTrue( + "X509_STORE check_time support not available in this " + + "wolfSSL version", WolfSSLX509StoreCtx.isStoreCheckTimeSupported()); KeyStore store = null; X509Certificate serverCert = null; - Collection certCollection = - new ArrayList<>(); + Collection certCollection = new ArrayList<>(); /* Load KeyStore with CA cert as trust anchor */ store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); @@ -4145,8 +4138,7 @@ public void testSetDateFallbackRejectsExpiredDate() * to 100 years from now (after any test cert's validity) */ Calendar cal = Calendar.getInstance(); cal.add(Calendar.YEAR, 100); - PKIXBuilderParameters params = - new PKIXBuilderParameters(store, null); + PKIXBuilderParameters params = new PKIXBuilderParameters(store, null); params.setRevocationEnabled(false); params.addCertStore(certStore); params.setDate(cal.getTime()); @@ -4156,14 +4148,13 @@ public void testSetDateFallbackRejectsExpiredDate() selector.setCertificate(serverCert); params.setTargetCertConstraints(selector); - /* Build cert path, should fail with both native - * check_time and Java fallback paths */ + /* Build cert path, should fail with custom verification + * time after cert expiry */ CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); try { cpb.build(params); - fail("Expected CertPathBuilderException for date after " + - "cert expiry"); + fail("Expected CertPathBuilderException for date after expiry"); } catch (CertPathBuilderException e) { /* Expected */ assertNotNull("Exception message should not be null", @@ -4178,8 +4169,7 @@ public void testSetDateFallbackRejectsExpiredDate() * RSA keySize < 4096, so all 2048-bit signer keys are rejected. */ @Test - public void testSignerKeyConstraintsRejectsSmallKey() - throws Exception { + public void testSignerKeyConstraintsRejectsSmallKey() throws Exception { String origProperty = null; KeyStore store = null; @@ -4188,12 +4178,10 @@ public void testSignerKeyConstraintsRejectsSmallKey() X509Certificate int1Cert = null; Collection certCollection = new ArrayList<>(); - origProperty = Security.getProperty( - "jdk.certpath.disabledAlgorithms"); + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); try { - store = createKeyStoreFromFile( - jksCaServerRSA2048, keyStorePass); + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); assertNotNull("KeyStore should not be null", store); serverCert = loadCertFromFile(intRsaServerCertDer); @@ -4205,8 +4193,7 @@ public void testSignerKeyConstraintsRejectsSmallKey() /* Disable RSA keys smaller than 4096 bits, * all test certs use 2048-bit RSA */ - Security.setProperty( - "jdk.certpath.disabledAlgorithms", + Security.setProperty("jdk.certpath.disabledAlgorithms", "RSA keySize < 4096"); CertStore certStore = CertStore.getInstance("Collection", @@ -4221,8 +4208,7 @@ public void testSignerKeyConstraintsRejectsSmallKey() selector.setCertificate(serverCert); params.setTargetCertConstraints(selector); - CertPathBuilder cpb = - CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); try { cpb.build(params); @@ -4233,21 +4219,18 @@ public void testSignerKeyConstraintsRejectsSmallKey() "Exception message should not be null", e.getMessage()); assertTrue( - "Exception should mention algorithm " + - "constraints, got: " + e.getMessage(), - e.getMessage().contains( + "Exception should mention algorithm constraints, got: " + + e.getMessage(), e.getMessage().contains( "Algorithm constraints")); } } finally { if (origProperty != null) { - Security.setProperty( - "jdk.certpath.disabledAlgorithms", + Security.setProperty("jdk.certpath.disabledAlgorithms", origProperty); } else { - Security.setProperty( - "jdk.certpath.disabledAlgorithms", ""); + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); } } } @@ -4255,13 +4238,12 @@ public void testSignerKeyConstraintsRejectsSmallKey() /** * Test that intermediate filtering prevents chain building when * intermediates use a disabled signature algorithm. Uses the RSA - * intermediate chain (server -> int2 -> int1 -> root) and disables - * SHA256 so intermediates are filtered out, preventing native - * chain building from finding a valid path. + * intermediate chain (server-int2-int1-root) and disables SHA256 so + * intermediates are filtered out, preventing native chain building from + * finding a valid path. */ @Test - public void testDisabledAlgoFiltersIntermediates() - throws Exception { + public void testDisabledAlgoFiltersIntermediates() throws Exception { String origProperty = null; KeyStore store = null; @@ -4270,12 +4252,10 @@ public void testDisabledAlgoFiltersIntermediates() X509Certificate int1Cert = null; Collection certCollection = new ArrayList<>(); - origProperty = Security.getProperty( - "jdk.certpath.disabledAlgorithms"); + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); try { - store = createKeyStoreFromFile( - jksCaServerRSA2048, keyStorePass); + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); assertNotNull("KeyStore should not be null", store); serverCert = loadCertFromFile(intRsaServerCertDer); @@ -4285,12 +4265,10 @@ public void testDisabledAlgoFiltersIntermediates() certCollection.add(int2Cert); certCollection.add(int1Cert); - /* Disable SHA256, which is used in all cert - * signatures (SHA256withRSA). Target cert check - * should catch this before intermediates are - * even considered. */ - Security.setProperty( - "jdk.certpath.disabledAlgorithms", "SHA256"); + /* Disable SHA256, which is used in all cert signatures + * (SHA256withRSA). Target cert check should catch this before + * intermediates are even considered. */ + Security.setProperty("jdk.certpath.disabledAlgorithms", "SHA256"); CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certCollection)); @@ -4304,8 +4282,7 @@ public void testDisabledAlgoFiltersIntermediates() selector.setCertificate(serverCert); params.setTargetCertConstraints(selector); - CertPathBuilder cpb = - CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); try { cpb.build(params); @@ -4316,34 +4293,30 @@ public void testDisabledAlgoFiltersIntermediates() "Exception message should not be null", e.getMessage()); assertTrue( - "Exception should mention algorithm " + - "constraints, got: " + e.getMessage(), - e.getMessage().contains( + "Exception should mention algorithm constraints, got: " + + e.getMessage(), e.getMessage().contains( "Algorithm constraints")); } } finally { if (origProperty != null) { - Security.setProperty( - "jdk.certpath.disabledAlgorithms", + Security.setProperty("jdk.certpath.disabledAlgorithms", origProperty); } else { - Security.setProperty( - "jdk.certpath.disabledAlgorithms", ""); + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); } } } /** - * Test that algorithm constraints allow a valid chain when - * the disabled algorithms list does not conflict with the - * chain's algorithms. Uses the RSA intermediate chain with - * only MD2 disabled (which none of the test certs use). + * Test that algorithm constraints allow a valid chain when the disabled + * algorithms list does not conflict with the chain's algorithms. Uses the + * RSA intermediate chain with only MD2 disabled (which none of the test + * certs use). */ @Test - public void testAlgorithmConstraintsAllowValidChain() - throws Exception { + public void testAlgorithmConstraintsAllowValidChain() throws Exception { String origProperty = null; KeyStore store = null; @@ -4353,12 +4326,10 @@ public void testAlgorithmConstraintsAllowValidChain() X509Certificate caCert = null; Collection certCollection = new ArrayList<>(); - origProperty = Security.getProperty( - "jdk.certpath.disabledAlgorithms"); + origProperty = Security.getProperty("jdk.certpath.disabledAlgorithms"); try { - store = createKeyStoreFromFile( - jksCaServerRSA2048, keyStorePass); + store = createKeyStoreFromFile(jksCaServerRSA2048, keyStorePass); assertNotNull("KeyStore should not be null", store); caCert = loadCertFromFile(caCertDer); @@ -4369,11 +4340,9 @@ public void testAlgorithmConstraintsAllowValidChain() certCollection.add(int2Cert); certCollection.add(int1Cert); - /* Set disabled algorithms to MD2 only, which is - * not used by any cert in the chain. Chain should - * build successfully. */ - Security.setProperty( - "jdk.certpath.disabledAlgorithms", "MD2"); + /* Set disabled algorithms to MD2 only, which is not used by any + * cert in the chain. Chain should build successfully. */ + Security.setProperty("jdk.certpath.disabledAlgorithms", "MD2"); CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certCollection)); @@ -4387,25 +4356,20 @@ public void testAlgorithmConstraintsAllowValidChain() selector.setCertificate(serverCert); params.setTargetCertConstraints(selector); - CertPathBuilder cpb = - CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); CertPathBuilderResult result = cpb.build(params); - assertNotNull( - "CertPathBuilderResult should not be null", - result); - checkPKIXCertPathBuilderResult( - result, caCert, serverCert.getPublicKey()); + assertNotNull("CertPathBuilderResult should not be null", result); + checkPKIXCertPathBuilderResult(result, caCert, + serverCert.getPublicKey()); } finally { if (origProperty != null) { - Security.setProperty( - "jdk.certpath.disabledAlgorithms", + Security.setProperty("jdk.certpath.disabledAlgorithms", origProperty); } else { - Security.setProperty( - "jdk.certpath.disabledAlgorithms", ""); + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); } } } @@ -4521,5 +4485,281 @@ public Object clone() { e.getMessage()); } } + + /** + * Test that builder succeeds with a bad custom date on the Java fallback + * path (no native check_time support), but that the subsequent + * CertPathValidator rejects the chain due to expired certificates. + * + * When native check_time is not supported, the builder uses Java chain + * building which does not enforce dates. Date validation is deferred to + * CertPathValidator. This test verifies that contract. + */ + @Test + public void testFallbackBuilderDefersDateCheckToValidator() + throws Exception { + + Assume.assumeTrue("Only applies when native check_time not supported", + !WolfSSLX509StoreCtx.isStoreCheckTimeSupported()); + + /* Use expired certs with date after expiry (March 2017). On the + * fallback path, the builder should succeed because buildPath() does + * not check dates. */ + PKIXBuilderParameters params = createExpiredCertParams(1489561200000L); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + /* Builder should succeed on fallback path */ + assertNotNull("Builder should succeed on fallback path (dates " + + "not checked)", result); + + PKIXCertPathBuilderResult pResult = (PKIXCertPathBuilderResult) result; + CertPath certPath = pResult.getCertPath(); + assertNotNull("CertPath should not be null", certPath); + + /* Validate the built path, this should fail because the custom + * date (March 2017) is after cert expiry (April 2016). + * CertPathValidator enforces dates. */ + X509Certificate rootCert = loadCertFromPEM(EXPIRED_ROOT_PEM); + Set anchors = new HashSet<>(); + anchors.add(new TrustAnchor(rootCert, null)); + + PKIXParameters valParams = new PKIXParameters(anchors); + valParams.setRevocationEnabled(false); + valParams.setDate(new Date(1489561200000L)); + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + try { + cpv.validate(certPath, valParams); + fail("Expected CertPathValidatorException for date after " + + "cert expiry"); + } catch (CertPathValidatorException e) { + /* Expected, validator catches date issue */ + assertNotNull("Exception message should not be null", + e.getMessage()); + } + } + + /** + * Test that builder succeeds with a bad custom date (before notBefore) on + * the Java fallback path, but CertPathValidator rejects it. + * + * Mirrors testFallbackBuilderDefersDateCheckToValidator but uses a date + * before cert validity (Jan 2014) instead of after expiry. + */ + @Test + public void testFallbackBuilderDefersNotBeforeCheckToValidator() + throws Exception { + + Assume.assumeTrue("Only applies when native check_time not supported", + !WolfSSLX509StoreCtx.isStoreCheckTimeSupported()); + + /* Use expired certs with date before validity (Jan 2014). Certs valid + * May 1, 2014 - Apr 30, 2016 */ + PKIXBuilderParameters params = createExpiredCertParams(1388534400000L); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + /* Builder should succeed on fallback path */ + assertNotNull("Builder should succeed on fallback path (dates " + + "not checked)", result); + + PKIXCertPathBuilderResult pResult = (PKIXCertPathBuilderResult) result; + CertPath certPath = pResult.getCertPath(); + assertNotNull("CertPath should not be null", certPath); + + /* Validate, should fail, date is before notBefore */ + X509Certificate rootCert = loadCertFromPEM(EXPIRED_ROOT_PEM); + Set anchors = new HashSet<>(); + anchors.add(new TrustAnchor(rootCert, null)); + + PKIXParameters valParams = new PKIXParameters(anchors); + valParams.setRevocationEnabled(false); + valParams.setDate(new Date(1388534400000L)); + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + try { + cpv.validate(certPath, valParams); + fail("Expected CertPathValidatorException for date before " + + "cert notBefore"); + } catch (CertPathValidatorException e) { + /* Expected, validator catches date issue */ + assertNotNull("Exception message should not be null", + e.getMessage()); + } + } + + /** + * Test end-to-end builder + validator with a valid custom date. Builds a + * path with expired certs using a date within their validity, then + * validates the result. Should succeed on all wolfSSL versions (native or + * Java fallback). + */ + @Test + public void testBuildThenValidateWithValidCustomDate() + throws Exception { + + X509Certificate rootCert = loadCertFromPEM(EXPIRED_ROOT_PEM); + + /* Date is March 15, 2015 (within validity 2014-2016) */ + PKIXBuilderParameters buildParams = + createExpiredCertParams(1426399200000L); + + /* Build */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(buildParams); + assertNotNull("CertPathBuilderResult should not be null", + result); + + PKIXCertPathBuilderResult pResult = + (PKIXCertPathBuilderResult) result; + CertPath certPath = pResult.getCertPath(); + assertNotNull("CertPath should not be null", certPath); + assertEquals("Path should contain 2 certificates", + 2, certPath.getCertificates().size()); + + /* Validate with same custom date, should succeed */ + Set anchors = new HashSet<>(); + anchors.add(new TrustAnchor(rootCert, null)); + + PKIXParameters valParams = new PKIXParameters(anchors); + valParams.setRevocationEnabled(false); + valParams.setDate(new Date(1426399200000L)); + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + cpv.validate(certPath, valParams); + } + + /** + * Test end-to-end builder + validator where the builder uses a valid + * custom date but the validator uses a date after expiry. Builder should + * succeed, validator should fail. + */ + @Test + public void testBuildThenValidateWithMismatchedDates() + throws Exception { + + X509Certificate rootCert = loadCertFromPEM(EXPIRED_ROOT_PEM); + + /* Build with valid date (March 2015, in validity) */ + PKIXBuilderParameters buildParams = + createExpiredCertParams(1426399200000L); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(buildParams); + assertNotNull("CertPathBuilderResult should not be null", + result); + + PKIXCertPathBuilderResult pResult = (PKIXCertPathBuilderResult) result; + CertPath certPath = pResult.getCertPath(); + + /* Validate with date AFTER expiry (March 2017) */ + Set anchors = new HashSet<>(); + anchors.add(new TrustAnchor(rootCert, null)); + + PKIXParameters valParams = new PKIXParameters(anchors); + valParams.setRevocationEnabled(false); + valParams.setDate(new Date(1489561200000L)); + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + try { + cpv.validate(certPath, valParams); + fail("Expected CertPathValidatorException for validator date " + + "after cert expiry"); + } catch (CertPathValidatorException e) { + /* Expected */ + assertNotNull("Exception message should not be null", + e.getMessage()); + } + } + + /** + * Test that when the target cert is also the trust anchor, the builder + * succeeds with a custom date set regardless of native check_time support. + * The early-return path does not check dates (trust anchor certs are + * inherently trusted). + */ + @Test + public void testTargetIsTrustAnchorWithCustomDate() + throws Exception { + + X509Certificate rootCert = + loadCertFromPEM(EXPIRED_ROOT_PEM); + + /* Set up root cert as both trust anchor and target */ + Set anchors = new HashSet<>(); + anchors.add(new TrustAnchor(rootCert, null)); + + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(rootCert); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(anchors, selector); + params.setRevocationEnabled(false); + + /* Set custom date within validity (March 2015) */ + params.setDate(new Date(1426399200000L)); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + assertNotNull("CertPathBuilderResult should not be null", result); + PKIXCertPathBuilderResult pResult = + (PKIXCertPathBuilderResult) result; + + /* Trust anchor should be the root cert */ + assertEquals( + "Trust anchor should be the root cert", + rootCert, + pResult.getTrustAnchor().getTrustedCert()); + + /* Path should be empty (target is trust anchor) */ + assertEquals("Path should be empty when target is trust anchor", 0, + pResult.getCertPath().getCertificates().size()); + } + + /** + * Test that when the target cert is the trust anchor, the builder succeeds + * even with a custom date outside cert validity. Trust anchor certs are + * inherently trusted, so dates are not checked on the early-return path. + */ + @Test + public void testTargetIsTrustAnchorWithDateAfterExpiry() + throws Exception { + + X509Certificate rootCert = + loadCertFromPEM(EXPIRED_ROOT_PEM); + + /* Set up root cert as both trust anchor and target */ + Set anchors = new HashSet<>(); + anchors.add(new TrustAnchor(rootCert, null)); + + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(rootCert); + + PKIXBuilderParameters params = + new PKIXBuilderParameters(anchors, selector); + params.setRevocationEnabled(false); + + /* Set custom date AFTER expiry (March 2017) */ + params.setDate(new Date(1489561200000L)); + + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + CertPathBuilderResult result = cpb.build(params); + + /* Should succeed, trust anchor dates not checked */ + assertNotNull("CertPathBuilderResult should not be null", result); + PKIXCertPathBuilderResult pResult = + (PKIXCertPathBuilderResult) result; + assertEquals("Trust anchor should be the root cert", rootCert, + pResult.getTrustAnchor().getTrustedCert()); + assertEquals("Path should be empty when target is trust anchor", 0, + pResult.getCertPath().getCertificates().size()); + } } From 78a52701681fed4b71a453f488461977e84290dd Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Mon, 16 Feb 2026 17:18:08 -0700 Subject: [PATCH 5/7] JCE: use DATE_ERR_OKAY flag when loading expired trust anchors with custom validation date --- ...com_wolfssl_wolfcrypt_WolfSSLCertManager.h | 16 +++++ jni/jni_wolfssl_cert_manager.c | 34 ++++++++++ .../jce/WolfCryptPKIXCertPathValidator.java | 18 +++-- .../wolfssl/wolfcrypt/WolfSSLCertManager.java | 68 +++++++++++++++++++ .../WolfCryptPKIXCertPathBuilderTest.java | 10 +-- 5 files changed, 135 insertions(+), 11 deletions(-) diff --git a/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h b/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h index dbe9d211..c0dd5ea8 100644 --- a/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h +++ b/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h @@ -7,6 +7,14 @@ #ifdef __cplusplus extern "C" { #endif +/* + * Class: com_wolfssl_wolfcrypt_WolfSSLCertManager + * Method: getWOLFSSL_LOAD_FLAG_DATE_ERR_OKAY + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_getWOLFSSL_1LOAD_1FLAG_1DATE_1ERR_1OKAY + (JNIEnv *, jclass); + /* * Class: com_wolfssl_wolfcrypt_WolfSSLCertManager * Method: CertManagerNew @@ -39,6 +47,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCABuffer (JNIEnv *, jclass, jlong, jbyteArray, jlong, jint); +/* + * Class: com_wolfssl_wolfcrypt_WolfSSLCertManager + * Method: CertManagerLoadCABufferEx + * Signature: (J[BJII)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCABufferEx + (JNIEnv *, jclass, jlong, jbyteArray, jlong, jint, jint); + /* * Class: com_wolfssl_wolfcrypt_WolfSSLCertManager * Method: CertManagerUnloadCAs diff --git a/jni/jni_wolfssl_cert_manager.c b/jni/jni_wolfssl_cert_manager.c index 8d645321..424694fe 100644 --- a/jni/jni_wolfssl_cert_manager.c +++ b/jni/jni_wolfssl_cert_manager.c @@ -281,6 +281,15 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store) return (int)result; } +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_getWOLFSSL_1LOAD_1FLAG_1DATE_1ERR_1OKAY + (JNIEnv* env, jclass jcl) +{ + (void)env; + (void)jcl; + + return WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY; +} + JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerNew (JNIEnv* env, jclass jcl) { @@ -363,6 +372,31 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager return (jint)ret; } +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCABufferEx + (JNIEnv* env, jclass jcl, jlong cmPtr, jbyteArray in, jlong sz, jint format, jint flags) +{ + int ret = 0; + word32 buffSz = 0; + byte* buff = NULL; + WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr; + (void)jcl; + (void)sz; + + if (env == NULL || in == NULL) { + return BAD_FUNC_ARG; + } + + buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); + buffSz = (*env)->GetArrayLength(env, in); + + ret = wolfSSL_CertManagerLoadCABuffer_ex(cm, buff, buffSz, format, 0, + (word32)flags); + + (*env)->ReleaseByteArrayElements(env, in, (jbyte*)buff, JNI_ABORT); + + return (jint)ret; +} + JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerUnloadCAs (JNIEnv* env, jclass jcl, jlong cmPtr) { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java index e9cffdb2..621c1156 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java @@ -569,11 +569,15 @@ private void callCertPathCheckers(X509Certificate cert, * * @param params PKIXParameters from which to get TrustAnchor Set * @param cm WolfSSLCertManager to load TrustAnchors into as trusted roots + * @param validationDate custom validation date, or null to use current + * time. When non-null, + * WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY is used to allow + * loading expired/not-yet-valid CAs * * @throws CertPathValidatorException on failure to load trust anchors */ - private void loadTrustAnchorsIntoCertManager( - PKIXParameters params, WolfSSLCertManager cm) + private void loadTrustAnchorsIntoCertManager(PKIXParameters params, + WolfSSLCertManager cm, Date validationDate) throws CertPathValidatorException { Set trustAnchors = null; @@ -601,7 +605,12 @@ private void loadTrustAnchorsIntoCertManager( X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert != null) { try { - cm.CertManagerLoadCA(anchorCert); + if (validationDate != null) { + cm.CertManagerLoadCA(anchorCert, + WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY); + } else { + cm.CertManagerLoadCA(anchorCert); + } log("loaded TrustAnchor: " + anchorCert.getSubjectX500Principal().getName()); @@ -1118,7 +1127,8 @@ public CertPathValidatorResult engineValidate( /* Load trust anchors into CertManager from PKIXParameters. * This must happen before initializing cert path checkers since * OCSP validation requires trust anchors to verify responses. */ - loadTrustAnchorsIntoCertManager(pkixParams, cm); + loadTrustAnchorsIntoCertManager(pkixParams, cm, + pkixParams.getDate()); /* Initialize all PKIXCertPathCheckers before calling check(). * Store the returned list so we use the same checker instances diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java b/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java index 4310dca7..8f5bd9be 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java +++ b/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java @@ -55,14 +55,22 @@ public class WolfSSLCertManager { /* lock around native WOLFSSL_CERT_MANAGER pointer use */ private final Object cmLock = new Object(); + /** Flag to allow loading certs with date errors */ + public static final int WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY = + getWOLFSSL_LOAD_FLAG_DATE_ERR_OKAY(); + /* Verification callback, null if not set */ private WolfSSLCertManagerVerifyCallback verifyCallback = null; + private static native int getWOLFSSL_LOAD_FLAG_DATE_ERR_OKAY(); + static native long CertManagerNew(); static native void CertManagerFree(long cm); static native int CertManagerLoadCA(long cm, String f, String d); static native int CertManagerLoadCABuffer( long cm, byte[] in, long sz, int format); + static native int CertManagerLoadCABufferEx( + long cm, byte[] in, long sz, int format, int flags); static native int CertManagerUnloadCAs(long cm); static native int CertManagerVerifyBuffer( long cm, byte[] in, long sz, int format); @@ -188,6 +196,66 @@ public synchronized void CertManagerLoadCA(X509Certificate cert) } } + /** + * Load CA into CertManager from byte array with extended flags. + * + * @param in byte array holding X.509 certificate to load + * @param sz size of input byte array, bytes + * @param format format of input certificate, either + * WolfCrypt.SSL_FILETYPE_PEM (PEM formatted) or + * WolfCrypt.SSL_FILETYPE_ASN1 (ASN.1/DER). + * @param flags load flags, e.g. + * WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY + * + * @throws IllegalStateException WolfSSLCertManager has been freed + * @throws WolfCryptException on native wolfSSL error + */ + public synchronized void CertManagerLoadCABufferEx(byte[] in, long sz, + int format, int flags) + throws IllegalStateException, WolfCryptException { + + int ret = 0; + + confirmObjectIsActive(); + + synchronized (cmLock) { + ret = CertManagerLoadCABufferEx(this.cmPtr, in, sz, format, flags); + if (ret != WolfCrypt.WOLFSSL_SUCCESS) { + throw new WolfCryptException(ret); + } + } + } + + /** + * Load CA into CertManager from X509Certificate object with extended flags. + * + * @param cert X509Certificate containing CA cert + * @param flags load flags, e.g. + * WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY + * + * @throws IllegalStateException WolfSSLCertManager has been freed + * @throws WolfCryptException on native wolfSSL error + */ + public synchronized void CertManagerLoadCA(X509Certificate cert, int flags) + throws IllegalStateException, WolfCryptException { + + confirmObjectIsActive(); + + if (cert == null) { + throw new WolfCryptException("Input X509Certificate is null"); + } + + synchronized (cmLock) { + try { + CertManagerLoadCABufferEx(cert.getEncoded(), + cert.getEncoded().length, WolfCrypt.SSL_FILETYPE_ASN1, + flags); + } catch (CertificateEncodingException e) { + throw new WolfCryptException(e); + } + } + } + /** * Loads KeyStore certificates into WolfSSLCertManager object. * diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java index f308b3f0..debe4304 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java @@ -4461,11 +4461,8 @@ public boolean match(Certificate cert) { return false; } public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } + /* Stateless selector, safe to return this */ + return this; } }; @@ -4596,8 +4593,7 @@ public void testFallbackBuilderDefersNotBeforeCheckToValidator() /** * Test end-to-end builder + validator with a valid custom date. Builds a * path with expired certs using a date within their validity, then - * validates the result. Should succeed on all wolfSSL versions (native or - * Java fallback). + * validates the result. */ @Test public void testBuildThenValidateWithValidCustomDate() From e3755a858d3a593a0d04528c748138d6066b3b8d Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 19 Feb 2026 12:29:20 -0700 Subject: [PATCH 6/7] JCE: add OCSP response status check before native verification via wolfSSL_d2i_OCSP_RESPONSE() --- ...com_wolfssl_wolfcrypt_WolfSSLCertManager.h | 8 ++ jni/jni_wolfssl_cert_manager.c | 108 +++++++++++++++++- .../jce/WolfCryptPKIXRevocationChecker.java | 50 +++++++- .../wolfssl/wolfcrypt/WolfSSLCertManager.java | 53 +++++++-- 4 files changed, 205 insertions(+), 14 deletions(-) diff --git a/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h b/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h index c0dd5ea8..bf3901d1 100644 --- a/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h +++ b/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h @@ -135,6 +135,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerCheckOCSPResponse (JNIEnv *, jclass, jlong, jbyteArray, jint, jbyteArray, jint); +/* + * Class: com_wolfssl_wolfcrypt_WolfSSLCertManager + * Method: OcspResponseStatus + * Signature: ([BI)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_OcspResponseStatus + (JNIEnv *, jclass, jbyteArray, jint); + /* * Class: com_wolfssl_wolfcrypt_WolfSSLCertManager * Method: CertManagerSetVerify diff --git a/jni/jni_wolfssl_cert_manager.c b/jni/jni_wolfssl_cert_manager.c index 424694fe..c14e33de 100644 --- a/jni/jni_wolfssl_cert_manager.c +++ b/jni/jni_wolfssl_cert_manager.c @@ -170,6 +170,32 @@ static void removeCallbackCtx(WOLFSSL_CERT_MANAGER* cm) } } +/* Extract cert DER bytes at given depth from WOLFSSL_X509_STORE_CTX into + * a new jbyteArray. Returns NULL if cert not available at depth. */ +static jbyteArray getCertDerAtDepth(JNIEnv* jenv, + WOLFSSL_X509_STORE_CTX* store, int depth) +{ + jbyteArray der = NULL; + WOLFSSL_BUFFER_INFO* certInfo = NULL; + + if (store->certs == NULL || depth < 0 || depth >= store->totalCerts) { + return NULL; + } + + certInfo = &store->certs[depth]; + if (certInfo->buffer == NULL || certInfo->length == 0) { + return NULL; + } + + der = (*jenv)->NewByteArray(jenv, (jsize)certInfo->length); + if (der != NULL) { + (*jenv)->SetByteArrayRegion(jenv, der, 0, (jsize)certInfo->length, + (const jbyte*)certInfo->buffer); + } + + return der; +} + /* Native verify callback that calls into Java verify callback. * * This is registered with wolfSSL_CertManagerSetVerify() and called @@ -185,6 +211,7 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store) jint result = 0; JNIEnv* jenv = NULL; VerifyCallbackCtx* ctx = NULL; + jbyteArray certDer = NULL; jclass callbackClass = NULL; jmethodID verifyMethod = NULL; @@ -236,9 +263,15 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store) error = store->error; errorDepth = store->error_depth; + /* If available, extract cert DER bytes from store context at errorDepth */ + certDer = getCertDerAtDepth(jenv, store, errorDepth); + /* Find verify() method on callback object */ callbackClass = (*jenv)->GetObjectClass(jenv, ctx->callback); if (callbackClass == NULL) { + if (certDer != NULL) { + (*jenv)->DeleteLocalRef(jenv, certDer); + } if (needsDetach) { (*ctx->jvm)->DetachCurrentThread(ctx->jvm); } @@ -246,8 +279,11 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store) } verifyMethod = (*jenv)->GetMethodID(jenv, callbackClass, - "verify", "(III)I"); + "verify", "(III[B)I"); if (verifyMethod == NULL) { + if (certDer != NULL) { + (*jenv)->DeleteLocalRef(jenv, certDer); + } (*jenv)->DeleteLocalRef(jenv, callbackClass); if (needsDetach) { (*ctx->jvm)->DetachCurrentThread(ctx->jvm); @@ -255,9 +291,9 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store) return 0; } - /* Call Java callback.verify(preverify, error, errorDepth) */ + /* Call Java callback.verify(preverify, error, errorDepth, certDer) */ result = (*jenv)->CallIntMethod(jenv, ctx->callback, verifyMethod, - (jint)preverify, (jint)error, (jint)errorDepth); + (jint)preverify, (jint)error, (jint)errorDepth, certDer); /* Check for Java exceptions, reject on exception */ if ((*jenv)->ExceptionCheck(jenv)) { @@ -273,6 +309,9 @@ static int nativeVerifyCallback(int preverify, WOLFSSL_X509_STORE_CTX* store) store->error = 0; } + if (certDer != NULL) { + (*jenv)->DeleteLocalRef(jenv, certDer); + } (*jenv)->DeleteLocalRef(jenv, callbackClass); if (needsDetach) { (*ctx->jvm)->DetachCurrentThread(ctx->jvm); @@ -749,6 +788,69 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager #endif } +/* Get OCSPResponseStatus from raw OCSP response bytes. + * + * Uses wolfSSL_d2i_OCSP_RESPONSE() to parse response and + * wolfSSL_OCSP_response_status() to extract the status. + * + * RFC 6960: + * OCSPResponse ::= SEQUENCE { + * responseStatus OCSPResponseStatus, + * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + * OCSPResponseStatus ::= ENUMERATED { + * successful (0), malformedRequest (1), internalError (2), + * tryLater (3), sigRequired (5), unauthorized (6) } + * + * Returns OCSPResponseStatus ENUMERATED value (0-6) on success, or negative + * error code on failure (BAD_FUNC_ARG, MEMORY_E, NOT_COMPILED_IN, or -1 on + * parse failure). + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_OcspResponseStatus + (JNIEnv* env, jclass jcl, jbyteArray response, jint responseSz) +{ +#if defined(HAVE_OCSP) && defined(OPENSSL_EXTRA) + int ret = -1; + jint arrLen = 0; + byte* respBuf = NULL; + const unsigned char* p = NULL; + OcspResponse* ocspResp = NULL; + (void)jcl; + (void)responseSz; + + if (env == NULL || response == NULL) { + return BAD_FUNC_ARG; + } + + arrLen = (*env)->GetArrayLength(env, response); + if (arrLen <= 0) { + return BAD_FUNC_ARG; + } + + respBuf = (byte*)(*env)->GetByteArrayElements(env, response, NULL); + if (respBuf == NULL) { + return MEMORY_E; + } + + p = (const unsigned char*)respBuf; + ocspResp = wolfSSL_d2i_OCSP_RESPONSE(NULL, &p, (int)arrLen); + if (ocspResp != NULL) { + ret = wolfSSL_OCSP_response_status(ocspResp); + wolfSSL_OCSP_RESPONSE_free(ocspResp); + } + + (*env)->ReleaseByteArrayElements(env, response, (jbyte*)respBuf, JNI_ABORT); + + return (jint)ret; + +#else + (void)env; + (void)jcl; + (void)response; + (void)responseSz; + return NOT_COMPILED_IN; +#endif /* HAVE_OCSP && OPENSSL_EXTRA */ +} + JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerSetVerify (JNIEnv* env, jclass jcl, jlong cmPtr, jobject callback) { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java index 96013260..8909c470 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java @@ -406,6 +406,36 @@ else if (certIndex >= 0 && trustAnchors != null) { } } + /** + * Get human readable name for OCSPResponseStatus value. + * + * Per RFC 6960, OCSPResponseStatus ::= ENUMERATED { + * successful (0), malformedRequest (1), internalError (2), + * tryLater (3), sigRequired (5), unauthorized (6) } + * + * @param status OCSPResponseStatus value + * @return status name string + */ + private String getOcspResponseStatusName(int status) { + + switch (status) { + case 0: + return "SUCCESSFUL"; + case 1: + return "MALFORMED_REQUEST"; + case 2: + return "INTERNAL_ERROR"; + case 3: + return "TRY_LATER"; + case 5: + return "SIG_REQUIRED"; + case 6: + return "UNAUTHORIZED"; + default: + return "UNKNOWN(" + status + ")"; + } + } + /** * Check pre-loaded OCSP response. * @@ -420,6 +450,7 @@ else if (certIndex >= 0 && trustAnchors != null) { private void checkPreloadedOcspResponse(X509Certificate cert) throws CertPathValidatorException { + int ocspStatus; byte[] response; byte[] certDer; @@ -435,14 +466,29 @@ private void checkPreloadedOcspResponse(X509Certificate cert) "CertManager not available for OCSP response checking"); } + /* Check OCSP response status before native verification. + * Non-successful OCSP responses (e.g. UNAUTHORIZED, TRY_LATER) should + * be reported as errors per RFC 6960. Use native wolfSSL to parse the + * response status from raw DER bytes. */ + ocspStatus = WolfSSLCertManager.getOcspResponseStatus(response, + response.length); + if (ocspStatus > 0) { + throw new CertPathValidatorException("OCSP response error: " + + getOcspResponseStatusName(ocspStatus)); + } + else if (ocspStatus < 0) { + throw new CertPathValidatorException( + "Failed to parse OCSP response status: " + ocspStatus); + } + /* Load issuer cert so OCSP response signature can be verified */ loadIssuerForOcspVerification(cert); try { certDer = cert.getEncoded(); - certManager.CertManagerCheckOCSPResponse( - response, response.length, certDer, certDer.length); + certManager.CertManagerCheckOCSPResponse(response, response.length, + certDer, certDer.length); } catch (CertificateEncodingException e) { throw new CertPathValidatorException( diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java b/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java index 8f5bd9be..5bd4dcc9 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java +++ b/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManager.java @@ -84,6 +84,7 @@ static native int CertManagerLoadCRLBuffer( static native int CertManagerCheckOCSP(long cm, byte[] cert, int sz); static native int CertManagerCheckOCSPResponse( long cm, byte[] response, int responseSz, byte[] cert, int certSz); + static native int OcspResponseStatus(byte[] response, int responseSz); static native int CertManagerSetVerify(long cm, Object callback); static native int CertManagerClearVerify(long cm); @@ -188,8 +189,9 @@ public synchronized void CertManagerLoadCA(X509Certificate cert) synchronized (cmLock) { try { /* Throws WolfCryptException on native error */ - CertManagerLoadCABuffer(cert.getEncoded(), - cert.getEncoded().length, WolfCrypt.SSL_FILETYPE_ASN1); + byte[] encoded = cert.getEncoded(); + CertManagerLoadCABuffer(encoded, encoded.length, + WolfCrypt.SSL_FILETYPE_ASN1); } catch (CertificateEncodingException e) { throw new WolfCryptException(e); } @@ -247,9 +249,9 @@ public synchronized void CertManagerLoadCA(X509Certificate cert, int flags) synchronized (cmLock) { try { - CertManagerLoadCABufferEx(cert.getEncoded(), - cert.getEncoded().length, WolfCrypt.SSL_FILETYPE_ASN1, - flags); + byte[] encoded = cert.getEncoded(); + CertManagerLoadCABufferEx(encoded, encoded.length, + WolfCrypt.SSL_FILETYPE_ASN1, flags); } catch (CertificateEncodingException e) { throw new WolfCryptException(e); } @@ -292,8 +294,8 @@ public synchronized void CertManagerLoadCAKeyStore(KeyStore ks) if (cert != null && cert.getBasicConstraints() >= 0) { /* Will throw WolfCryptException on error */ - CertManagerLoadCABuffer(cert.getEncoded(), - cert.getEncoded().length, + byte[] encoded = cert.getEncoded(); + CertManagerLoadCABuffer(encoded, encoded.length, WolfCrypt.SSL_FILETYPE_ASN1); loadedCerts++; } @@ -383,8 +385,9 @@ public synchronized void CertManagerVerify( synchronized (cmLock) { try { /* Throws WolfCryptException on native error */ - CertManagerVerifyBuffer(cert.getEncoded(), - cert.getEncoded().length, WolfCrypt.SSL_FILETYPE_ASN1); + byte[] encoded = cert.getEncoded(); + CertManagerVerifyBuffer(encoded, encoded.length, + WolfCrypt.SSL_FILETYPE_ASN1); } catch (CertificateEncodingException e) { throw new WolfCryptException(e); } @@ -691,6 +694,38 @@ public synchronized void CertManagerCheckOCSPResponse( } } + /** + * Get OCSPResponseStatus from raw OCSP response bytes. + * + * Uses native wolfSSL_d2i_OCSP_RESPONSE() to parse the response and + * wolfSSL_OCSP_response_status() to extract the status value per RFC 6960. + * + * This is a static method that does not require a WolfSSLCertManager + * instance. + * + * @param response raw OCSP response bytes (DER encoded) + * @param responseSz size of OCSP response + * + * @return OCSPResponseStatus value: + * 0 = successful, + * 1 = malformedRequest, + * 2 = internalError, + * 3 = tryLater, + * 5 = sigRequired, + * 6 = unauthorized, + * negative on error (NOT_COMPILED_IN, BAD_FUNC_ARG, + * MEMORY_E, or -1 if response parsing failed) + */ + public static int getOcspResponseStatus(byte[] response, int responseSz) { + + if (response == null || responseSz < 0 || + responseSz > response.length) { + return WolfCryptError.BAD_FUNC_ARG.getCode(); + } + + return OcspResponseStatus(response, responseSz); + } + /** * Set verification callback for this CertManager. * From 790f6acbd221eacfde1dafb9ca9eee2332f804f3 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 19 Feb 2026 12:29:43 -0700 Subject: [PATCH 7/7] JCE: pass cert DER from native verify callback and use DATE_ERR_OKAY for all CA loading --- .../jce/WolfCryptPKIXCertPathValidator.java | 334 ++++++++++-------- .../WolfSSLCertManagerVerifyCallback.java | 24 ++ 2 files changed, 203 insertions(+), 155 deletions(-) diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java index 621c1156..1a7a977c 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java @@ -26,8 +26,6 @@ import java.util.Set; import java.util.Collection; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; import java.util.Date; import java.security.InvalidAlgorithmParameterException; import java.security.PublicKey; @@ -60,6 +58,9 @@ import com.wolfssl.wolfcrypt.WolfSSLCertManager; import com.wolfssl.wolfcrypt.WolfSSLCertManagerVerifyCallback; import com.wolfssl.wolfcrypt.WolfCryptException; +import java.io.ByteArrayInputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateException; /** * wolfJCE implementation of CertPathValidator for PKIX (X.509) @@ -83,121 +84,144 @@ public class WolfCryptPKIXCertPathValidator extends CertPathValidatorSpi { * Inner class implementing verification callback for date override. * * This callback is registered with WolfSSLCertManager when - * PKIXParameters.getDate() returns a non-null override date. It - * intercepts certificate date validation errors and re-validates - * the certificate dates against the override date instead of the - * current system time. + * PKIXParameters.getDate() returns a non-null override date. It intercepts + * certificate date validation errors and re-validates the certificate dates + * against the override date instead of the current system time. * * For thread safety, each CertPathValidator creates its own callback - * instance with its own certificate map and override date. + * instance with its own override date. */ private class DateOverrideVerifyCallback implements WolfSSLCertManagerVerifyCallback { private final Date overrideDate; - private final Map certsByDepth; + private final CertificateFactory certFactory; + + /* X509 error codes wolfSSL maps date errors to when + * OPENSSL_COMPATIBLE_DEFAULTS is defined. Values from wolfssl/ssl.h, + * WOLFSSL_X509_V_ERR enum. */ + private static final int X509_V_ERR_CERT_NOT_YET_VALID = 9; + private static final int X509_V_ERR_CERT_HAS_EXPIRED = 10; /** * Create new DateOverrideVerifyCallback. * * @param date Override date to use for validation - * @param certs List of certificates in chain, ordered from - * end-entity (index 0) to root + * + * @throws CertificateException if X.509 CertificateFactory + * is not available */ - public DateOverrideVerifyCallback( - Date date, List certs) { + public DateOverrideVerifyCallback(Date date) + throws CertificateException { this.overrideDate = date; - this.certsByDepth = new HashMap<>(); + this.certFactory = CertificateFactory.getInstance("X.509"); + } - /* Map certificates by depth for lookup in callback. - * Depth 0 = end entity cert, increasing depth toward root. */ - for (int i = 0; i < certs.size(); i++) { - certsByDepth.put(i, certs.get(i)); - } + /** + * Delegates to 4-arg verify with null certDer. + * + * @param preverify 1 if pre-verification passed, 0 if failed + * @param error Error code from verification (0 = no error) + * @param errorDepth Certificate depth in chain (0 = end entity) + * + * @return 1 to accept certificate, 0 to reject + */ + @Override + public int verify(int preverify, int error, int errorDepth) { + return verify(preverify, error, errorDepth, null); } /** - * Verify callback implementation that overrides date validation. + * Verify callback that enforces override date validation. * - * When a date validation error occurs (ASN_BEFORE_DATE_E or - * ASN_AFTER_DATE_E), this checks if the override date falls - * within the certificate's validity period. If so, the error is - * overridden and verification continues. For all other errors, - * the original preverify result is used. + * This callback is called from native JNI with the DER-encoded + * certificate bytes from the WOLFSSL_X509_STORE_CTX. * - * This callback is called from native JNI. + * Validates the certificate against the override date. For date errors + * where the override date is valid, the error is overridden. Non date + * errors defer to preverify. + * + * Parses the DER-encoded certificate bytes provided by the native + * callback to obtain the certificate validity dates. + * + * Date errors are checked as both raw ASN error codes + * (ASN_BEFORE_DATE_E, ASN_AFTER_DATE_E) and X509 codes + * (X509_V_ERR_CERT_NOT_YET_VALID, X509_V_ERR_CERT_HAS_EXPIRED) + * since wolfSSL maps ASN codes to X509 codes when + * OPENSSL_COMPATIBLE_DEFAULTS is defined. * * @param preverify 1 if pre-verification passed, 0 if failed * @param error Error code from verification (0 = no error) * @param errorDepth Certificate depth in chain (0 = end entity) + * @param certDer DER-encoded certificate at errorDepth, or + * null if not available * * @return 1 to accept certificate, 0 to reject */ @Override - public int verify(int preverify, int error, int errorDepth) { + public int verify(int preverify, int error, int errorDepth, + byte[] certDer) { Date notBefore; Date notAfter; - X509Certificate cert; + X509Certificate cert = null; - /* Get the certificate at this depth */ - cert = certsByDepth.get(errorDepth); + /* Parse cert from DER bytes provided by native callback */ + if (certDer != null) { + try { + cert = (X509Certificate)certFactory.generateCertificate( + new ByteArrayInputStream(certDer)); + } catch (CertificateException e) { + log("Date override: failed to parse cert DER at depth " + + errorDepth + ": " + e.getMessage()); + } + } + + /* If no cert available, defer to native result */ if (cert == null) { - /* Reject if cert not found */ - log("Date override: cert not found at depth " + errorDepth + - ", rejecting"); - return 0; + log("Date override: no cert available at depth " + errorDepth + + ", returning preverify: " + preverify); + return preverify; } - /* Get certificate validity dates */ + /* Get cert validity dates */ notBefore = cert.getNotBefore(); notAfter = cert.getNotAfter(); - /* When date override is active, always validate against the - * override date, not system time. */ - - /* If date error exists but override date is valid, accept */ - if (error == WolfCryptError.ASN_BEFORE_DATE_E.getCode() || - error == WolfCryptError.ASN_AFTER_DATE_E.getCode()) { - - /* Override date must be within cert validity */ - if (overrideDate.before(notBefore) || - overrideDate.after(notAfter)) { - log("Date override: override date " + overrideDate + - " outside validity window (notBefore: " + notBefore + - ", notAfter: " + notAfter + "), rejecting"); - return 0; - } - + /* Always check override date against cert validity. Override date + * may be outside cert validity window. */ + if (overrideDate.before(notBefore) || + overrideDate.after(notAfter)) { log("Date override: override date " + overrideDate + - " within validity, accepting despite date error"); - - return 1; + " outside validity window (notBefore: " + notBefore + + ", notAfter: " + notAfter + "), rejecting"); + return 0; } - /* No date error, but still need to validate override date. - * If override date is outside cert validity, reject even though - * current system time might be valid. */ - if (overrideDate.before(notBefore)) { - log("Date override: override date " + overrideDate + - " before cert validity (notBefore: " + notBefore + - "), rejecting"); - return 0; + /* Override date is within cert validity window. Override native + * date error if present. For non-date errors, defer to preverify. + * + * Check both raw ASN codes and X509 mapped codes since wolfSSL + * maps ASN to X509 error codes with OPENSSL_COMPATIBLE_DEFAULTS. */ + if (error == 0 || preverify == 1) { + return preverify; } - if (overrideDate.after(notAfter)) { + if (error == WolfCryptError.ASN_BEFORE_DATE_E.getCode() || + error == WolfCryptError.ASN_AFTER_DATE_E.getCode() || + error == X509_V_ERR_CERT_NOT_YET_VALID || + error == X509_V_ERR_CERT_HAS_EXPIRED) { log("Date override: override date " + overrideDate + - " after cert validity (notAfter: " + notAfter + - "), rejecting"); - return 0; + " within validity, accepting despite date error " + error); + return 1; } - /* Override date is valid, accept */ - log("Date override: override date " + overrideDate + - " within validity, accepting"); + /* Non-date error, defer to preverify */ + log("Date override: non-date error " + error + " at depth " + + errorDepth + ", returning preverify: " + preverify); - return 1; + return preverify; } } @@ -211,7 +235,7 @@ public WolfCryptPKIXCertPathValidator() { /** * Check CertPathParameters matches our requirements. * 1. Not null - * 2. Is an instance of PKIXParameters + * 2. Instance of PKIXParameters * * @throws InvalidAlgorithmParameterException if null or not an instance * of PKIXParameters @@ -350,8 +374,8 @@ private void disallowCertPolicyUse(PKIXParameters params) } /** - * Check certificate against disabled algorithms constraints from - * security property jdk.certpath.disabledAlgorithms. + * Check certificate against disabled algorithms constraints from security + * property jdk.certpath.disabledAlgorithms. * * Validates both the signature algorithm and public key algorithm/size * against the disabled algorithms list. @@ -378,12 +402,10 @@ private void checkAlgorithmConstraints(X509Certificate cert, /* Check signature algorithm against disabled list */ sigAlg = cert.getSigAlgName(); if (WolfCryptUtil.isAlgorithmDisabled(sigAlg, propertyName)) { - log("Algorithm constraints check failed on signature " + - "algorithm: " + sigAlg); + log("Algorithm constraints check failed on sig algo: " + sigAlg); throw new CertPathValidatorException( - "Algorithm constraints check failed on signature " + - "algorithm: " + sigAlg, null, path, certIdx, - BasicReason.ALGORITHM_CONSTRAINED); + "Algorithm constraints check failed on sig algo: " + sigAlg, + null, path, certIdx, BasicReason.ALGORITHM_CONSTRAINED); } /* Check public key algorithm and size against constraints */ @@ -470,9 +492,8 @@ private void sanitizeX509Certificate(X509Certificate cert, /* Check target cert constraints, if set in parameters */ checkTargetCertConstraints(cert, certIdx, path, params); - /* Certificate policies are not currently supported by this - * CertPathValidator implementation, throw exceptions when - * user tries to use them. */ + /* Certificate policies are not currently supported by this validator, + * throw exceptions when user tries to use them. */ disallowCertPolicyUse(params); } @@ -567,17 +588,16 @@ private void callCertPathCheckers(X509Certificate cert, * Load TrustAnchors from PKIXParameters into WolfSSLCertManager as * trusted CA certificates. * + * Trust anchors are always loaded with WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY + * since per RFC 5280 trust anchors should not be date-validated. + * * @param params PKIXParameters from which to get TrustAnchor Set * @param cm WolfSSLCertManager to load TrustAnchors into as trusted roots - * @param validationDate custom validation date, or null to use current - * time. When non-null, - * WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY is used to allow - * loading expired/not-yet-valid CAs * * @throws CertPathValidatorException on failure to load trust anchors */ private void loadTrustAnchorsIntoCertManager(PKIXParameters params, - WolfSSLCertManager cm, Date validationDate) + WolfSSLCertManager cm) throws CertPathValidatorException { Set trustAnchors = null; @@ -605,18 +625,21 @@ private void loadTrustAnchorsIntoCertManager(PKIXParameters params, X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert != null) { try { - if (validationDate != null) { - cm.CertManagerLoadCA(anchorCert, - WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY); - } else { - cm.CertManagerLoadCA(anchorCert); - } + cm.CertManagerLoadCA(anchorCert, + WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY); log("loaded TrustAnchor: " + anchorCert.getSubjectX500Principal().getName()); } catch (WolfCryptException e) { - throw new CertPathValidatorException(e); + /* In wolfSSL <= 5.8.4 with WOLFSSL_TRUST_PEER_CERT and + * OPENSSL_COMPATIBLE_DEFAULTS, a date error from trusted + * peer loading may overwrite the successful CA load return + * code. The CA is still loaded into the CertManager despite + * the error return, so we just log and continue here. */ + log("TrustAnchor load returned error for " + + anchorCert.getSubjectX500Principal().getName() + + ", CA may still be loaded: " + e.getMessage()); } } } @@ -660,18 +683,21 @@ private void verifyCertChain(CertPath path, PKIXParameters params, "Failed verification on certificate", e, path, i); } - /* Verified successfully. If this is a CA and we have more - * certs, load this as trusted (intermediate) */ + /* Verified successfully. If this is a CA and we have more certs, + * load this as trusted (intermediate). Use DATE_ERR_OKAY since + * this cert has already been date-validated above. */ if (i > 0 && cert.getBasicConstraints() >= 0) { try { - cm.CertManagerLoadCA(cert); + cm.CertManagerLoadCA(cert, + WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY); log("chain [" + i + "] is intermediate, loading as root"); } catch (WolfCryptException e) { - - log("chain [" + i + "] is CA, but failed to load as " + - "trusted root, not loading"); + /* CA may still be loaded despite error from trusted peer + * loading on older wolfSSL */ + log("chain [" + i + "] intermediate load returned error, " + + "CA may still be loaded: " + e.getMessage()); } } } @@ -728,13 +754,10 @@ public TrustAnchor findTrustAnchor(PKIXParameters params, overrideDate = params.getDate(); if (overrideDate != null) { try { - /* Create simple single-cert callback for this verification */ - List singleCert = new ArrayList<>(); - singleCert.add(cert); DateOverrideVerifyCallback callback = - new DateOverrideVerifyCallback(overrideDate, singleCert); + new DateOverrideVerifyCallback(overrideDate); cm.setVerifyCallback(callback); - } catch (WolfCryptException e) { + } catch (WolfCryptException | CertificateException e) { cm.free(); throw new CertPathValidatorException( "Failed to set date override callback in findTrustAnchor"); @@ -766,11 +789,18 @@ public TrustAnchor findTrustAnchor(PKIXParameters params, } try { - /* Load anchor as CA */ - cm.CertManagerLoadCA(anchorCert); + /* Load anchor as CA, skip date validation per RFC 5280 (trust + * anchors not date-validated) */ + cm.CertManagerLoadCA(anchorCert, + WolfSSLCertManager.WOLFSSL_LOAD_FLAG_DATE_ERR_OKAY); } catch (WolfCryptException e) { - /* error loading CA, skip to next */ - continue; + /* In wolfSSL <= 5.8.4 with WOLFSSL_TRUST_PEER_CERT and + * OPENSSL_COMPATIBLE_DEFAULTS, a date error from trusted + * peer loading may overwrite the successful CA load return + * code. The CA is still loaded into the CertManager despite + * the error return, so we just log and continue here. */ + log("findTrustAnchor load error, CA may still be loaded: " + + e.getMessage()); } try { @@ -820,14 +850,14 @@ private void throwUndeterminedRevocationStatus(String message, * Check if revocation has been enabled in PKIXParameters, and if so * find and load any CRLs in params.getCertStores(). * - * When a PKIXRevocationChecker has been registered via - * addCertPathChecker(), that checker handles revocation checking. CRL - * checking in the native CertManager is only enabled if: + * 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 params parameters used to check if revocation is enabled - * and, if so load any CRLs available + * @param params parameters used to check if revocation is enabled and, + * if so load any CRLs available * @param cm WolfSSLCertManager to load CRLs into * @param certPath the CertPath being validated (for exception reporting) * @param certs list of certificates from certPath @@ -836,9 +866,8 @@ private void throwUndeterminedRevocationStatus(String message, * @throws CertPathValidatorException if error is encountered during * revocation checking or CRL loading */ - private void checkRevocationEnabledAndLoadCRLs( - PKIXParameters params, WolfSSLCertManager cm, - CertPath certPath, List certs, + private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params, + WolfSSLCertManager cm, CertPath certPath, List certs, List pathCheckers) throws CertPathValidatorException { @@ -866,9 +895,8 @@ private void checkRevocationEnabledAndLoadCRLs( (WolfCryptPKIXRevocationChecker)checker; Set options = revChecker.getOptions(); - if (options != null && - options.contains( - PKIXRevocationChecker.Option.PREFER_CRLS)) { + if (options != null && options.contains( + PKIXRevocationChecker.Option.PREFER_CRLS)) { preferCrls = true; } break; @@ -883,8 +911,8 @@ private void checkRevocationEnabledAndLoadCRLs( } if (params.isRevocationEnabled()) { - log("revocation enabled in PKIXParameters, checking " + - "for CRLs to load"); + log("revocation enabled in PKIXParameters, checking for CRLs " + + "to load"); if (!WolfCrypt.CrlEnabled()) { throw new CertPathValidatorException( @@ -928,8 +956,8 @@ private void checkRevocationEnabledAndLoadCRLs( certCount++; } catch (WolfCryptException e) { /* Log but not hard fail */ - log("Warning: failed to load cert from " + - "CertStore: " + e.getMessage()); + log("Failed to load cert from CertStore: " + + e.getMessage()); } } } @@ -1094,10 +1122,9 @@ public CertPathValidatorResult engineValidate( } try { - /* Get List of Certificate objects in CertPath, sanity check - * that they are X509Certificate instances. This needs to be - * done before date override callback registration since - * callback needs access to certificate list. */ + /* Get List of Certificate objects in CertPath, sanity check that + * they are X509Certificate instances. Done before date override + * callback registration since callback needs access to cert list */ certs = new ArrayList<>(); for (Certificate cert : certPath.getCertificates()) { if (cert instanceof X509Certificate) { @@ -1106,34 +1133,32 @@ public CertPathValidatorResult engineValidate( } /* Register verify callback to override date validation if - * PKIXParameters specifies an override date */ + * PKIXParameters specifies an override date. Callback checks certs + * against the override date. */ if (pkixParams.getDate() != null) { try { - DateOverrideVerifyCallback callback = - new DateOverrideVerifyCallback( - pkixParams.getDate(), certs); - cm.setVerifyCallback(callback); + cm.setVerifyCallback(new DateOverrideVerifyCallback( + pkixParams.getDate())); - log("Registered date override callback for " + - "validation date: " + pkixParams.getDate()); + log("Registered date override callback for validation " + + "date: " + pkixParams.getDate()); - } catch (WolfCryptException e) { + } catch (WolfCryptException | CertificateException e) { throw new CertPathValidatorException( "Failed to register date override callback: " + e.getMessage()); } } - /* Load trust anchors into CertManager from PKIXParameters. - * This must happen before initializing cert path checkers since - * OCSP validation requires trust anchors to verify responses. */ - loadTrustAnchorsIntoCertManager(pkixParams, cm, - pkixParams.getDate()); + /* Load trust anchors into CertManager from PKIXParameters. Done + * before initializing cert path checkers since OCSP validation + * requires trust anchors to verify responses. */ + loadTrustAnchorsIntoCertManager(pkixParams, cm); /* Initialize all PKIXCertPathCheckers before calling check(). - * Store the returned list so we use the same checker instances - * for both init() and check() calls. Pass certs so revocation - * checker can find issuers for OCSP response verification. */ + * Store returned list to use same checker instances for init() and + * check(). Pass certs so revocation checker can find issuers + * for OCSP response verification. */ pathCheckers = initCertPathCheckers(pkixParams, cm, certs); /* Sanity checks on certs from PKIXParameters constraints */ @@ -1142,12 +1167,12 @@ public CertPathValidatorResult engineValidate( callCertPathCheckers(certs.get(i), pathCheckers); } - /* Enable CRL if PKIXParameters.isRevocationEnabled(), load - * any CRLs found in PKIXParameters.getCertStores(). Needs to - * happen after trust anchors are loaded, since native wolfSSL - * will try to find/verify CRL against trusted roots on load. - * Pass pathCheckers so we can skip CRL setup when a - * PKIXRevocationChecker is handling revocation via OCSP. */ + /* Enable CRL if PKIXParameters.isRevocationEnabled(), load any + * CRLs found in PKIXParameters.getCertStores(). Done after trust + * anchors loaded, since native wolfSSL will try to find/verify CRL + * against trusted roots on load. Pass pathCheckers so we can skip + * CRL setup when a PKIXRevocationChecker is handling revocation + * via OCSP. */ checkRevocationEnabledAndLoadCRLs(pkixParams, cm, certPath, certs, pathCheckers); @@ -1156,8 +1181,8 @@ public CertPathValidatorResult engineValidate( /* Cert chain has been verified, find TrustAnchor to return * in PKIXCertPathValidatorResult */ - trustAnchor = findTrustAnchor( - pkixParams, certs.get(certs.size() - 1)); + trustAnchor = findTrustAnchor(pkixParams, + certs.get(certs.size() - 1)); /* Check trust anchor public key constraints */ if (trustAnchor != null) { @@ -1169,8 +1194,7 @@ public CertPathValidatorResult engineValidate( cm.free(); } - /* PolicyNode not returned, since certificate policies are not - * yet supported */ + /* PolicyNode not returned, since certificate policies not supported */ return new PKIXCertPathValidatorResult(trustAnchor, null, certs.get(0).getPublicKey()); } @@ -1185,9 +1209,9 @@ public CertPathValidatorResult engineValidate( * various checking options before being passed to the validate() method * via PKIXParameters.addCertPathChecker(). * - * Note: The CertManager will be provided to the checker during - * certificate path validation when the checker's init() method is - * called with the appropriate parameters. + * Note: The CertManager will be provided to the checker during certificate + * path validation when the checker's init() method is called with the + * appropriate parameters. * * @return a CertPathChecker object that this implementation uses to * check the revocation status of certificates. diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManagerVerifyCallback.java b/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManagerVerifyCallback.java index 11435dbe..4f219c10 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManagerVerifyCallback.java +++ b/src/main/java/com/wolfssl/wolfcrypt/WolfSSLCertManagerVerifyCallback.java @@ -59,5 +59,29 @@ public interface WolfSSLCertManagerVerifyCallback { * @return 1 to accept the certificate, 0 to reject */ int verify(int preverify, int error, int errorDepth); + + /** + * Verify callback with certificate DER bytes from store context. + * + * This overload passes the DER-encoded certificate being verified, + * extracted from the native WOLFSSL_X509_STORE_CTX. Implementations + * can override this method to inspect the actual certificate when it + * is not available through other means (e.g. trust anchors not in a + * pre-built map). + * + * The default implementation delegates to the 3-arg verify() method, + * so existing implementations are not broken. + * + * @param preverify 1 if pre-verification passed, 0 if failed + * @param error Error code from verification (0 indicates no error) + * @param errorDepth Certificate depth in chain (0 = end entity cert) + * @param certDer DER-encoded certificate at errorDepth, or null + * if not available from the store context + * @return 1 to accept the certificate, 0 to reject + */ + default int verify(int preverify, int error, int errorDepth, + byte[] certDer) { + return verify(preverify, error, errorDepth); + } }