diff --git a/README_JCE.md b/README_JCE.md index 57cc709b..432d367c 100644 --- a/README_JCE.md +++ b/README_JCE.md @@ -888,6 +888,10 @@ revoked, validation will fail. The difference only affects behavior when one method succeeds and the other would have failed (e.g., OCSP unreachable but CRL available). +A `PKIXRevocationChecker` added with `addCertPathChecker()` applies +irregardless of if `setRevocationEnabled()` is set, so `PREFER_CRLS` with CRLs +in the `CertStore` list performs CRL checking even when revocation is disabled. + #### Indirect CRL Not Supported Native wolfSSL does not support indirect CRLs. An indirect CRL is a CRL signed diff --git a/jni/jni_aesgmac.c b/jni/jni_aesgmac.c index f5401828..0b8d9161 100644 --- a/jni/jni_aesgmac.c +++ b/jni/jni_aesgmac.c @@ -64,17 +64,19 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_AesGmac_native_1init( JNIEnv* env, jobject this) { #ifdef HAVE_AESGCM + int ret = 0; Gmac* gmac = (Gmac*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { /* getNativeStruct may throw exception, prevent throwing another */ return; } - /* GMAC struct is already zero-initialized in mallocNativeStruct_internal */ - /* Actual initialization happens in wc_GmacSetKey when we have the key */ + ret = wc_AesInit(&gmac->aes, NULL, INVALID_DEVID); + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); + } LogStr("native_init(gmac=%p)\n", gmac); - (void)gmac; /* suppress unused variable warning */ #else throwNotCompiledInException(env); #endif @@ -93,9 +95,9 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_AesGmac_native_1free( LogStr("free Gmac %p\n", gmac); if (gmac) { - /* Only clear the GMAC struct - do NOT free the memory here. - * The base class NativeStruct.xfree() will handle the actual - * memory deallocation to avoid double-free. */ + /* Free AES backend resources, then clear the struct. + * NativeStruct.xfree() frees Gmac struct. */ + wc_AesFree(&gmac->aes); XMEMSET(gmac, 0, sizeof(Gmac)); } #else @@ -242,8 +244,10 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_AesGmac_wc_1Gmac( authInSz = getByteArrayLength(env, authIn_object); authTagSz = getByteArrayLength(env, authTag_object); - /* Set the key */ - ret = wc_GmacSetKey(&gmac, key, keySz); + ret = wc_AesInit(&gmac.aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_GmacSetKey(&gmac, key, keySz); + } if (ret == 0) { /* Use a local buffer for the auth tag result to avoid @@ -319,8 +323,10 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_AesGmac_wc_1GmacVerify( authInSz = getByteArrayLength(env, authIn_object); authTagSz = getByteArrayLength(env, authTag_object); - /* Set the key */ - ret = wc_GmacSetKey(&gmac, key, keySz); + ret = wc_AesInit(&gmac.aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_GmacSetKey(&gmac, key, keySz); + } if (ret == 0) { /* Generate the expected tag and compare */ diff --git a/jni/jni_aesofb.c b/jni/jni_aesofb.c index a138a3f8..cccf429c 100644 --- a/jni/jni_aesofb.c +++ b/jni/jni_aesofb.c @@ -71,6 +71,7 @@ Java_com_wolfssl_wolfcrypt_AesOfb_native_1set_1key_1internal( byte* key = NULL; byte* iv = NULL; word32 keySz = 0; + (void)opmode; aes = (Aes*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -87,7 +88,7 @@ Java_com_wolfssl_wolfcrypt_AesOfb_native_1set_1key_1internal( } if (ret == 0) { - ret = wc_AesSetKey(aes, key, keySz, iv, opmode); + ret = wc_AesSetKey(aes, key, keySz, iv, AES_ENCRYPTION); } if (ret != 0) { diff --git a/jni/jni_dh.c b/jni/jni_dh.c index 621f4ba4..1953db5e 100644 --- a/jni/jni_dh.c +++ b/jni/jni_dh.c @@ -320,7 +320,6 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhGenerateKeyPair( LogStr("wc_DhGenerateKeyPair(key, rng, priv, privSz, pub, pubSz) = %d\n", ret); LogStr("private[%u]: [%p]\n", privSz, priv); - LogHex(priv, 0, privSz); LogStr("public[%u]: [%p]\n", pubSz, pub); LogHex(pub, 0, pubSz); @@ -455,7 +454,6 @@ Java_com_wolfssl_wolfcrypt_Dh_wc_1DhAgree( LogStr("wc_DhAgree(key, secret, secretSz, priv, privSz, pub, pubSz) = %d\n", ret); LogStr("secret[%u]: [%p]\n", secretSz, secret); - LogHex(secret, 0, secretSz); if (secret != NULL) { #if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \ diff --git a/jni/jni_ecc.c b/jni/jni_ecc.c index fe0a1a03..d0aa9179 100644 --- a/jni/jni_ecc.c +++ b/jni/jni_ecc.c @@ -164,6 +164,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1make_1key_1ex { #ifdef HAVE_ECC int ret = 0; + int curveId = 0; ecc_key* ecc = NULL; RNG* rng = NULL; const char* name = NULL; @@ -188,29 +189,26 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ecc_wc_1ecc_1make_1key_1ex if (ret == 0) { name = (*env)->GetStringUTFChars(env, curveName, 0); if (name == NULL) { - ret = BAD_FUNC_ARG; + /* GetStringUTFChars failed with an exception already pending */ + return; } - } - if (ret == 0) { - ret = wc_ecc_get_curve_id_from_name(name); + curveId = wc_ecc_get_curve_id_from_name(name); (*env)->ReleaseStringUTFChars(env, curveName, name); - } - - if (ret < 0) { - throwWolfCryptException(env, "ECC curve unsupported or not enabled"); - } else { - /* When using a specific curve_id, pass keysize as 0 to let the - * curve_id determine the key size. This is required for FIPS mode - * compatibility where keysize must be 0 when using approved curves. - * The 'size' parameter from Java is ignored here since curve_id - * (stored in ret) defines the actual key size. */ - ret = wc_ecc_make_key_ex(rng, 0, ecc, ret); - - if (ret < 0) { - throwWolfCryptExceptionFromError(env, ret); + if (curveId < 0) { + throwWolfCryptException(env, + "ECC curve unsupported or not enabled"); + return; } + + /* Pass keysize 0 so the curve_id sets the key size, required for + * FIPS where approved curves need keysize 0 */ + ret = wc_ecc_make_key_ex(rng, 0, ecc, curveId); + } + + if (ret != 0) { + throwWolfCryptExceptionFromError(env, ret); } LogStr("ecc_make_key_ex(rng, size, ecc=%p) = %d\n", ecc, ret); diff --git a/jni/jni_feature_detect.c b/jni/jni_feature_detect.c index ee0bfa1d..cc19bc1a 100644 --- a/jni/jni_feature_detect.c +++ b/jni/jni_feature_detect.c @@ -527,7 +527,11 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RsaPssLongSa { (void)env; (void)jcl; -#if !defined(NO_RSA) && defined(WC_RSA_PSS) && defined(WOLFSSL_PSS_LONG_SALT) +/* FIPS v7 and later cap the PSS salt at the digest length, even when + * WOLFSSL_PSS_LONG_SALT is defined */ +#if !defined(NO_RSA) && defined(WC_RSA_PSS) && \ + defined(WOLFSSL_PSS_LONG_SALT) && \ + !(defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 7)) return JNI_TRUE; #else return JNI_FALSE; diff --git a/jni/jni_rsa.c b/jni/jni_rsa.c index 75a92865..976ba99c 100644 --- a/jni/jni_rsa.c +++ b/jni/jni_rsa.c @@ -561,23 +561,6 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateKeyToP } } - /* Get PKCS#8 output size, into pkcs8Sz */ - if (ret == 0) { - ret = wc_CreatePKCS8Key(NULL, &pkcs8Sz, derKey, derKeySz, algoID, - curveOID, oidSz); - if (ret == LENGTH_ONLY_E) { - pkcs8 = (byte*)XMALLOC(pkcs8Sz, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (pkcs8 == NULL) { - ret = MEMORY_E; - } - else { - XMEMSET(pkcs8, 0, pkcs8Sz); - pkcs8BufSz = pkcs8Sz; - ret = 0; - } - } - } - if (ret == 0) { /* Allocate temp buffer to hold DER encoded key */ derKey = (byte*)XMALLOC(derKeySz, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -599,6 +582,23 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateKeyToP } } + /* Get PKCS#8 output size, into pkcs8Sz. */ + if (ret == 0) { + ret = wc_CreatePKCS8Key(NULL, &pkcs8Sz, derKey, derKeySz, algoID, + curveOID, oidSz); + if (ret == LENGTH_ONLY_E) { + pkcs8 = (byte*)XMALLOC(pkcs8Sz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (pkcs8 == NULL) { + ret = MEMORY_E; + } + else { + XMEMSET(pkcs8, 0, pkcs8Sz); + pkcs8BufSz = pkcs8Sz; + ret = 0; + } + } + } + /* Create PKCS#8 from DER key */ if (ret == 0) { ret = wc_CreatePKCS8Key(pkcs8, &pkcs8Sz, derKey, derKeySz, diff --git a/jni/jni_slhdsa.c b/jni/jni_slhdsa.c index 4160490c..438a2bab 100644 --- a/jni/jni_slhdsa.c +++ b/jni/jni_slhdsa.c @@ -50,6 +50,12 @@ /* #define WOLFCRYPT_JNI_DEBUG_ON */ #include +#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && !defined(WOLFSSL_NO_FORCE_ZERO) + #define SLHDSA_FORCE_ZERO(p, len) wc_ForceZero((p), (len)) +#else + #define SLHDSA_FORCE_ZERO(p, len) XMEMSET((p), 0, (len)) +#endif + /* A WOLFSSL_SLHDSA_VERIFY_ONLY build provides only public-key verify. DER * encode (KeyToDer / PublicKeyToDer) additionally needs * WC_ENABLE_ASYM_KEY_EXPORT. */ @@ -1258,7 +1264,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1ex LogStr("wc_SlhDsaKey_ExportPrivate(key=%p) = %d\n", key, ret); - wc_ForceZero(output, outputBufSz); + SLHDSA_FORCE_ZERO(output, outputBufSz); XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER); #else (void)env; @@ -1463,7 +1469,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1Ke LogStr("wc_SlhDsaKey_KeyToDer(key=%p) = %d\n", key, ret); - wc_ForceZero(output, outputBufSz); + SLHDSA_FORCE_ZERO(output, outputBufSz); XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER); #else (void)env; @@ -1558,7 +1564,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_SlhDsa_wc_1SlhDsaKey_1PrivateK LogStr("wc_SlhDsaKey_PrivateKeyDecode(key=%p) = %d\n", key, ret); if (derCopy != NULL) { - wc_ForceZero(derCopy, derLen); + SLHDSA_FORCE_ZERO(derCopy, derLen); XFREE(derCopy, NULL, DYNAMIC_TYPE_TMP_BUFFER); } releaseByteArray(env, der_object, der, JNI_ABORT); diff --git a/jni/jni_wolfssl_cert_manager.c b/jni/jni_wolfssl_cert_manager.c index 08674055..0cb9a972 100644 --- a/jni/jni_wolfssl_cert_manager.c +++ b/jni/jni_wolfssl_cert_manager.c @@ -523,6 +523,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); + if (buff == NULL) { + return MEMORY_E; + } buffSz = (word32)sz; ret = wolfSSL_CertManagerLoadCABuffer(cm, buff, buffSz, format); @@ -547,6 +550,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); + if (buff == NULL) { + return MEMORY_E; + } buffSz = (word32)sz; ret = wolfSSL_CertManagerLoadCABuffer_ex(cm, buff, buffSz, format, 0, @@ -588,6 +594,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); + if (buff == NULL) { + return MEMORY_E; + } buffSz = (word32)sz; ret = wolfSSL_CertManagerVerifyBuffer(cm, buff, buffSz, format); @@ -656,6 +665,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager } buff = (byte*)(*env)->GetByteArrayElements(env, in, NULL); + if (buff == NULL) { + return MEMORY_E; + } buffSz = (word32)sz; ret = wolfSSL_CertManagerLoadCRLBuffer(cm, buff, buffSz, type); diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java index 55a925a8..4ee8f212 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXCertPathValidator.java @@ -73,7 +73,8 @@ * validation will not return PolicyNode in CertPathValidatorResult * * Revocation checking is supported via: - * - CRL: If PKIXParameters.isRevocationEnabled() is true and appropriate + * - CRL: If PKIXParameters.isRevocationEnabled() is true, or a + * PKIXRevocationChecker with PREFER_CRLS is registered, and appropriate * CRLs have been loaded into CertStore Set * - OCSP: via getRevocationChecker() which returns a * WolfCryptPKIXRevocationChecker supporting OCSP and options @@ -839,25 +840,71 @@ private void throwUndeterminedRevocationStatus(String message, CertPath certPath, List certs) throws CertPathValidatorException { - /* Report index of last cert in path (closest to trust anchor) - * to match SunJCE behavior. */ - int failIndex = 0; + throw new CertPathValidatorException(message, null, certPath, + lastCertIndex(certs), BasicReason.UNDETERMINED_REVOCATION_STATUS); + } + + /** + * Index of cert closest to the trust anchor. + * + * @param certs certificate list from the CertPath + * + * @return index of the last cert, or 0 for a single-cert path + */ + private static int lastCertIndex(List certs) { + if (certs != null && certs.size() > 1) { - failIndex = certs.size() - 1; + return certs.size() - 1; } - throw new CertPathValidatorException(message, null, certPath, - failIndex, BasicReason.UNDETERMINED_REVOCATION_STATUS); + + return 0; } /** - * Check if revocation has been enabled in PKIXParameters, and if so - * find and load any CRLs in params.getCertStores(). + * Disable native CRL checking after a PREFER_CRLS checker found no CRL + * to load, so wolfSSL doesn't fail chain validation on a missing CRL. With + * NO_FALLBACK set, revocation is undetermined, which fails validation + * unless SOFT_FAIL is set. Without NO_FALLBACK, the OCSP result the + * checker already produced in check() propogates. * - * 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 revChecker the registered PREFER_CRLS checker + * @param noFallback true if the checker has NO_FALLBACK set + * @param cm WolfSSLCertManager with CRL checking enabled + * @param certPath the CertPath being validated, for exception reporting + * @param failIndex index of the cert to report + * + * @throws CertPathValidatorException if revocation is undetermined and + * SOFT_FAIL is not set, or native CRL checking cannot be disabled + */ + private void handleMissingCrl(WolfCryptPKIXRevocationChecker revChecker, + boolean noFallback, WolfSSLCertManager cm, CertPath certPath, + int failIndex) throws CertPathValidatorException { + + if (noFallback) { + revChecker.handleMissingCrlRevocation(certPath, failIndex); + } + else { + log("no CRL loaded, PREFER_CRLS checker falls back to OCSP"); + } + + try { + cm.CertManagerDisableCRL(); + } + catch (WolfCryptException e) { + throw new CertPathValidatorException("Failed to disable CRL " + + "checking in native WolfSSLCertManager", e); + } + } + + /** + * Check if CRL checking is wanted and, if so, find and load any CRLs in + * params.getCertStores(). + * + * CRL checking in the native CertManager is enabled when + * PKIXParameters.isRevocationEnabled() is true, or when a + * PKIXRevocationChecker with PREFER_CRLS is registered, which applies + * irregardless of the revocation flag. A registered checker without + * PREFER_CRLS handles revocation itself via OCSP. * * @param params parameters used to check if revocation is enabled and, * if so load any CRLs available @@ -877,10 +924,13 @@ private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params, int i = 0; int loadedCount = 0; int certCount = 0; + int failIndex = lastCertIndex(certs); List stores = null; Collection crls = null; boolean hasRevocationChecker = false; boolean preferCrls = false; + boolean noFallback = false; + WolfCryptPKIXRevocationChecker revChecker = null; if (params == null || cm == null) { throw new CertPathValidatorException( @@ -894,13 +944,14 @@ private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params, for (PKIXCertPathChecker checker : pathCheckers) { if (checker instanceof WolfCryptPKIXRevocationChecker) { hasRevocationChecker = true; - WolfCryptPKIXRevocationChecker revChecker = - (WolfCryptPKIXRevocationChecker)checker; + revChecker = (WolfCryptPKIXRevocationChecker)checker; Set options = revChecker.getOptions(); - if (options != null && options.contains( - PKIXRevocationChecker.Option.PREFER_CRLS)) { - preferCrls = true; + if (options != null) { + preferCrls = options.contains( + PKIXRevocationChecker.Option.PREFER_CRLS); + noFallback = options.contains( + PKIXRevocationChecker.Option.NO_FALLBACK); } break; } @@ -913,18 +964,24 @@ private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params, return; } - if (params.isRevocationEnabled()) { - log("revocation enabled in PKIXParameters, checking for CRLs " + - "to load"); + if (params.isRevocationEnabled() || preferCrls) { + log("revocation enabled or PREFER_CRLS checker registered, " + + "checking for CRLs to load"); if (!WolfCrypt.CrlEnabled()) { throw new CertPathValidatorException( - "Revocation enabled in PKIXParameters but native " + - "wolfCrypt CRL not compiled in"); + "CRL checking requested but native wolfCrypt CRL not " + + "compiled in"); } /* Enable CRL in native WolfSSLCertManager */ - cm.CertManagerEnableCRL(WolfCrypt.WOLFSSL_CRL_CHECK); + try { + cm.CertManagerEnableCRL(WolfCrypt.WOLFSSL_CRL_CHECK); + } + catch (WolfCryptException e) { + throw new CertPathValidatorException("Failed to enable CRL " + + "checking in native WolfSSLCertManager", e); + } log("CRL support enabled in native WolfSSLCertManager"); stores = params.getCertStores(); @@ -940,6 +997,10 @@ private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params, "and no PKIXRevocationChecker configured for OCSP", certPath, certs); } + else { + handleMissingCrl(revChecker, noFallback, cm, certPath, + failIndex); + } return; } @@ -990,22 +1051,32 @@ private void checkRevocationEnabledAndLoadCRLs(PKIXParameters params, } } catch (CertStoreException e) { throw new CertPathValidatorException(e); + } catch (WolfCryptException e) { + throw new CertPathValidatorException( + "Failed to load CRL into native WolfSSLCertManager", e); } log("loaded " + loadedCount + " CRLs into WolfSSLCertManager"); /* If no CRLs were loaded and no PKIXRevocationChecker is handling * OCSP, we cannot determine revocation status. */ - if (loadedCount == 0 && !hasRevocationChecker) { - throwUndeterminedRevocationStatus( - "Revocation checking enabled but no CRLs found in " + - "CertStores and no PKIXRevocationChecker configured " + - "for OCSP", - certPath, certs); + if (loadedCount == 0) { + if (!hasRevocationChecker) { + throwUndeterminedRevocationStatus( + "Revocation checking enabled but no CRLs found in " + + "CertStores and no PKIXRevocationChecker configured " + + "for OCSP", + certPath, certs); + } + else { + handleMissingCrl(revChecker, noFallback, cm, certPath, + failIndex); + } } } else { - log("revocation not enabled in PKIXParameters"); + log("revocation not enabled in PKIXParameters and no PREFER_CRLS" + + "checker registered"); } } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java index 6de9a68f..9523d5b9 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java @@ -24,6 +24,7 @@ import java.net.URI; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertPath; import java.security.cert.CertPathValidatorException; import java.security.cert.CertPathValidatorException.BasicReason; import java.security.cert.Extension; @@ -543,6 +544,27 @@ private void handleException(CertPathValidatorException e) } } + /** + * Fail closed when a PREFER_CRLS/NO_FALLBACK checker has no CRL source. + * + * check() runs before CRLs are loaded and cannot see whether a CRL source + * is configured, so the validator calls this after CRL setup. With OCSP + * suppressed by NO_FALLBACK and no CRL to check, revocation status is + * undetermined. Honors SOFT_FAIL via handleException(). + * + * @param certPath the CertPath being validated, for exception reporting + * @param index index of the cert whose revocation status is undetermined + * + * @throws CertPathValidatorException if SOFT_FAIL is not set + */ + void handleMissingCrlRevocation(CertPath certPath, int index) + throws CertPathValidatorException { + handleException(new CertPathValidatorException( + "PREFER_CRLS with NO_FALLBACK selected but no CRL source is " + + "available, revocation status cannot be determined", null, + certPath, index, BasicReason.UNDETERMINED_REVOCATION_STATUS)); + } + /** * Set OCSP responder URI override. * diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java index e6dcb0a0..9f058d22 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptUtil.java @@ -904,11 +904,10 @@ private static String getSHAVariant(String component) { * Get minimum key size limit from disabled algorithms security property * for specified algorithm. * - * Parses constraints like "RSA keySize < 1024" from the security - * property and returns the minimum allowed key size. Entries are - * matched on their leading algorithm name, any algorithm name may be - * used. Only the "<" and "<=" operators are supported, entries - * using other operators are ignored. + * Parses constraints (ex: {@code RSA keySize < 1024}) from the security + * property and returns the minimum allowed key size. Match entries on their + * leading algorithm name. Only {@code <} and {@code <=} operators are + * supported, entries using other operators are ignored. * * @param algo Algorithm to search for key size limitation for * (ex: "RSA", "DH", "DSA", "EC") @@ -920,30 +919,38 @@ private static String getSHAVariant(String component) { public static int getDisabledAlgorithmsKeySizeLimit(String algo, String propertyName) { - return getDisabledAlgorithmsKeySizeLimit(algo, propertyName, false); + return getDisabledAlgorithmsKeySizeLimit(algo, propertyName, false, 0); } /** * Internal implementation of getDisabledAlgorithmsKeySizeLimit(). * - * Matches entries on their leading algorithm name so that, for + * Matches entries on leading algorithm name so that, for * example, an "ECDH keySize" entry does not set the "DH" limit. * + * With a known keySize, an entry disables the key only when all of its + * keySize constraints match. denyAfter and jdkCA qualifiers are treated as + * satisfied rather than evaluated, so an entry using them fails closed. + * With keySize 0, only the floor from the first {@code <} or {@code <=} + * constraint is returned. + * * @param algo Algorithm to search for key size limitation for * (ex: "RSA", "DH", "DSA", "EC") * @param propertyName Security property name to check * @param certPathContext true when checking for CertPath validation, * skips entries scoped to usage contexts that can never apply * there + * @param keySize actual key size in bits, or 0 to compute the floor only * - * @return minimum key size allowed, or 0 if not set in property + * @return -1 if keySize is disabled, the min-size floor when keySize is + * 0, or 0 when no constraint applies */ private static int getDisabledAlgorithmsKeySizeLimit(String algo, - String propertyName, boolean certPathContext) { + String propertyName, boolean certPathContext, int keySize) { int ret = 0; List disabledList = null; - Pattern p = Pattern.compile("keySize\\s*<(=?)\\s*(\\d+)", + Pattern p = Pattern.compile("keySize\\s*(>=|<=|==|!=|>|<)\\s*(\\d+)", Pattern.CASE_INSENSITIVE); Matcher match = null; @@ -959,8 +966,7 @@ private static int getDisabledAlgorithmsKeySizeLimit(String algo, disabledList = getExpandedDisabledEntries(propertyName); for (String s : disabledList) { - /* Match on the leading algorithm name only, so "ECDH keySize" - * does not match algo "DH" */ + /* Match on leading algorithm name only */ String disabledName = extractDisabledAlgorithmName(s); if (disabledName == null || !disabledName.equalsIgnoreCase(algo)) { continue; @@ -973,20 +979,54 @@ private static int getDisabledAlgorithmsKeySizeLimit(String algo, } match = p.matcher(s); - if (match.find()) { - try { - int limit = Integer.parseInt(match.group(2)); - if (match.group(1).equals("=") && - limit < Integer.MAX_VALUE) { - /* "keySize <= N" disables through N, minimum allowed - * size is N + 1 */ - limit = limit + 1; + + if (keySize > 0) { + boolean anyConstraint = false; + boolean allMatch = true; + while (match.find()) { + String op = match.group(1); + int limit; + try { + limit = Integer.parseInt(match.group(2)); + } catch (NumberFormatException e) { + /* Exceeds Integer.MAX_VALUE, ignore this constraint */ + continue; + } + anyConstraint = true; + boolean matches = + (op.equals("<") && keySize < limit) || + (op.equals("<=") && keySize <= limit) || + (op.equals(">") && keySize > limit) || + (op.equals(">=") && keySize >= limit) || + (op.equals("==") && keySize == limit) || + (op.equals("!=") && keySize != limit); + if (!matches) { + allMatch = false; + break; + } + } + if (anyConstraint && allMatch) { + return -1; + } + } + else { + /* keySize unknown, derive floor from first < or <= */ + while (match.find()) { + String op = match.group(1); + if (!op.equals("<") && !op.equals("<=")) { + continue; } - /* Keep the strictest of multiple matching entries */ - ret = Math.max(ret, limit); - } catch (NumberFormatException e) { - /* Number exceeds Integer.MAX_VALUE, ignore malformed - * number and leave ret unchanged. */ + try { + int limit = Integer.parseInt(match.group(2)); + if (op.equals("<=") && limit < Integer.MAX_VALUE) { + /* "keySize <= N" disables N, min allowed N + 1 */ + limit = limit + 1; + } + ret = Math.max(ret, limit); + } catch (NumberFormatException e) { + /* Number exceeds Integer.MAX_VALUE, ignore */ + } + break; } } } @@ -1049,7 +1089,7 @@ private static boolean isKeyAllowed(PublicKey key, String propertyName, boolean certPathContext) { int keySize = 0; - int minSize = 0; + int sizeLimit = 0; String algorithm = null; if (key == null) { @@ -1067,8 +1107,8 @@ private static boolean isKeyAllowed(PublicKey key, String propertyName, if (key instanceof RSAPublicKey) { RSAPublicKey rsaKey = (RSAPublicKey)key; keySize = rsaKey.getModulus().bitLength(); - minSize = getDisabledAlgorithmsKeySizeLimit("RSA", propertyName, - certPathContext); + sizeLimit = getDisabledAlgorithmsKeySizeLimit("RSA", propertyName, + certPathContext, keySize); } else if (key instanceof ECPublicKey) { ECPublicKey ecKey = (ECPublicKey)key; @@ -1097,24 +1137,24 @@ else if (key instanceof ECPublicKey) { } } - minSize = getDisabledAlgorithmsKeySizeLimit("EC", propertyName, - certPathContext); + sizeLimit = getDisabledAlgorithmsKeySizeLimit("EC", propertyName, + certPathContext, keySize); } else if (key instanceof DSAPublicKey) { DSAPublicKey dsaKey = (DSAPublicKey)key; if (dsaKey.getParams() != null) { keySize = dsaKey.getParams().getP().bitLength(); } - minSize = getDisabledAlgorithmsKeySizeLimit("DSA", propertyName, - certPathContext); + sizeLimit = getDisabledAlgorithmsKeySizeLimit("DSA", propertyName, + certPathContext, keySize); } else if (key instanceof DHPublicKey) { DHPublicKey dhKey = (DHPublicKey)key; if (dhKey.getParams() != null) { keySize = dhKey.getParams().getP().bitLength(); } - minSize = getDisabledAlgorithmsKeySizeLimit("DH", propertyName, - certPathContext); + sizeLimit = getDisabledAlgorithmsKeySizeLimit("DH", propertyName, + certPathContext, keySize); } else if (key instanceof WolfCryptMlDsaPublicKey) { /* ML-DSA uses fixed parameter sets, no key size constraints */ @@ -1136,8 +1176,13 @@ else if (key instanceof WolfCryptSlhDsaPublicKey) { certPathContext); } - /* If minimum size constraint exists and key is smaller, reject */ - if (minSize > 0 && keySize < minSize) { + /* Negative limit means a >/>=/==/!= disabled this size. */ + if (sizeLimit < 0) { + return false; + } + + /* Reject a key below the min size floor */ + if (sizeLimit > 0 && keySize < sizeLimit) { return false; } diff --git a/src/main/java/com/wolfssl/wolfcrypt/AesGmac.java b/src/main/java/com/wolfssl/wolfcrypt/AesGmac.java index 08160c29..54249a39 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/AesGmac.java +++ b/src/main/java/com/wolfssl/wolfcrypt/AesGmac.java @@ -66,10 +66,18 @@ protected long mallocNativeStruct() throws OutOfMemoryError { } @Override - public void releaseNativeStruct() { + public synchronized void releaseNativeStruct() { synchronized (stateLock) { - native_free(); - super.releaseNativeStruct(); + if (state == WolfCryptState.RELEASED) { + return; + } + synchronized (pointerLock) { + if (state != WolfCryptState.UNINITIALIZED) { + native_free(); + } + super.releaseNativeStruct(); + } + state = WolfCryptState.RELEASED; } } @@ -225,8 +233,10 @@ public static synchronized boolean verify(byte[] key, byte[] iv, } private void throwIfKeyNotLoaded() throws IllegalStateException { - if (state != WolfCryptState.READY) { - throw new IllegalStateException("No key available"); + synchronized (stateLock) { + if (state != WolfCryptState.READY) { + throw new IllegalStateException("No key available"); + } } } diff --git a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java index a989fcc8..88a81db2 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java +++ b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java @@ -326,10 +326,10 @@ public class FeatureDetect { public static native boolean RsaPssEnabled(); /** - * Tests if RSA-PSS salt lengths longer than the digest are compiled into - * the native wolfSSL library (WOLFSSL_PSS_LONG_SALT). + * Tests if RSA-PSS salt lengths longer than the digest are supported by + * the native wolfSSL library. * - * @return true if enabled, otherwise false if not compiled in. + * @return true if supported, otherwise false. */ public static native boolean RsaPssLongSaltEnabled(); diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java index 027684e1..67a8d074 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXCertPathValidatorTest.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.ArrayList; +import java.util.EnumSet; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -41,6 +42,10 @@ import java.io.InputStream; import java.io.IOException; import java.io.FileInputStream; +import java.util.Arrays; +import java.nio.file.Paths; +import java.nio.file.Files; +import java.nio.charset.StandardCharsets; import java.io.FileNotFoundException; import java.security.Security; import java.security.Provider; @@ -57,6 +62,7 @@ import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorResult; import java.security.cert.PKIXParameters; +import java.security.cert.PKIXRevocationChecker; import java.security.cert.PKIXCertPathChecker; import java.security.cert.PKIXCertPathValidatorResult; import java.security.cert.CertificateException; @@ -97,6 +103,7 @@ public class WolfCryptPKIXCertPathValidatorTest { protected static String caCertDer = null; /* ca-cert.der */ protected static String caEccCertDer = null; /* ca-ecc-cert.der */ protected static String crlDer = null; /* crl.der */ + protected static String crlRevoked = null; /* crl.revoked */ /* RSA-based cert chain with intermediates: * server/peer: server-int-cert.pem/der @@ -208,6 +215,8 @@ public static void testSetupAndProviderInstallation() crlDer = certPre.concat("examples/certs/crl/crl.der"); + crlRevoked = + certPre.concat("examples/certs/crl/crl.revoked"); } /** @@ -314,6 +323,312 @@ public void testSingleCertValidateRSA() checkPKIXCertPathValidatorResult(result, caCert, certPubKey); } + /* Build a single server-cert path validated against the RSA CA anchor. */ + private CertPath singleServerCertPath(CertificateFactory certFactory) + throws Exception { + + List certList = new ArrayList<>(); + InputStream fis = new FileInputStream(serverCertDer); + certList.add(certFactory.generateCertificate(fis)); + fis.close(); + return certFactory.generateCertPath(certList); + } + + /** + * PREFER_CRLS with NO_FALLBACK and no CRL source must fail closed with + * UNDETERMINED_REVOCATION_STATUS. + */ + @Test + public void testPreferCrlsNoFallbackWithoutCrlFailsClosed() + throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(false); + params.addCertPathChecker(rc); + + try { + cpv.validate(path, params); + fail("Validation should fail closed with no CRL source"); + } catch (CertPathValidatorException e) { + assertEquals(BasicReason.UNDETERMINED_REVOCATION_STATUS, + e.getReason()); + } + } + + /** + * With SOFT_FAIL, the missing-CRL determination is soft, so validation + * completes instead of failing closed. The soft-fail exception is not + * asserted here because addCertPathChecker() clones rc, so it lands on + * the clone rather than rc. + */ + @Test + public void testPreferCrlsNoFallbackWithoutCrlSoftFailPasses() + throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = + CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(false); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * SOFT_FAIL with revocation enabled but no CRL source must also complete. + * Revocation turns on the native CRL check, so the missing-CRL path must + * disable it under SOFT_FAIL to avoid a hard CRL_MISSING failure. + */ + @Test + public void testPreferCrlsNoFallbackRevocationEnabledSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println("CertPathValidator revocation status test " + + "skipped, CRL not compiled in"); + return; + } + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * Load a CRL from a file. Some CRL files carry a PEM text dump ahead + * of the PEM block, which not every CertificateFactory skips, so start + * at the PEM header when one is present. + */ + private CRL crlFromFile(CertificateFactory certFactory, String path) + throws Exception { + + byte[] data = Files.readAllBytes(Paths.get(path)); + int begin = new String(data, StandardCharsets.US_ASCII) + .indexOf("-----BEGIN X509 CRL-----"); + + if (begin > 0) { + data = Arrays.copyOfRange(data, begin, data.length); + } + return certFactory.generateCRL(new ByteArrayInputStream(data)); + } + + /** + * Validate the single server cert with a PREFER_CRLS/NO_FALLBACK checker + * and the given CRL supplied through a CertStore. + */ + private void validatePreferCrlsNoFallbackWithCrl(String crlPath, + boolean revocationEnabled) throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK)); + + /* CRL files are issued by ca-cert.der, the root for server-cert */ + Collection crls = new HashSet<>(); + crls.add(crlFromFile(certFactory, crlPath)); + List certStores = new ArrayList<>(); + certStores.add(CertStore.getInstance("Collection", + new CollectionCertStoreParameters(crls))); + + PKIXParameters params = new PKIXParameters(store); + params.setCertStores(certStores); + params.setRevocationEnabled(revocationEnabled); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * Validate a server cert with a PREFER_CRLS checker that allows OCSP + * fallback, SOFT_FAIL set, and no CRL source. + */ + private void validatePreferCrlsFallbackSoftFailWithoutCrl( + boolean revocationEnabled) throws Exception { + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(revocationEnabled); + params.addCertPathChecker(rc); + + cpv.validate(path, params); + } + + /** + * PREFER_CRLS with fallback allowed and no CRL source must not fail on + * a missing CRL, revocation is then determined by OCSP alone. With + * SOFT_FAIL an unreachable OCSP responder is not fatal, so validation + * should complete successfully. + */ + @Test + public void testPreferCrlsFallbackWithoutCrlSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS fallback test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsFallbackSoftFailWithoutCrl(false); + } + + /** + * PREFER_CRLS with fallback allowed, SOFT_FAIL, no CRL source, and + * revocation enabled must not fail on the missing CRL, revocation is + * then determined only by OCSP. + */ + @Test + public void testPreferCrlsFallbackEnabledWithoutCrlSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS fallback test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsFallbackSoftFailWithoutCrl(true); + } + + /** + * A PREFER_CRLS/NO_FALLBACK checker with CRLs actually loaded must still + * validate a non-revoked cert. The missing-CRL fail-closed path must not + * fire when a CRL source is present. + */ + @Test + public void testPreferCrlsNoFallbackWithCrlValidates() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS with CRL test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsNoFallbackWithCrl(crlDer, true); + } + + /** + * A checker added with addCertPathChecker() applies irrespective of + * setRevocationEnabled(), so PREFER_CRLS with a CRL in the CertStores is + * a supported CRL-only setup even with revocation disabled. + */ + @Test + public void testPreferCrlsNoFallbackRevocationDisabledWithCrlValidates() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS with CRL test skipped, CRL not compiled in"); + return; + } + + validatePreferCrlsNoFallbackWithCrl(crlDer, false); + } + + /** + * PREFER_CRLS with NO_FALLBACK, revocation disabled, and a CRL in the + * CertStores that revokes the server cert must fail validation, proving + * the supplied CRL is checked and not just ignored. + */ + @Test + public void testPreferCrlsNoFallbackRevocationDisabledRevokedFails() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println( + "PREFER_CRLS with CRL test skipped, CRL not compiled in"); + return; + } + + try { + validatePreferCrlsNoFallbackWithCrl(crlRevoked, false); + fail("Revoked cert should fail with revocation disabled and " + + "PREFER_CRLS"); + + } catch (CertPathValidatorException e) { + /* expected */ + } + } + /** * Test that setting the target cert constraints with * PKIXParameters.setTargetCertConstraints() passes with correct cert @@ -2049,6 +2364,96 @@ public void testRevocationEnabledEmptyCertStoreFailsWithUndeterminedStatus() } } + /** + * PREFER_CRLS/NO_FALLBACK with revocation enabled and a CertStore that + * loads no matching CRL (loadedCount == 0) must fail closed. + */ + @Test + public void testPreferCrlsNoFallbackEmptyCertStoreFailsClosed() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println("CertPathValidator revocation status test " + + "skipped, CRL not compiled in"); + return; + } + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + /* Non-empty store list holding no matching CRL drives loadedCount 0 */ + List certStores = new ArrayList<>(); + certStores.add(CertStore.getInstance("Collection", + new CollectionCertStoreParameters(new HashSet()))); + params.setCertStores(certStores); + + try { + cpv.validate(path, params); + fail("Expected UNDETERMINED_REVOCATION_STATUS"); + } catch (CertPathValidatorException e) { + assertEquals(BasicReason.UNDETERMINED_REVOCATION_STATUS, + e.getReason()); + } + } + + /** + * Same as above with SOFT_FAIL, validation must complete because the + * missing-CRL path disables the native CRL check. + */ + @Test + public void testPreferCrlsNoFallbackEmptyCertStoreSoftFailPasses() + throws Exception { + + if (!WolfCrypt.CrlEnabled()) { + System.out.println("CertPathValidator revocation status test " + + "skipped, CRL not compiled in"); + return; + } + + KeyStore store = createKeyStoreFromFile(jksCaServerRSA2048, + keyStorePass); + if (store == null || store.size() != 1) { + throw new Exception("Error creating KeyStore"); + } + + CertificateFactory certFactory = + CertificateFactory.getInstance("X.509"); + CertPath path = singleServerCertPath(certFactory); + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + + PKIXRevocationChecker rc = + (PKIXRevocationChecker) cpv.getRevocationChecker(); + rc.setOptions(EnumSet.of(PKIXRevocationChecker.Option.PREFER_CRLS, + PKIXRevocationChecker.Option.NO_FALLBACK, + PKIXRevocationChecker.Option.SOFT_FAIL)); + + PKIXParameters params = new PKIXParameters(store); + params.setRevocationEnabled(true); + params.addCertPathChecker(rc); + List certStores = new ArrayList<>(); + certStores.add(CertStore.getInstance("Collection", + new CollectionCertStoreParameters(new HashSet()))); + params.setCertStores(certStores); + + cpv.validate(path, params); + } + /** * Test that zero-length cert paths are valid per RFC 5280. This occurs * when CertPathBuilder determines the trust anchor itself is the target. diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java index 3df3f516..1f3400f8 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptSignatureTest.java @@ -1821,7 +1821,7 @@ private void testRsaPssMaxSaltLengths() if (!FeatureDetect.RsaPssLongSaltEnabled()) { System.out.println("\tSkipping max salt lengths, " + - "WOLFSSL_PSS_LONG_SALT not compiled in"); + "long PSS salts not supported by native wolfSSL"); return; } @@ -2218,7 +2218,7 @@ public void testRsaPssMultipleUpdates() /* Uses the maximum salt length for each digest */ if (!FeatureDetect.RsaPssLongSaltEnabled()) { System.out.println( - "\tSkipping, WOLFSSL_PSS_LONG_SALT not compiled in"); + "\tSkipping, long PSS salts not supported by native wolfSSL"); return; } diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptUtilTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptUtilTest.java index 79000306..c406c526 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptUtilTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptUtilTest.java @@ -1196,10 +1196,17 @@ public void testGetDisabledAlgorithmsKeySizeLimitNameMatch() { WolfCryptUtil.getDisabledAlgorithmsKeySizeLimit( "RSA", "jdk.certpath.disabledAlgorithms")); - /* Unsupported operators are ignored */ + /* The floor overload returns 0 for non-floor operators */ Security.setProperty("jdk.certpath.disabledAlgorithms", "RSA keySize >= 8192"); - assertEquals("Unsupported operator should be ignored", 0, + assertEquals("keySize >= sets no minimum-size floor", 0, + WolfCryptUtil.getDisabledAlgorithmsKeySizeLimit( + "RSA", "jdk.certpath.disabledAlgorithms")); + + /* An & chain still yields the floor from its < constraint */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize >= 1024 & keySize < 2048"); + assertEquals("floor comes from the < half of an & chain", 2048, WolfCryptUtil.getDisabledAlgorithmsKeySizeLimit( "RSA", "jdk.certpath.disabledAlgorithms")); @@ -1290,6 +1297,126 @@ public void testIsKeyAllowedKeySizeUsageQualifiers() throws Exception { } } + @Test + public void testIsKeyAllowedKeySizeOperators() throws Exception { + + String origProperty = Security.getProperty( + "jdk.certpath.disabledAlgorithms"); + PublicKey rsaPub = null; + + try { + KeyPairGenerator kpg = + KeyPairGenerator.getInstance("RSA", "wolfJCE"); + kpg.initialize(2048); + rsaPub = kpg.generateKeyPair().getPublic(); + } catch (Exception e) { + /* skip, RSA key generation not available */ + return; + } + + try { + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize == 2048"); + assertFalse("keySize == N must reject a key of size N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize == 4096"); + assertTrue("keySize == N must allow a key of a different size", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize >= 2048"); + assertFalse("keySize >= N must reject a key of size N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize >= 4096"); + assertTrue("keySize >= N must allow a key below N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize > 1024"); + assertFalse("keySize > N must reject a key above N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize > 2048"); + assertTrue("keySize > N must allow a key of size N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize != 4096"); + assertFalse("keySize != N must reject a key of a different size", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize != 2048"); + assertTrue("keySize != N must allow a key of size N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + /* < and <= remain handled by the minimum-size floor */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 4096"); + assertFalse("keySize < N must reject a key below N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 2048"); + assertTrue("keySize < N must allow a key of size N", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 1024, RSA keySize > 4096"); + assertTrue("floor plus a non-matching range must allow the key", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize < 1024, RSA keySize >= 2048"); + assertFalse("floor plus a matching range must reject the key", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize >= 2048"); + assertFalse("keySize >= N rejects via CertPath overload", + WolfCryptUtil.isKeyAllowedForCertPath(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + /* & chains AND their keySize constraints, an entry disables the + * key only when every constraint holds */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize >= 1024 & keySize < 2048"); + assertTrue("key outside an AND range must be allowed", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize >= 1024 & keySize <= 2048"); + assertFalse("key inside an AND range must be rejected", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + + /* denyAfter is not evaluated, the entry fails closed on keySize */ + Security.setProperty("jdk.certpath.disabledAlgorithms", + "RSA keySize == 2048 & denyAfter 2999-01-01"); + assertFalse("denyAfter entry applies unconditionally on keySize", + WolfCryptUtil.isKeyAllowed(rsaPub, + "jdk.certpath.disabledAlgorithms")); + } finally { + if (origProperty != null) { + Security.setProperty("jdk.certpath.disabledAlgorithms", + origProperty); + } else { + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); + } + } + } + @Test public void testIsKeyAllowedECNamedCurve() throws Exception { diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java index 535b16f0..57f0f5b8 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfSSLKeyStoreTest.java @@ -2104,6 +2104,10 @@ public void testLoadWKSasPKCS12FromFile() WolfCryptProvider prov = null; KeyStore store = null; + /* The Android platform PKCS12 provider cannot read the PBES2/PBKDF2 + * client.p12, matching testLoadWKSasJKSFromFile which also skips. */ + Assume.assumeTrue(!isAndroid()); + /* Use client.wks (clientWKS) to test. Any WKS KeyStore could be used, * this was just picked since was first used/tested in test above. */ diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AesGmacTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AesGmacTest.java index aecfbda7..0ec7d399 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AesGmacTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AesGmacTest.java @@ -377,6 +377,46 @@ public void testAesGmacStateTransitions() { gmac.releaseNativeStruct(); } + @Test + public void testAesGmacSetKeyAfterReleaseThrows() { + if (!FeatureDetect.AesGmacEnabled()) { + /* skip test if AES-GMAC is not compiled in native wolfCrypt */ + return; + } + + AesGmac gmac = new AesGmac(); + gmac.setKey(new byte[16]); + gmac.releaseNativeStruct(); + + /* Re-keying a released object must throw, its AES was freed */ + try { + gmac.setKey(new byte[16]); + fail("Expected IllegalStateException"); + } catch (IllegalStateException e) { + /* Expected */ + } + } + + @Test + public void testAesGmacReleaseBeforeSetKey() { + if (!FeatureDetect.AesGmacEnabled()) { + /* skip test if AES-GMAC is not compiled in native wolfCrypt */ + return; + } + + /* Release with no key set must free quietly, twice is harmless */ + AesGmac gmac = new AesGmac(); + gmac.releaseNativeStruct(); + gmac.releaseNativeStruct(); + + try { + gmac.setKey(new byte[16]); + fail("Expected IllegalStateException"); + } catch (IllegalStateException e) { + /* Expected */ + } + } + @Test public void testAesGmacInvalidInputs() { if (!FeatureDetect.AesGmacEnabled()) { diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java index 86654351..28e30a46 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java @@ -207,6 +207,25 @@ public void aes128OfbEncryptDecryptTest() { aesOfb.releaseNativeStruct(); } + /** + * A DECRYPT_MODE key schedule must still recover ciphertext produced + * under ENCRYPT_MODE. + */ + @Test + public void aes128OfbDecryptModeSetKeyRoundTrip() { + AesOfb enc = new AesOfb(); + enc.setKey(KEY_128, IV_128, AesOfb.ENCRYPT_MODE); + byte[] ciphertext = enc.encrypt(PLAINTEXT_128); + assertArrayEquals(CIPHERTEXT_128, ciphertext); + enc.releaseNativeStruct(); + + AesOfb dec = new AesOfb(); + dec.setKey(KEY_128, IV_128, AesOfb.DECRYPT_MODE); + byte[] decrypted = dec.decrypt(ciphertext); + assertArrayEquals(PLAINTEXT_128, decrypted); + dec.releaseNativeStruct(); + } + @Test public void aes128OfbLongDataTest() { AesOfb aesOfb = new AesOfb(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java index 1f7b2cc4..cd6c37db 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/EccTest.java @@ -297,6 +297,19 @@ public void eccMakeKeyOnCurve() { } } + @Test + public void eccMakeKeyOnCurveNullRngReportsBadFuncArg() { + Ecc alice = new Ecc(); + try { + /* A null Rng is an argument error, not a curve problem, so it + * must be reported as BAD_FUNC_ARG */ + alice.makeKeyOnCurve(null, 32, "secp256r1"); + fail("null Rng should fail with exception"); + } catch (WolfCryptException e) { + assertEquals(WolfCryptError.BAD_FUNC_ARG, e.getError()); + } + } + @Test public void eccPrivateToPkcs8() { Ecc alice = new Ecc(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java index 738e01aa..83eee480 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/RsaTest.java @@ -423,6 +423,21 @@ public void rsaPkcs8DecodeAndVerify() { pub.releaseNativeStruct(); } + @Test + public void rsaPrivateKeyToPkcs8RoundTrip() { + Rsa key = makeKeyWithRetry(2048, 65537, rng); + + byte[] pkcs8 = key.privateKeyEncodePKCS8(); + assertNotNull(pkcs8); + assertTrue(pkcs8.length > 0); + key.releaseNativeStruct(); + + /* PKCS8 output must decode back into a usable private key */ + Rsa decoded = new Rsa(); + decoded.decodePrivateKeyPKCS8(pkcs8); + decoded.releaseNativeStruct(); + } + @Test public void publicKeyDecodeAndEncodeWithByteBuffer() { Rsa key = new Rsa(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java index ac61e6b0..646d9ec3 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/SlhDsaTest.java @@ -36,6 +36,7 @@ import com.wolfssl.wolfcrypt.SlhDsa; import com.wolfssl.wolfcrypt.Rng; import com.wolfssl.wolfcrypt.Sha256; +import com.wolfssl.wolfcrypt.Sha384; import com.wolfssl.wolfcrypt.Sha512; import com.wolfssl.wolfcrypt.WolfCrypt; import com.wolfssl.wolfcrypt.WolfCryptError; @@ -766,20 +767,17 @@ public void preHashPairingMatchesSha2() { byte[] msg = "pre-hash pairing check".getBytes(); - /* Independently validate the FIPS 205 Section 10.2.2 pre-hash pairing - * for the SHA2 sets. signPreHash() chooses the hash in native C. Here - * we reconstruct the expected digest and hashType in Java and confirm - * the signature verifies through the explicit-digest verifyHash() - * path, then that the other SHA-2 hash does NOT verify. Sign and verify - * do not share the digest helper, so a wrong-but-consistent pairing is - * caught (128-bit sets use SHA-256, 192/256-bit sets use SHA-512). The - * SHAKE sets cannot be cross-checked here (no Java SHAKE digest). */ + /* Confirm the pre-hash signPreHash() picks natively (SHA-256 for + * 128-bit sets, SHA-512 for 192/256-bit) by verifying with the + * expected digest, then with a mismatched one. The mismatch must be + * at least as strong as the set, weaker hashes are rejected with + * BAD_FUNC_ARG. SHAKE sets are not checked (no Java SHAKE digest). */ checkSha2Pairing(SlhDsa.SLH_DSA_SHA2_128F, msg, sha256(msg), WolfCrypt.WC_HASH_TYPE_SHA256, sha512(msg), WolfCrypt.WC_HASH_TYPE_SHA512); checkSha2Pairing(SlhDsa.SLH_DSA_SHA2_192F, msg, sha512(msg), WolfCrypt.WC_HASH_TYPE_SHA512, - sha256(msg), WolfCrypt.WC_HASH_TYPE_SHA256); + sha384(msg), WolfCrypt.WC_HASH_TYPE_SHA384); } @Test @@ -888,6 +886,12 @@ private static byte[] sha256(byte[] in) { return sha.digest(); } + private static byte[] sha384(byte[] in) { + Sha384 sha = new Sha384(); + sha.update(in); + return sha.digest(); + } + private static byte[] sha512(byte[] in) { Sha512 sha = new Sha512(); sha.update(in);