diff --git a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h index f1d5e84a..6c81aaea 100644 --- a/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h +++ b/jni/include/com_wolfssl_wolfcrypt_FeatureDetect.h @@ -591,6 +591,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_LmsEnabled JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_XmssEnabled (JNIEnv *, jclass); +/* + * Class: com_wolfssl_wolfcrypt_FeatureDetect + * Method: RngHealthTestEnabled + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RngHealthTestEnabled + (JNIEnv *, jclass); + #ifdef __cplusplus } #endif diff --git a/jni/jni_feature_detect.c b/jni/jni_feature_detect.c index b5afa242..ac1190c5 100644 --- a/jni/jni_feature_detect.c +++ b/jni/jni_feature_detect.c @@ -28,6 +28,7 @@ #include #include #include +#include #include JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_Md5Enabled @@ -904,3 +905,16 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_XmssEnabled return JNI_FALSE; #endif } + +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_FeatureDetect_RngHealthTestEnabled + (JNIEnv* env, jclass jcl) +{ + (void)env; + (void)jcl; +#if defined(HAVE_FIPS) && \ + (!defined(WC_FIPS_LOCAL_API) || defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS)) + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} diff --git a/jni/jni_fips.c b/jni/jni_fips.c index b9ef29fb..5d0a165b 100644 --- a/jni/jni_fips.c +++ b/jni/jni_fips.c @@ -72,6 +72,17 @@ /* #define WOLFCRYPT_JNI_DEBUG_ON */ #include +/* Native wolfSSL marks the wc_RNG_HealthTest*() family WOLFSSL_LOCAL in FIPS + * builds so applications cannot run the DRBG health test with arbitrary seeds + * (FIPS 140-3 IG 9.3.A). They are only exported from FIPS builds that define + * WOLFSSL_FIPS_EXPORT_LOCAL_APIS. wolfSSL versions with this behavior define + * WC_FIPS_LOCAL_API in random.h, older versions always export the health + * test. */ +#if defined(HAVE_FIPS) && \ + (!defined(WC_FIPS_LOCAL_API) || defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS)) + #define WOLFCRYPT_JNI_FIPS_RNG_HEALTHTEST +#endif + #ifdef HAVE_FIPS extern JavaVM* g_vm; static jobject g_errCb; @@ -2211,7 +2222,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Fips_wc_1RNG_1HealthTest_1fips { jint ret = NOT_COMPILED_IN; -#if defined(HAVE_FIPS) +#if defined(WOLFCRYPT_JNI_FIPS_RNG_HEALTHTEST) const byte* entropyA = getDirectBufferAddress(env, entropyA_object); const byte* entropyB = getDirectBufferAddress(env, entropyB_object); @@ -2251,7 +2262,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_Fips_wc_1RNG_1HealthTest_1fips { jint ret = NOT_COMPILED_IN; -#if defined(HAVE_FIPS) +#if defined(WOLFCRYPT_JNI_FIPS_RNG_HEALTHTEST) jboolean entropyAIsCopy = JNI_FALSE; jboolean entropyBIsCopy = JNI_FALSE; diff --git a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java index a3e624a3..3ed9c2c1 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java +++ b/src/main/java/com/wolfssl/wolfcrypt/FeatureDetect.java @@ -592,6 +592,18 @@ public class FeatureDetect { */ public static native boolean XmssEnabled(); + /** + * Tests if the wolfCrypt FIPS Hash_DRBG health test is available through + * Fips.RNG_HealthTest_fips(). + * + * Always false in non-FIPS builds. Native wolfSSL FIPS builds do not + * export the DRBG health test unless compiled with + * WOLFSSL_FIPS_EXPORT_LOCAL_APIS. + * + * @return true if Fips.RNG_HealthTest_fips() is available, otherwise false. + */ + public static native boolean RngHealthTestEnabled(); + /** * Loads JNI library. * diff --git a/src/main/java/com/wolfssl/wolfcrypt/Fips.java b/src/main/java/com/wolfssl/wolfcrypt/Fips.java index 9244845c..db8be6d5 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/Fips.java +++ b/src/main/java/com/wolfssl/wolfcrypt/Fips.java @@ -1021,6 +1021,10 @@ public static int RNG_GenerateBlock_fips(Rng rng, byte[] buf, long bufSz) { * bytes entropyBSz and then tests the RNG against the expected output of * size in bytes outputSz. * + * Native wolfSSL FIPS builds do not export the DRBG health test unless + * compiled with WOLFSSL_FIPS_EXPORT_LOCAL_APIS (FIPS 140-3 IG 9.3.A). Use + * FeatureDetect.RngHealthTestEnabled() to check availability. + * * @param reseed the reseed flag. * @param entropyA the entropyA buffer. * @param entropyASz the entropyA length. @@ -1029,7 +1033,8 @@ public static int RNG_GenerateBlock_fips(Rng rng, byte[] buf, long bufSz) { * @param output the output buffer. * @param outputSz the output length. * - * @return 0 on success, {@literal <} 0 on error. + * @return 0 on success, {@literal <} 0 on error, NOT_COMPILED_IN if the + * native DRBG health test is not available. */ public static int RNG_HealthTest_fips(int reseed, ByteBuffer entropyA, long entropyASz, ByteBuffer entropyB, long entropyBSz, @@ -1050,6 +1055,10 @@ public static int RNG_HealthTest_fips(int reseed, ByteBuffer entropyA, * bytes entropyBSz and then tests the RNG against the expected output of * size in bytes outputSz. * + * Native wolfSSL FIPS builds do not export the DRBG health test unless + * compiled with WOLFSSL_FIPS_EXPORT_LOCAL_APIS (FIPS 140-3 IG 9.3.A). Use + * FeatureDetect.RngHealthTestEnabled() to check availability. + * * @param reseed the reseed flag. * @param entropyA the entropyA buffer. * @param entropyASz the entropyA length. @@ -1058,7 +1067,8 @@ public static int RNG_HealthTest_fips(int reseed, ByteBuffer entropyA, * @param output the output buffer. * @param outputSz the output length. * - * @return 0 on success, {@literal <} 0 on error. + * @return 0 on success, {@literal <} 0 on error, NOT_COMPILED_IN if the + * native DRBG health test is not available. */ public static int RNG_HealthTest_fips(int reseed, byte[] entropyA, long entropyASz, byte[] entropyB, long entropyBSz, byte[] output, diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/RngTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/RngTest.java index b578c4c7..ca3708d8 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/RngTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/RngTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.*; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.Rule; @@ -39,6 +40,9 @@ import java.util.concurrent.LinkedBlockingQueue; import com.wolfssl.wolfcrypt.Rng; +import com.wolfssl.wolfcrypt.Fips; +import com.wolfssl.wolfcrypt.FeatureDetect; +import com.wolfssl.wolfcrypt.WolfCryptError; import com.wolfssl.wolfcrypt.NativeStruct; import com.wolfssl.wolfcrypt.WolfCryptException; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; @@ -58,6 +62,18 @@ public void constructorShouldNotInitializeNativeStruct() { assertEquals(NativeStruct.NULL, new Rng().getNativeStruct()); } + @Test + public void rngHealthTestNotDetectedInNonFipsBuild() { + /* Fips.RNG_HealthTest_fips() only exists in FIPS builds, so + * FeatureDetect must not report it as available otherwise */ + Assume.assumeFalse("FIPS enabled", Fips.enabled); + + assertFalse(FeatureDetect.RngHealthTestEnabled()); + assertEquals(WolfCryptError.NOT_COMPILED_IN.getCode(), + Fips.RNG_HealthTest_fips(0, new byte[48], 48, null, 0, + new byte[128], 128)); + } + @Test public void testInitFree() { Rng wcRng = new Rng(); diff --git a/src/test/java/com/wolfssl/wolfcrypt/test/fips/RngFipsTest.java b/src/test/java/com/wolfssl/wolfcrypt/test/fips/RngFipsTest.java index 470142b4..3ddaf560 100644 --- a/src/test/java/com/wolfssl/wolfcrypt/test/fips/RngFipsTest.java +++ b/src/test/java/com/wolfssl/wolfcrypt/test/fips/RngFipsTest.java @@ -26,6 +26,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.junit.Rule; @@ -37,6 +38,7 @@ import com.wolfssl.wolfcrypt.WolfCrypt; import com.wolfssl.wolfcrypt.WolfCryptError; import com.wolfssl.wolfcrypt.Fips; +import com.wolfssl.wolfcrypt.FeatureDetect; import com.wolfssl.wolfcrypt.test.Util; import com.wolfssl.wolfcrypt.test.TimedTestWatcher; @@ -54,6 +56,11 @@ public static void setupClass() { System.out.println("JNI FIPS RNG Tests"); } + private static void assumeRngHealthTestEnabled() { + Assume.assumeTrue("RNG health test not exported by native wolfSSL", + FeatureDetect.RngHealthTestEnabled()); + } + @Test public void initShouldReturnZero() { Rng rng = new Rng(); @@ -123,6 +130,8 @@ public void GenerateBlockShouldRejectBadBufSzUsingByteArray() { @Test public void HeathTestShouldReturnZeroUsingByteBuffer() { + assumeRngHealthTestEnabled(); + String[] inputA = new String[] { "1734476fa80d0c74ee6c77c760223ccbbbebaac8d4f59fe3a1aba7be35a7d8ef5acd22db2cbbac88009c04776f99da06", "5eb3049f0b85b3a3a2bfd775ee10b68c4ff232e9695601f7f1b175643960f93f86418d771c91ca3b718aad20160c6bc2", @@ -155,6 +164,8 @@ public void HeathTestShouldReturnZeroUsingByteBuffer() { @Test public void HeathTestShouldReturnZeroUsingByteArray() { + assumeRngHealthTestEnabled(); + String[] inputA = new String[] { "1734476fa80d0c74ee6c77c760223ccbbbebaac8d4f59fe3a1aba7be35a7d8ef5acd22db2cbbac88009c04776f99da06", "5eb3049f0b85b3a3a2bfd775ee10b68c4ff232e9695601f7f1b175643960f93f86418d771c91ca3b718aad20160c6bc2", @@ -187,6 +198,8 @@ public void HeathTestShouldReturnZeroUsingByteArray() { @Test public void healthTestShouldRejectUndersizedOutputUsingByteArray() { + assumeRngHealthTestEnabled(); + /* outputSz claims 256 bytes but the array is only 1 byte */ assertEquals(WolfCryptError.BAD_FUNC_ARG.getCode(), Fips.RNG_HealthTest_fips(0, new byte[48], 48, null, 0, @@ -195,6 +208,8 @@ public void healthTestShouldRejectUndersizedOutputUsingByteArray() { @Test public void healthTestShouldRejectUndersizedEntropyBUsingByteArray() { + assumeRngHealthTestEnabled(); + /* with reseed set, entropyB must hold entropyBSz bytes */ assertEquals(WolfCryptError.BAD_FUNC_ARG.getCode(), Fips.RNG_HealthTest_fips(1, new byte[48], 48, new byte[1], 32, @@ -203,6 +218,8 @@ public void healthTestShouldRejectUndersizedEntropyBUsingByteArray() { @Test public void healthTestShouldRejectUndersizedOutputUsingByteBuffer() { + assumeRngHealthTestEnabled(); + ByteBuffer entropyA = ByteBuffer.allocateDirect(48); ByteBuffer output = ByteBuffer.allocateDirect(1); @@ -210,6 +227,20 @@ public void healthTestShouldRejectUndersizedOutputUsingByteBuffer() { Fips.RNG_HealthTest_fips(0, entropyA, 48, null, 0, output, 256)); } + @Test + public void healthTestShouldReturnNotCompiledInWhenUnavailable() { + Assume.assumeFalse("RNG health test exported by native wolfSSL", + FeatureDetect.RngHealthTestEnabled()); + + /* must return NOT_COMPILED_IN, not call a missing native symbol */ + assertEquals(WolfCryptError.NOT_COMPILED_IN.getCode(), + Fips.RNG_HealthTest_fips(0, new byte[48], 48, null, 0, + new byte[128], 128)); + assertEquals(WolfCryptError.NOT_COMPILED_IN.getCode(), + Fips.RNG_HealthTest_fips(0, ByteBuffer.allocateDirect(48), 48, + null, 0, ByteBuffer.allocateDirect(128), 128)); + } + @Test public void generateBlockShouldRejectUndersizedBufferUsingByteArray() { Rng rng = new Rng();