diff --git a/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h b/jni/include/com_wolfssl_wolfcrypt_WolfSSLCertManager.h index dbe9d211..bf3901d1 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 @@ -119,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 8d645321..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); @@ -281,6 +320,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 +411,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) { @@ -715,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/WolfCryptPKIXCertPathBuilder.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java index 17d317f0..7f1cf9c8 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathBuilder.java @@ -113,13 +113,188 @@ 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"); + } + } + + /** + * 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. * - * 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 * @@ -136,8 +311,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(); @@ -149,11 +323,13 @@ 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; - 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(); @@ -164,32 +340,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"); } /** @@ -545,7 +731,8 @@ 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 or use disabled + * algorithms per jdk.certpath.disabledAlgorithms. * * @param certStores list of CertStores to search * @param anchors set of trust anchors to exclude @@ -572,7 +759,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 +769,16 @@ 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; } + + /* Convert to DER and add to list */ + intermediatesDer.add(x509.getEncoded()); + log("collected intermediate: " + + x509.getSubjectX500Principal().getName()); } } @@ -635,34 +828,19 @@ private NativeChainResult buildAndVerifyPathNative( 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. - * - * 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 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) { - if (!WolfSSLX509StoreCtx.isStoreCheckTimeSupported()) { - throw new CertPathBuilderException( - "PKIXBuilderParameters.setDate() requires " + - "a wolfSSL version that supports X509_STORE " + - "check_time propagation (> 5.8.4)"); - } 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 */ for (TrustAnchor anchor : anchors) { - X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert != null) { byte[] anchorDer; @@ -680,9 +858,10 @@ private NativeChainResult buildAndVerifyPathNative( } } - /* Collect all intermediate certificates from CertStores */ - List intermediatesDer = - collectIntermediateCertificates(certStores, anchors); + /* Collect all intermediate certificates from CertStores, + * filtering disabled algorithms */ + List intermediatesDer = collectIntermediateCertificates( + certStores, anchors); /* Convert target cert to DER */ byte[] targetDer; @@ -698,8 +877,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 +912,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 +930,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 +1109,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,11 +1127,34 @@ public CertPathBuilderResult engineBuild(CertPathParameters params) } } - /* Build and verify path using wolfSSL X509_STORE */ - NativeChainResult result = buildAndVerifyPathNative( - targetCert, pkixParams); - path = result.path; - trustAnchor = result.trustAnchor; + /* Check target cert algorithm constraints before building */ + checkAlgorithmConstraints(targetCert); + + 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); try { /* Convert path to CertPath object */ @@ -964,7 +1168,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..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. + * + * This callback is called from native JNI with the DER-encoded + * certificate bytes from the WOLFSSL_X509_STORE_CTX. * - * 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. + * 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. * - * This callback is called from native JNI. + * 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 */ @@ -397,6 +419,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. @@ -423,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); } @@ -520,13 +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 * * @throws CertPathValidatorException on failure to load trust anchors */ - private void loadTrustAnchorsIntoCertManager( - PKIXParameters params, WolfSSLCertManager cm) + private void loadTrustAnchorsIntoCertManager(PKIXParameters params, + WolfSSLCertManager cm) throws CertPathValidatorException { Set trustAnchors = null; @@ -554,13 +625,21 @@ private void loadTrustAnchorsIntoCertManager( X509Certificate anchorCert = anchor.getTrustedCert(); if (anchorCert != null) { try { - 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()); } } } @@ -604,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()); } } } @@ -672,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"); @@ -710,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 { @@ -764,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 @@ -780,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 { @@ -810,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; @@ -827,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( @@ -872,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()); } } } @@ -1022,6 +1106,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()); } @@ -1035,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) { @@ -1047,33 +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. */ + /* 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 */ @@ -1082,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); @@ -1096,16 +1181,20 @@ 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) { + checkTrustAnchorConstraints(trustAnchor); + } } finally { /* Free native WolfSSLCertManager resources */ 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()); } @@ -1120,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/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 4310dca7..5bd4dcc9 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); @@ -76,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); @@ -180,8 +189,69 @@ 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); + } + } + } + + /** + * 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 { + byte[] encoded = cert.getEncoded(); + CertManagerLoadCABufferEx(encoded, encoded.length, + WolfCrypt.SSL_FILETYPE_ASN1, flags); } catch (CertificateEncodingException e) { throw new WolfCryptException(e); } @@ -224,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++; } @@ -315,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); } @@ -623,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. * 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); + } } 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..debe4304 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathBuilderTest.java @@ -57,14 +57,20 @@ 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; import java.security.cert.X509CertSelector; +import java.security.cert.CertSelector; import java.security.cert.CertStore; 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; @@ -3514,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); @@ -3672,5 +3673,1089 @@ 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. + * + * Uses currently-valid certs and sets a custom date that is also within + * their validity period. + */ + @Test + public void testSetDateSucceedsWithValidDate() 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 custom date */ + 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. + * + * Uses currently-valid certs but sets a custom date far in the future + * which is after their expiry. The native builder rejects with the + * custom verification time. + */ + @Test + 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<>(); + + /* 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 custom verification + * time after cert expiry */ + CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX", provider); + + try { + cpb.build(params); + fail("Expected CertPathBuilderException for date after 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", ""); + } + } + } + + /** + * 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()); + } + + /** + * 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() { + /* Stateless selector, safe to return this */ + return this; + } + }; + + 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()); + } + } + + /** + * 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. + */ + @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()); + } } 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", ""); + } + } + } }