From 5a75925fefdc96a1193217c3ff6530dc97f4af4c Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Thu, 13 Aug 2026 16:20:09 -0600 Subject: [PATCH] F-6437: zeroize secret ML-KEM input copies before JNI release --- jni/include/wolfcrypt_jni_NativeStruct.h | 3 +++ jni/jni_mlkem.c | 27 ++++++++++++--------- jni/jni_native_struct.c | 31 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/jni/include/wolfcrypt_jni_NativeStruct.h b/jni/include/wolfcrypt_jni_NativeStruct.h index 04d4b319..99979893 100644 --- a/jni/include/wolfcrypt_jni_NativeStruct.h +++ b/jni/include/wolfcrypt_jni_NativeStruct.h @@ -40,6 +40,9 @@ byte* getByteArrayIsCopy(JNIEnv* env, jbyteArray array, jboolean* isCopy); void zeroizeByteArrayCopy(byte* buf, word32 sz, jboolean isCopy); void releaseByteArray(JNIEnv* env, jbyteArray array, byte* elements, jint abort); word32 getByteArrayLength(JNIEnv* env, jbyteArray array); +byte* getSecretByteArray(JNIEnv* env, jbyteArray array, jboolean* isCopy); +void releaseSecretByteArray(JNIEnv* env, jbyteArray array, byte* elements, + word32 len, jboolean isCopy); void initializeNativeStruct(JNIEnv* env, jobject obj); #ifdef __cplusplus diff --git a/jni/jni_mlkem.c b/jni/jni_mlkem.c index 2f4c93c7..b7ee03c0 100644 --- a/jni/jni_mlkem.c +++ b/jni/jni_mlkem.c @@ -200,16 +200,17 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1make_1key_1fr MlKemKey* key = NULL; byte* seed = NULL; word32 seedSz = 0; + jboolean seedIsCopy = JNI_FALSE; key = (MlKemKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { return; } - seed = getByteArray(env, seed_object); + seed = getSecretByteArray(env, seed_object, &seedIsCopy); seedSz = getByteArrayLength(env, seed_object); - /* getByteArray() can return NULL with a pending exception */ + /* getSecretByteArray() can return NULL with a pending exception */ if (seed_object != NULL && seed == NULL) { return; } @@ -228,7 +229,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1make_1key_1fr LogStr("wc_MlKemKey_MakeKeyWithRandom(key=%p, seedSz=%u) = %d\n", key, (word32)seedSz, ret); - releaseByteArray(env, seed_object, seed, JNI_ABORT); + releaseSecretByteArray(env, seed_object, seed, seedSz, seedIsCopy); #else (void)this; (void)seed_object; @@ -324,6 +325,7 @@ Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsulate_1with_1random( MlKemKey* key = NULL; byte* rand = NULL; word32 randSz = 0; + jboolean randIsCopy = JNI_FALSE; byte* output = NULL; word32 ctSz = 0; word32 ssSz = 0; @@ -334,16 +336,16 @@ Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsulate_1with_1random( return NULL; } - rand = getByteArray(env, rand_object); + rand = getSecretByteArray(env, rand_object, &randIsCopy); randSz = getByteArrayLength(env, rand_object); - /* getByteArray() can return NULL with a pending exception */ + /* getSecretByteArray() can return NULL with a pending exception */ if (rand_object != NULL && rand == NULL) { return NULL; } if (key == NULL || rand == NULL) { - releaseByteArray(env, rand_object, rand, JNI_ABORT); + releaseSecretByteArray(env, rand_object, rand, randSz, randIsCopy); throwWolfCryptExceptionFromError(env, BAD_FUNC_ARG); return NULL; } @@ -353,7 +355,7 @@ Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsulate_1with_1random( ret = wc_MlKemKey_SharedSecretSize(key, &ssSz); } if (ret != 0) { - releaseByteArray(env, rand_object, rand, JNI_ABORT); + releaseSecretByteArray(env, rand_object, rand, randSz, randIsCopy); throwWolfCryptExceptionFromError(env, ret); return NULL; } @@ -361,7 +363,7 @@ Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsulate_1with_1random( totalSz = ctSz + ssSz; output = (byte*)XMALLOC(totalSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (output == NULL) { - releaseByteArray(env, rand_object, rand, JNI_ABORT); + releaseSecretByteArray(env, rand_object, rand, randSz, randIsCopy); throwOutOfMemoryException(env, "Failed to allocate encapsulation"); return NULL; } @@ -390,7 +392,7 @@ Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1encapsulate_1with_1random( MLKEM_FORCE_ZERO(output, totalSz); XFREE(output, NULL, DYNAMIC_TYPE_TMP_BUFFER); - releaseByteArray(env, rand_object, rand, JNI_ABORT); + releaseSecretByteArray(env, rand_object, rand, randSz, randIsCopy); #else (void)this; (void)rand_object; @@ -659,16 +661,17 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1import_1priva MlKemKey* key = NULL; byte* priv = NULL; word32 privSz = 0; + jboolean privIsCopy = JNI_FALSE; key = (MlKemKey*) getNativeStruct(env, this); if ((*env)->ExceptionOccurred(env)) { return; } - priv = getByteArray(env, priv_object); + priv = getSecretByteArray(env, priv_object, &privIsCopy); privSz = getByteArrayLength(env, priv_object); - /* getByteArray() can return NULL with a pending exception */ + /* getSecretByteArray() can return NULL with a pending exception */ if (priv_object != NULL && priv == NULL) { return; } @@ -687,7 +690,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_MlKem_wc_1mlkem_1import_1priva LogStr("wc_MlKemKey_DecodePrivateKey(key=%p, privSz=%u) = %d\n", key, (word32)privSz, ret); - releaseByteArray(env, priv_object, priv, JNI_ABORT); + releaseSecretByteArray(env, priv_object, priv, privSz, privIsCopy); #else (void)this; (void)priv_object; diff --git a/jni/jni_native_struct.c b/jni/jni_native_struct.c index 6eacfe22..c561802b 100644 --- a/jni/jni_native_struct.c +++ b/jni/jni_native_struct.c @@ -247,6 +247,37 @@ word32 getByteArrayLength(JNIEnv* env, jbyteArray array) return array ? (*env)->GetArrayLength(env, array) : 0; } +/* Zeroize sensitive buffer 'buf' of size 'sz' bytes, using wc_ForceZero + * when available, otherwise XMEMSET. */ +#if (LIBWOLFSSL_VERSION_HEX >= 0x05008004) && !defined(WOLFSSL_NO_FORCE_ZERO) + #define WC_JNI_FORCE_ZERO(buf, sz) wc_ForceZero((buf), (sz)) +#else + #define WC_JNI_FORCE_ZERO(buf, sz) XMEMSET((buf), 0, (sz)) +#endif + +/* Pin secret input array elements, capturing if JVM returned a copy */ +byte* getSecretByteArray(JNIEnv* env, jbyteArray array, jboolean* isCopy) +{ + *isCopy = JNI_FALSE; + + if (array == NULL) { + return NULL; + } + + return (byte*)(*env)->GetByteArrayElements(env, array, isCopy); +} + +/* Zero a pinned copy of secret input, then release without copy back. */ +void releaseSecretByteArray(JNIEnv* env, jbyteArray array, + byte* elements, word32 len, jboolean isCopy) +{ + if (elements != NULL && isCopy == JNI_TRUE) { + WC_JNI_FORCE_ZERO(elements, len); + } + + releaseByteArray(env, array, elements, JNI_ABORT); +} + void initializeNativeStruct(JNIEnv* env, jobject obj) { jclass class;