diff --git a/examples/provider/CryptoBenchmark.sh b/examples/provider/CryptoBenchmark.sh index 1b66336e..845896b7 100755 --- a/examples/provider/CryptoBenchmark.sh +++ b/examples/provider/CryptoBenchmark.sh @@ -20,8 +20,9 @@ verify_sha256() { elif command -v shasum >/dev/null; then actual=$(shasum -a 256 "$file" | awk '{print $1}') else - echo "Warning: no sha256sum or shasum available, skipping hash verification" - return 0 + echo "Error: no sha256sum or shasum found, refusing unverified $file" + rm -f "$file" + return 1 fi if [ "$actual" != "$expected" ]; then @@ -40,6 +41,13 @@ download_bc_jars() { local lib_dir="$LIB_DIR" local bc_url="https://repo1.maven.org/maven2/org/bouncycastle" + # Require a SHA-256 tool before any download, JARs are only added to the + # classpath after hash verification + if ! command -v sha256sum >/dev/null && ! command -v shasum >/dev/null; then + echo "failed (no sha256sum or shasum for hash verification)" + return 1 + fi + echo -n "Downloading Bouncy Castle JARs (version $bc_version)... " mkdir -p "$lib_dir" || { echo "failed (cannot create $lib_dir)" diff --git a/jni/jni_aes.c b/jni/jni_aes.c index 4b501e32..3480e0b5 100644 --- a/jni/jni_aes.c +++ b/jni/jni_aes.c @@ -71,6 +71,7 @@ Java_com_wolfssl_wolfcrypt_Aes_native_1set_1key_1internal( byte* key = NULL; byte* iv = NULL; word32 keySz = 0; + word32 ivSz = 0; aes = (Aes*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -81,10 +82,15 @@ Java_com_wolfssl_wolfcrypt_Aes_native_1set_1key_1internal( key = getByteArray(env, key_object); iv = getByteArray(env, iv_object); keySz = getByteArrayLength(env, key_object); + ivSz = getByteArrayLength(env, iv_object); - ret = (!aes || !key) /* iv is optional */ - ? BAD_FUNC_ARG - : wc_AesSetKey(aes, key, keySz, iv, opmode); + /* IV optional. If provided, reject under/oversized array. */ + if (!aes || !key || (iv != NULL && ivSz != AES_BLOCK_SIZE)) { + ret = BAD_FUNC_ARG; + } + else { + ret = wc_AesSetKey(aes, key, keySz, iv, opmode); + } if (ret != 0) throwWolfCryptExceptionFromError(env, ret); diff --git a/jni/jni_aesctr.c b/jni/jni_aesctr.c index 6300d75b..312c93c1 100644 --- a/jni/jni_aesctr.c +++ b/jni/jni_aesctr.c @@ -70,6 +70,7 @@ Java_com_wolfssl_wolfcrypt_AesCtr_native_1set_1key_1internal( byte* key = NULL; byte* iv = NULL; word32 keySz = 0; + word32 ivSz = 0; aes = (Aes*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -80,8 +81,10 @@ Java_com_wolfssl_wolfcrypt_AesCtr_native_1set_1key_1internal( key = getByteArray(env, key_object); iv = getByteArray(env, iv_object); keySz = getByteArrayLength(env, key_object); + ivSz = getByteArrayLength(env, iv_object); - if (aes == NULL || key == NULL || iv == NULL) { + /* wc_AesSetKey reads AES_BLOCK_SIZE IV bytes, reject a short array */ + if (aes == NULL || key == NULL || iv == NULL || ivSz != AES_BLOCK_SIZE) { ret = BAD_FUNC_ARG; } diff --git a/jni/jni_aesofb.c b/jni/jni_aesofb.c index a138a3f8..70dbbcf0 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; + word32 ivSz = 0; aes = (Aes*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -81,8 +82,10 @@ Java_com_wolfssl_wolfcrypt_AesOfb_native_1set_1key_1internal( key = getByteArray(env, key_object); iv = getByteArray(env, iv_object); keySz = getByteArrayLength(env, key_object); + ivSz = getByteArrayLength(env, iv_object); - if (aes == NULL || key == NULL || iv == NULL) { + /* wc_AesSetKey reads AES_BLOCK_SIZE IV bytes, reject a short array */ + if (aes == NULL || key == NULL || iv == NULL || ivSz != AES_BLOCK_SIZE) { ret = BAD_FUNC_ARG; } diff --git a/jni/jni_chacha.c b/jni/jni_chacha.c index 614b2233..9752056b 100644 --- a/jni/jni_chacha.c +++ b/jni/jni_chacha.c @@ -72,6 +72,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Chacha_wc_1Chacha_1setIV int ret = 0; ChaCha* chacha = NULL; byte* iv = NULL; + word32 ivSz = 0; chacha = (ChaCha*)(uintptr_t)getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -79,8 +80,9 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Chacha_wc_1Chacha_1setIV return; } iv = getByteArray(env, iv_object); + ivSz = getByteArrayLength(env, iv_object); - if (chacha == NULL || iv == NULL) { + if (chacha == NULL || iv == NULL || ivSz != CHACHA_IV_BYTES) { ret = BAD_FUNC_ARG; } @@ -107,13 +109,16 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Chacha_wc_1Chacha_1setKey ChaCha* chacha = NULL; byte* key = NULL; word32 keySz = 0; + jboolean keyIsCopy = JNI_FALSE; chacha = (ChaCha*)(uintptr_t)getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { /* getNativeStruct may throw exception, prevent throwing another */ return; } - key = getByteArray(env, key_object); + if (key_object != NULL) { + key = (byte*)(*env)->GetByteArrayElements(env, key_object, &keyIsCopy); + } keySz = getByteArrayLength(env, key_object); if (chacha == NULL || key == NULL) { @@ -129,6 +134,14 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Chacha_wc_1Chacha_1setKey LogStr("wc_Chacha_SetKey(chacha=%p) = %d\n", chacha, ret); + if (key != NULL && keyIsCopy == JNI_TRUE) { + #if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \ + !defined(WOLFSSL_NO_FORCE_ZERO) + wc_ForceZero(key, keySz); + #else + XMEMSET(key, 0, keySz); + #endif + } releaseByteArray(env, key_object, key, JNI_ABORT); #else throwNotCompiledInException(env); diff --git a/jni/jni_des3.c b/jni/jni_des3.c index 569559c8..c61cda43 100644 --- a/jni/jni_des3.c +++ b/jni/jni_des3.c @@ -69,6 +69,8 @@ Java_com_wolfssl_wolfcrypt_Des3_native_1set_1key_1internal( Des3* des = NULL; byte* key = NULL; byte* iv = NULL; + word32 keySz = 0; + word32 ivSz = 0; des = (Des3*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -78,10 +80,16 @@ Java_com_wolfssl_wolfcrypt_Des3_native_1set_1key_1internal( key = getByteArray(env, key_object); iv = getByteArray(env, iv_object); + keySz = getByteArrayLength(env, key_object); + ivSz = getByteArrayLength(env, iv_object); - ret = (!des || !key) /* iv is optional */ - ? BAD_FUNC_ARG - : wc_Des3_SetKey(des, key, iv, opmode); + if (!des || !key || keySz != DES3_KEY_SIZE || + (iv != NULL && ivSz != DES_BLOCK_SIZE)) { + ret = BAD_FUNC_ARG; + } + else { + ret = wc_Des3_SetKey(des, key, iv, opmode); + } if (ret != 0) throwWolfCryptExceptionFromError(env, ret); diff --git a/jni/jni_mldsa.c b/jni/jni_mldsa.c index a4592a60..509f441b 100644 --- a/jni/jni_mldsa.c +++ b/jni/jni_mldsa.c @@ -767,13 +767,16 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1import_1p wc_MlDsaKey* key = NULL; byte* in = NULL; word32 inLen = 0; + jboolean inIsCopy = JNI_FALSE; key = (wc_MlDsaKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { return; } - in = getByteArray(env, in_object); + if (in_object != NULL) { + in = (byte*)(*env)->GetByteArrayElements(env, in_object, &inIsCopy); + } inLen = getByteArrayLength(env, in_object); if (key == NULL || in == NULL) { @@ -789,6 +792,9 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1import_1p LogStr("wc_MlDsaKey_ImportPrivRaw(key=%p) = %d\n", key, ret); + if (in != NULL && inIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(in, inLen); + } releaseByteArray(env, in_object, in, JNI_ABORT); #else (void)env; @@ -1132,13 +1138,17 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1make_1key wc_MlDsaKey* key = NULL; byte* seed = NULL; word32 seedLen = 0; + jboolean seedIsCopy = JNI_FALSE; key = (wc_MlDsaKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { return; } - seed = getByteArray(env, seed_object); + if (seed_object != NULL) { + seed = (byte*)(*env)->GetByteArrayElements(env, seed_object, + &seedIsCopy); + } seedLen = getByteArrayLength(env, seed_object); /* Native API takes no seed length, seed must be exactly 32 bytes */ @@ -1158,6 +1168,9 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1make_1key LogStr("wc_MlDsaKey_MakeKeyFromSeed(key=%p) = %d\n", key, ret); + if (seed != NULL && seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); #else (void)env; @@ -1305,6 +1318,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig word32 msgLen = 0; word32 seedLen = 0; word32 sigLen = 0; + jboolean seedIsCopy = JNI_FALSE; key = (wc_MlDsaKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -1328,7 +1342,8 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig } if (seed_object != NULL) { - seed = getByteArray(env, seed_object); + seed = (byte*)(*env)->GetByteArrayElements(env, seed_object, + &seedIsCopy); seedLen = getByteArrayLength(env, seed_object); } @@ -1345,6 +1360,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig releaseByteArray(env, msg_object, msg, JNI_ABORT); } if (seed != NULL) { + if (seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); } return NULL; @@ -1409,6 +1427,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig releaseByteArray(env, msg_object, msg, JNI_ABORT); } if (seed_object != NULL) { + if (seed != NULL && seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); } #else @@ -1439,6 +1460,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig word32 hashLen = 0; word32 seedLen = 0; word32 sigLen = 0; + jboolean seedIsCopy = JNI_FALSE; key = (wc_MlDsaKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -1462,7 +1484,8 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig } if (seed_object != NULL) { - seed = getByteArray(env, seed_object); + seed = (byte*)(*env)->GetByteArrayElements(env, seed_object, + &seedIsCopy); seedLen = getByteArrayLength(env, seed_object); } @@ -1479,6 +1502,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig releaseByteArray(env, hash_object, hash, JNI_ABORT); } if (seed != NULL) { + if (seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); } return NULL; @@ -1543,6 +1569,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1sig releaseByteArray(env, hash_object, hash, JNI_ABORT); } if (seed_object != NULL) { + if (seed != NULL && seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); } #else @@ -1673,6 +1702,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1MlDsaKey_1Sign word32 muLen = 0; word32 seedLen = 0; word32 sigLen = 0; + jboolean seedIsCopy = JNI_FALSE; key = (wc_MlDsaKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { @@ -1690,7 +1720,8 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1MlDsaKey_1Sign } if (seed_object != NULL) { - seed = getByteArray(env, seed_object); + seed = (byte*)(*env)->GetByteArrayElements(env, seed_object, + &seedIsCopy); seedLen = getByteArrayLength(env, seed_object); } @@ -1703,6 +1734,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1MlDsaKey_1Sign releaseByteArray(env, mu_object, mu, JNI_ABORT); } if (seed != NULL) { + if (seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); } return NULL; @@ -1758,6 +1792,9 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1MlDsaKey_1Sign releaseByteArray(env, mu_object, mu, JNI_ABORT); } if (seed_object != NULL) { + if (seed != NULL && seedIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(seed, seedLen); + } releaseByteArray(env, seed_object, seed, JNI_ABORT); } #else @@ -1859,13 +1896,17 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1import_1k byte* pub = NULL; word32 privLen = 0; word32 pubLen = 0; + jboolean privIsCopy = JNI_FALSE; key = (wc_MlDsaKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { return; } - priv = getByteArray(env, priv_object); + if (priv_object != NULL) { + priv = (byte*)(*env)->GetByteArrayElements(env, priv_object, + &privIsCopy); + } privLen = getByteArrayLength(env, priv_object); pub = getByteArray(env, pub_object); pubLen = getByteArrayLength(env, pub_object); @@ -1883,6 +1924,9 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlDsa_wc_1dilithium_1import_1k LogStr("wc_MlDsaKey_ImportKey(key=%p) = %d\n", key, ret); + if (priv != NULL && privIsCopy == JNI_TRUE) { + MLDSA_FORCE_ZERO(priv, privLen); + } releaseByteArray(env, priv_object, priv, JNI_ABORT); releaseByteArray(env, pub_object, pub, JNI_ABORT); #else diff --git a/jni/jni_rsa.c b/jni/jni_rsa.c index d11376de..b43bad22 100644 --- a/jni/jni_rsa.c +++ b/jni/jni_rsa.c @@ -1012,7 +1012,7 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateDecrypt( RsaKey* key = NULL; byte* ciphertext = NULL; byte* output = NULL; - word32 size = 0, outputSz = 0; + word32 size = 0, outputSz = 0, outputBufSz = 0; int encSz = 0; key = (RsaKey*) getNativeStruct(env, this); @@ -1037,6 +1037,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateDecrypt( ret = BAD_FUNC_ARG; } else { outputSz = (word32)encSz; + /* Keep full allocation size for zeroization, outputSz shrinks + * to plaintext len after decrypt */ + outputBufSz = outputSz; } } @@ -1077,9 +1080,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateDecrypt( if (output != NULL) { #if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \ !defined(WOLFSSL_NO_FORCE_ZERO) - wc_ForceZero(output, outputSz); + wc_ForceZero(output, outputBufSz); #else - XMEMSET(output, 0, outputSz); + XMEMSET(output, 0, outputBufSz); #endif XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER); } @@ -1203,7 +1206,7 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateDecrypt_1ex( RsaKey* key = NULL; byte* ciphertext = NULL; byte* output = NULL; - word32 size = 0, outputSz = 0; + word32 size = 0, outputSz = 0, outputBufSz = 0; int encSz = 0; key = (RsaKey*) getNativeStruct(env, this); @@ -1233,6 +1236,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateDecrypt_1ex( ret = BAD_FUNC_ARG; } else { outputSz = (word32)encSz; + /* Keep full allocation size for zeroization, outputSz shrinks to + * the plaintext len after decrypt */ + outputBufSz = outputSz; } } @@ -1273,9 +1279,9 @@ Java_com_wolfssl_wolfcrypt_Rsa_wc_1RsaPrivateDecrypt_1ex( if (output != NULL) { #if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && \ !defined(WOLFSSL_NO_FORCE_ZERO) - wc_ForceZero(output, outputSz); + wc_ForceZero(output, outputBufSz); #else - XMEMSET(output, 0, outputSz); + XMEMSET(output, 0, outputBufSz); #endif XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER); } diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java index a220cb0a..43faf877 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptCipher.java @@ -1748,8 +1748,20 @@ else if (cipherMode == CipherMode.WC_CCM) { tmpIn = Arrays.copyOfRange(tmpIn, 0, tmpIn.length - this.gcmTagLen); - tmpOut = this.aesCcm.decrypt(tmpIn, this.iv, tag, - aad); + try { + tmpOut = this.aesCcm.decrypt(tmpIn, this.iv, + tag, aad); + + } catch (WolfCryptException e) { + /* Convert to AEADBadTagException */ + if (e.getCode() == + WolfCryptError.AES_CCM_AUTH_E.getCode()) { + /* Authentication check fail */ + throw new AEADBadTagException( + e.getMessage()); + } + throw e; + } } } else if (cipherMode == CipherMode.WC_ECB) { diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java index cf102295..24f25e09 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHKeyFactory.java @@ -295,6 +295,9 @@ private PrivateKey generatePrivateFromDHSpec(DHPrivateKeySpec keySpec) "Private key value must be positive"); } + /* Reject a prime below the minimum wolfCrypt will generate */ + checkDhPrimeSize(keySpec.getP()); + try { /* Create DHParameterSpec from p and g */ DHParameterSpec paramSpec = new DHParameterSpec( @@ -311,6 +314,26 @@ private PrivateKey generatePrivateFromDHSpec(DHPrivateKeySpec keySpec) } } + /** + * Enforce a minimum DH prime size on imported keys. + * + * wolfCrypt does not generate DH keys below DH_MIN_SIZE, so accepting a + * smaller prime on import would allow a group weaker than any wolfJCE + * produces. Reject those at the KeyFactory boundary. + * + * @param p DH prime modulus to check + * + * @throws InvalidKeySpecException if p has fewer than DH_MIN_SIZE bits + */ + private static void checkDhPrimeSize(BigInteger p) + throws InvalidKeySpecException { + + if (p.bitLength() < Dh.DH_MIN_SIZE) { + throw new InvalidKeySpecException( + "DH prime must be at least " + Dh.DH_MIN_SIZE + " bits"); + } + } + /** * Private helper method for generating DHPublicKey from * X509EncodedKeySpec. @@ -403,6 +426,9 @@ private PublicKey generatePublicFromDHSpec(DHPublicKeySpec keySpec) "Public key out of valid range: must satisfy 1 < Y < p-1"); } + /* Reject a prime below the minimum wolfCrypt will generate */ + checkDhPrimeSize(keySpec.getP()); + try { /* Create DHParameterSpec from p and g */ DHParameterSpec paramSpec = new DHParameterSpec( diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java index 5b261a90..118f9f28 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPrivateKey.java @@ -207,6 +207,12 @@ private void parsePKCS8Der(byte[] derData) p = new BigInteger(1, pBytes); idx += pLen; + /* Reject a prime below the minimum wolfCrypt will generate */ + if (p.bitLength() < Dh.DH_MIN_SIZE) { + throw new IllegalArgumentException( + "DH prime must be at least " + Dh.DH_MIN_SIZE + " bits"); + } + /* g INTEGER */ if (derData[idx++] != 0x02) { throw new IllegalArgumentException( diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java index 35d97a46..c94169d3 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptDHPublicKey.java @@ -250,6 +250,12 @@ private void parseX509Der(byte[] derData) "must satisfy 1 < Y < p-1"); } + /* Reject a prime below the minimum wolfCrypt will generate */ + if (p.bitLength() < Dh.DH_MIN_SIZE) { + throw new IllegalArgumentException( + "DH prime must be at least " + Dh.DH_MIN_SIZE + " bits"); + } + /* Store extracted values */ this.publicValue = publicVal; this.paramSpec = new DHParameterSpec(p, g); diff --git a/src/main/java/com/wolfssl/wolfcrypt/Hmac.java b/src/main/java/com/wolfssl/wolfcrypt/Hmac.java index 45da8fe1..a7ed7ad8 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Hmac.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Hmac.java @@ -182,7 +182,10 @@ public synchronized void setKey(int type, byte[] key) this.type = type; - /* Save copy of key[] into this.key */ + /* Save copy of key[] into this.key, zero old one first */ + if (this.key != null) { + Arrays.fill(this.key, (byte)0); + } this.key = new byte[key.length]; System.arraycopy(key, 0, this.key, 0, key.length); } diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java index bce86cc0..18e7e299 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptCipherTest.java @@ -3053,6 +3053,69 @@ public void testAesGcmBadTagExceptionRegression() } } + /** + * AES-CCM decrypt failure should throw AEADBadTagException. + */ + @Test + public void testAesCcmBadTagExceptionRegression() + throws NoSuchProviderException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, + IllegalBlockSizeException, InvalidAlgorithmParameterException, + BadPaddingException { + + if (!enabledJCEAlgos.contains("AES/CCM/NoPadding")) { + /* skip if AES-CCM is not enabled */ + return; + } + + byte[] key = new byte[] { + (byte)0x2b, (byte)0x7e, (byte)0x15, (byte)0x16, + (byte)0x28, (byte)0xae, (byte)0xd2, (byte)0xa6, + (byte)0xab, (byte)0xf7, (byte)0x15, (byte)0x88, + (byte)0x09, (byte)0xcf, (byte)0x4f, (byte)0x3c + }; + + byte[] nonce = new byte[] { + (byte)0x00, (byte)0x01, (byte)0x02, (byte)0x03, + (byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07, + (byte)0x08, (byte)0x09, (byte)0x0a, (byte)0x0b + }; + + byte[] plaintext = new byte[] { + (byte)0x48, (byte)0x65, (byte)0x6c, (byte)0x6c, + (byte)0x6f, (byte)0x20, (byte)0x57, (byte)0x6f, + (byte)0x72, (byte)0x6c, (byte)0x64, (byte)0x21 + }; + + Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding", jceProvider); + SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + GCMParameterSpec ccmSpec = new GCMParameterSpec(128, nonce); + + /* First encrypt to get valid ciphertext */ + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ccmSpec); + byte[] ciphertext = cipher.doFinal(plaintext); + + /* Corrupt the authentication tag (last 16 bytes) */ + byte[] corruptedCiphertext = ciphertext.clone(); + int tagStart = corruptedCiphertext.length - 16; + for (int i = tagStart; i < corruptedCiphertext.length; i++) { + corruptedCiphertext[i] = (byte)0xFF; + } + + /* Attempt to decrypt with corrupted tag, should throw + * AEADBadTagException */ + cipher.init(Cipher.DECRYPT_MODE, keySpec, ccmSpec); + try { + cipher.doFinal(corruptedCiphertext); + fail("Expected AEADBadTagException for corrupted CCM tag"); + } catch (AEADBadTagException e) { + /* Expected */ + } catch (Exception e) { + fail("Expected AEADBadTagException but got: " + + e.getClass().getSimpleName() + " - " + e.getMessage()); + } + } + @Test public void testAesEcbNoPadding() throws NoSuchProviderException, NoSuchAlgorithmException, diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java index 11195565..21c437ff 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptDHKeyFactoryTest.java @@ -32,6 +32,7 @@ import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; @@ -53,6 +54,8 @@ import com.wolfssl.provider.jce.WolfCryptProvider; import java.lang.reflect.Field; import com.wolfssl.provider.jce.WolfCryptDHPublicKey; +import com.wolfssl.provider.jce.WolfCryptDHPrivateKey; +import com.wolfssl.wolfcrypt.Dh; import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; @@ -443,6 +446,68 @@ public void testMultipleKeySizes() throws Exception { } } + /** + * A prime below Dh.DH_MIN_SIZE, the floor wolfCrypt uses when generating + * DH keys, must be rejected on import so the KeyFactory doesn't accept a + * group weaker than one wolfJCE produces. + */ + @Test + public void testWeakPrimeRejected() throws Exception { + + Assume.assumeTrue(FeatureDetect.DhEnabled()); + + KeyFactory kf = KeyFactory.getInstance("DH", "wolfJCE"); + + /* Prime one bit below the enforced minimum, this is not a real + * DH prime but only the bit length is checked at intake */ + BigInteger weakP = BigInteger.ONE.shiftLeft(Dh.DH_MIN_SIZE - 2); + BigInteger g = BigInteger.valueOf(2); + BigInteger y = BigInteger.valueOf(3); + BigInteger x = BigInteger.valueOf(3); + + try { + kf.generatePublic(new DHPublicKeySpec(y, weakP, g)); + fail("weak DH prime should be rejected on public key import"); + } catch (InvalidKeySpecException e) { + /* expected */ + } + + try { + kf.generatePrivate(new DHPrivateKeySpec(x, weakP, g)); + fail("weak DH prime should be rejected on private key import"); + } catch (InvalidKeySpecException e) { + /* expected */ + } + + /* Build weak-prime DER via the raw key constructors (which do not + * validate) and confirm the X.509 and PKCS#8 DER import paths and + * translateKey all reject it */ + DHParameterSpec weakParams = new DHParameterSpec(weakP, g); + byte[] pubDer = new WolfCryptDHPublicKey(y, weakParams).getEncoded(); + byte[] privDer = new WolfCryptDHPrivateKey(x, weakParams).getEncoded(); + + try { + kf.generatePublic(new X509EncodedKeySpec(pubDer)); + fail("weak DH prime should be rejected on X.509 import"); + } catch (InvalidKeySpecException e) { + /* expected */ + } + + try { + kf.generatePrivate(new PKCS8EncodedKeySpec(privDer)); + fail("weak DH prime should be rejected on PKCS#8 import"); + } catch (InvalidKeySpecException e) { + /* expected */ + } + + try { + kf.translateKey(new WolfCryptDHPublicKey(y, weakParams)); + fail("weak DH prime should be rejected on translateKey"); + } catch (InvalidKeyException e) { + /* expected */ + } + } + @Test public void testInvalidKeySpecs() throws Exception { diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AesCtrTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AesCtrTest.java index 201fc2c6..62667365 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AesCtrTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AesCtrTest.java @@ -125,6 +125,21 @@ public void checkSetKeyParams() { /* test must throw */ } + /* wc_AesSetKey reads AES block size iv bytes, reject a short iv */ + try { + aesCtr.setKey(KEY_128, new byte[1]); + fail("undersized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + try { + aesCtr.setKey(KEY_128, new byte[AesCtr.BLOCK_SIZE + 1]); + fail("oversized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + aesCtr.setKey(KEY_128, IV_128); aesCtr.releaseNativeStruct(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java index 86654351..90ba9fd3 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AesOfbTest.java @@ -135,6 +135,21 @@ public void checkSetKeyParams() { /* test must throw */ } + /* wc_AesSetKey reads AES block size iv bytes, reject a short iv */ + try { + aesOfb.setKey(KEY_128, new byte[1]); + fail("undersized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + try { + aesOfb.setKey(KEY_128, new byte[AesOfb.BLOCK_SIZE + 1]); + fail("oversized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + aesOfb.setKey(KEY_128, IV_128); aesOfb.releaseNativeStruct(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/AesTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/AesTest.java index bf9d1a49..0a42c5dc 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/AesTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/AesTest.java @@ -98,6 +98,22 @@ public void checkSetKeyParams() { /* test must throw */ } + /* iv is optional, but a non-null iv must be the block size, + * wc_AesSetKey reads AES_BLOCK_SIZE bytes */ + try { + aes.setKey(KEY, new byte[1], Aes.ENCRYPT_MODE); + fail("undersized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + try { + aes.setKey(KEY, new byte[Aes.BLOCK_SIZE + 1], Aes.ENCRYPT_MODE); + fail("oversized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + aes.setKey(KEY, IV, Aes.ENCRYPT_MODE); aes.releaseNativeStruct(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/ChachaTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/ChachaTest.java index 5ffd5527..4dc5a735 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/ChachaTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/ChachaTest.java @@ -132,6 +132,22 @@ public void checkSetIv() { /* test must throw */ } + /* IV shorter than 12 bytes should be rejected */ + try { + chacha.setIV(new byte[4]); + fail("IV shorter than 12 bytes should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + /* An IV longer than 12 bytes is not a valid ChaCha nonce */ + try { + chacha.setIV(new byte[16]); + fail("IV longer than 12 bytes should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + chacha.setIV(IV); chacha.releaseNativeStruct(); } diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/Des3Test.java b/src/test/java/com/wolfssl/wolfcrypt/test/Des3Test.java index c7d58d17..8a91d094 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/Des3Test.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/Des3Test.java @@ -99,6 +99,48 @@ public void deprecatedConstructorThrows() { } } + @Test + public void checkSetKeyParams() { + byte[] key = Util.h2b( + "e61a38548694f1fd8cef251c518cc70bb613751c1ce52aa8"); + byte[] iv = Util.h2b("48a8ceb8551fd4ad"); + + Des3 des = new Des3(); + + /* wc_Des3_SetKey reads full 24 byte key, reject a short/long key */ + try { + des.setKey(new byte[1], iv, Des3.ENCRYPT_MODE); + fail("undersized key should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + try { + des.setKey(new byte[Des3.KEY_SIZE + 1], iv, Des3.ENCRYPT_MODE); + fail("oversized key should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + /* IV is optional, but non-null iv must be the block size */ + try { + des.setKey(key, new byte[1], Des3.ENCRYPT_MODE); + fail("undersized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + try { + des.setKey(key, new byte[Des3.BLOCK_SIZE + 1], Des3.ENCRYPT_MODE); + fail("oversized iv should be rejected."); + } catch (WolfCryptException e) { + /* test must throw */ + } + + des.setKey(key, iv, Des3.ENCRYPT_MODE); + des.releaseNativeStruct(); + } + @Test(expected=ShortBufferException.class) public void updateShouldMatchUsingByteByffer() throws ShortBufferException { String[] keys = new String[] {