From e93a23628d4c81bd88638568231d72dc6cb1a125 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 2 Nov 2026 15:08:57 -0700 Subject: [PATCH 1/6] gate wc_RNG_HealthTest* exports and tests on WOLFSSL_FIPS_PRIVATE_API --- tests/api/test_random.c | 19 +++++++++++-------- wolfcrypt/test/test.c | 27 +++++++++++++++------------ wolfssl/wolfcrypt/random.h | 18 ++++++++++++------ wolfssl/wolfcrypt/settings.h | 5 +++++ 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/tests/api/test_random.c b/tests/api/test_random.c index 18dceee9334..dde88062c83 100644 --- a/tests/api/test_random.c +++ b/tests/api/test_random.c @@ -701,7 +701,7 @@ int test_wc_RNG_TestSeed(void) int test_wc_RNG_HealthTest(void) { EXPECT_DECLS; -#if defined(HAVE_HASHDRBG) +#if defined(HAVE_HASHDRBG) && !defined(WOLFSSL_FIPS_PRIVATE_API) static const byte test1Seed[] = { 0xa6, 0x5a, 0xd0, 0xf3, 0x45, 0xdb, 0x4e, 0x0e, 0xff, 0xe8, 0x75, 0xc3, 0xa2, 0xe7, 0x1f, 0x42, @@ -892,8 +892,8 @@ int test_wc_RNG_HealthTest(void) int test_wc_RNG_HealthTest_SHA512(void) { EXPECT_DECLS; -#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_DRBG_SHA512) && \ - !defined(HAVE_SELFTEST) && \ +#if defined(HAVE_HASHDRBG) && !defined(WOLFSSL_FIPS_PRIVATE_API) && \ + defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) /* No-reseed test: drbgvectors_no_reseed/Hash_DRBG.rsp, [SHA-512], @@ -1027,7 +1027,8 @@ int test_wc_RNG_HealthTest_SHA512(void) test2SeedB, sizeof(test2SeedB), output, sizeof(output)), 0); ExpectBufEQ(test2Output, output, sizeof(output)); -#endif /* HAVE_HASHDRBG && WOLFSSL_DRBG_SHA512 && !HAVE_SELFTEST && FIPS v7+ */ +#endif /* HAVE_HASHDRBG && !WOLFSSL_FIPS_PRIVATE_API && WOLFSSL_DRBG_SHA512 + * && !HAVE_SELFTEST && FIPS v7+ */ return EXPECT_RESULT(); } @@ -1040,8 +1041,9 @@ int test_wc_RNG_HealthTest_SHA512(void) int test_wc_RNG_HealthTest_SHA256_Ext(void) { EXPECT_DECLS; -#if !defined(NO_SHA256) && defined(HAVE_HASHDRBG) && !defined(HAVE_SELFTEST) \ - && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) +#if !defined(NO_SHA256) && defined(HAVE_HASHDRBG) && \ + !defined(WOLFSSL_FIPS_PRIVATE_API) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) byte entropyA[48], entropyB[48], entropyC[48]; byte nonce[16], perso[16], addA[16], addB[16], addReseed[16]; byte output[WC_SHA256_DIGEST_SIZE * 4]; @@ -1148,8 +1150,9 @@ int test_wc_RNG_HealthTest_SHA256_Ext(void) int test_wc_RNG_HealthTest_SHA512_Ext(void) { EXPECT_DECLS; -#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_DRBG_SHA512) && \ - !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) +#if defined(HAVE_HASHDRBG) && !defined(WOLFSSL_FIPS_PRIVATE_API) && \ + defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) byte entropyA[32], entropyB[32], entropyC[32]; byte nonce[16], perso[16], addA[16], addB[16]; byte output[WC_SHA512_DIGEST_SIZE * 4]; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index caec95b8b92..193e27e0b34 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -30431,11 +30431,12 @@ static wc_test_ret_t rng_seed_test(void) WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) { /* SHA-256 Hash_DRBG known-answer vectors. wc_RNG_HealthTest is - * gated on !NO_SHA256 in random.h (it operates on DRBG_internal), - * so the data and calls are gated together to keep SHA-512-only - * builds clean of unused-variable warnings. SHA-512 vectors below - * have their own WOLFSSL_DRBG_SHA512 guard. */ -#ifndef NO_SHA256 + * gated on !NO_SHA256 in random.h (it operates on DRBG_internal) + * and hidden under WOLFSSL_FIPS_PRIVATE_API, so the data and calls + * are gated together to keep other builds clean of unused-variable + * warnings. SHA-512 vectors below have their own WOLFSSL_DRBG_SHA512 + * guard. */ +#if !defined(NO_SHA256) && !defined(WOLFSSL_FIPS_PRIVATE_API) WOLFSSL_SMALL_STACK_STATIC const byte test1Entropy[] = { 0xa6, 0x5a, 0xd0, 0xf3, 0x45, 0xdb, 0x4e, 0x0e, 0xff, 0xe8, 0x75, 0xc3, @@ -30484,7 +30485,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) 0x82, 0xc9, 0x55, 0xa8, 0x19, 0x69, 0xe0, 0x69, 0xfa, 0x8c, 0xe0, 0x07, 0xa1, 0x80, 0x18, 0x3a, 0x07, 0xdf, 0xae, 0x17 }; -#endif /* !NO_SHA256 - SHA-256 DRBG KAT vector data */ +#endif /* !NO_SHA256 && !WOLFSSL_FIPS_PRIVATE_API - SHA-256 DRBG KAT + * vector data */ byte output[32 * 4]; wc_test_ret_t ret; @@ -30500,7 +30502,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) if (ret != 0) return ret; -#ifndef NO_SHA256 +#if !defined(NO_SHA256) && !defined(WOLFSSL_FIPS_PRIVATE_API) ret = wc_RNG_HealthTest(0, test1Entropy, sizeof(test1Entropy), NULL, 0, output, sizeof(output)); if (ret != 0) @@ -30518,11 +30520,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) if (XMEMCMP(test2Output, output, sizeof(output)) != 0) return WC_TEST_RET_ENC_NC; #else - (void)ret; /* may not be reassigned before use under NO_SHA256 */ -#endif /* !NO_SHA256 - SHA-256 DRBG KAT calls */ + (void)ret; /* may not be reassigned before use without the KAT calls */ +#endif /* !NO_SHA256 && !WOLFSSL_FIPS_PRIVATE_API - SHA-256 DRBG KAT calls */ -#if defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ - (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) +#if defined(WOLFSSL_DRBG_SHA512) && !defined(WOLFSSL_FIPS_PRIVATE_API) && \ + !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) /* SHA-512 DRBG Health Tests using NIST CAVP test vectors. * Source: NIST CAVP drbgtestvectors.zip, Hash_DRBG.rsp, [SHA-512], * https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm- @@ -30854,7 +30856,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) return WC_TEST_RET_ENC_NC; } } -#endif /* WOLFSSL_DRBG_SHA512 && !HAVE_SELFTEST && FIPS v7+ */ +#endif /* WOLFSSL_DRBG_SHA512 && !WOLFSSL_FIPS_PRIVATE_API && + * !HAVE_SELFTEST && FIPS v7+ */ /* Basic RNG generate block test */ if ((ret = random_rng_test()) != 0) diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index d0f7ef788d7..9858439a8b7 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -954,6 +954,12 @@ WOLFSSL_API int wc_FreeRng_PreLocked(WC_RNG* rng); #define wc_FreeRng(rng) (void)NOT_COMPILED_IN #endif +#ifdef WOLFSSL_FIPS_PRIVATE_API + #define FIPS_LOCAL_API WOLFSSL_LOCAL +#else + #define FIPS_LOCAL_API WOLFSSL_API +#endif + #ifdef WC_RNG_SEED_CB WOLFSSL_API int wc_SetSeed_Cb(wc_RngSeed_Cb cb); #endif @@ -1013,11 +1019,11 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); /* SHA-256 Hash_DRBG health test entry points. SHA-512-only builds * (NO_SHA256 + WOLFSSL_DRBG_SHA512) use wc_RNG_HealthTest_SHA512_ex * declared below. */ - WOLFSSL_API int wc_RNG_HealthTest(int reseed, + FIPS_LOCAL_API int wc_RNG_HealthTest(int reseed, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, byte* output, word32 outputSz); - WOLFSSL_API int wc_RNG_HealthTest_ex(int reseed, + FIPS_LOCAL_API int wc_RNG_HealthTest_ex(int reseed, const byte* nonce, word32 nonceSz, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, @@ -1029,7 +1035,7 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); /* Extended SHA-256 Hash_DRBG health test per SP 800-90A. * Flexible output size, prediction resistance, personalization * strings, and additional input support. */ - WOLFSSL_API int wc_RNG_HealthTest_SHA256_ex( + FIPS_LOCAL_API int wc_RNG_HealthTest_SHA256_ex( int predResistance, const byte* nonce, word32 nonceSz, const byte* persoString, @@ -1051,11 +1057,11 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); #endif /* !NO_SHA256 && !HAVE_SELFTEST && FIPS v7+ */ #if defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) - WOLFSSL_API int wc_RNG_HealthTest_SHA512(int reseed, + FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512(int reseed, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, byte* output, word32 outputSz); - WOLFSSL_API int wc_RNG_HealthTest_SHA512_ex(int reseed, + FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512_ex(int reseed, const byte* nonce, word32 nonceSz, const byte* persoString, word32 persoStringSz, @@ -1073,7 +1079,7 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); * Generate gets NULL additional input. * predResistance=0: additionalReseed goes to Reseed, additionalA/B go * to Generate calls 1 and 2 respectively. */ - WOLFSSL_API int wc_RNG_HealthTest_SHA512_ex2( + FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512_ex2( int predResistance, const byte* nonce, word32 nonceSz, const byte* persoString, diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 4cf00c8f08f..5ce5aac302d 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4070,6 +4070,11 @@ #define NO_OLD_TLS #endif +/* Hide the FIPS module's self-test entry points (wc_RNG_HealthTest*) from + * applications; HAVE_FORCE_FIPS_FAILURE builds expose them for testing. */ +#if defined(HAVE_FIPS) && !defined(HAVE_FORCE_FIPS_FAILURE) + #define WOLFSSL_FIPS_PRIVATE_API +#endif /* Default AES minimum auth tag sz, allow user to override */ #ifndef WOLFSSL_MIN_AUTH_TAG_SZ From dcbe26cab467d991486c50869de3c3e139796b10 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 2 Nov 2026 15:36:16 -0700 Subject: [PATCH 2/6] gate RUST RNG health test wrappers on WOLFSSL_FIPS_PRIVATE_API --- wrapper/rust/wolfssl-wolfcrypt/build.rs | 5 +++++ wrapper/rust/wolfssl-wolfcrypt/headers.h | 5 +++++ wrapper/rust/wolfssl-wolfcrypt/src/random.rs | 8 ++++---- wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index dd9bc832ca5..f128aa5a12c 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -702,6 +702,11 @@ fn scan_cfg() -> Result<()> { /* random */ check_cfg(&binding, "wc_RNG_DRBG_Reseed", "random_hashdrbg"); + /* wc_RNG_HealthTest*() are declared regardless of + * WOLFSSL_FIPS_PRIVATE_API but hidden with it (FIPS builds), so also + * require the sentinel from headers.h to be absent. */ + check_cfg_if(&binding, "wc_RNG_HealthTest_ex", "random_health_test", + !has_symbol(&binding, "WOLFSSL_RUST_FIPS_PRIVATE_API")); check_cfg(&binding, "wc_InitRng", "random"); check_cfg(&binding, "wc_rng_new_ex", "rng_new_ex"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/headers.h b/wrapper/rust/wolfssl-wolfcrypt/headers.h index 4798d0294d4..dd7996a093e 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/headers.h +++ b/wrapper/rust/wolfssl-wolfcrypt/headers.h @@ -33,6 +33,11 @@ #ifdef HAVE_COMP_KEY #define WOLFSSL_RUST_HAVE_COMP_KEY 1 #endif +/* wc_RNG_HealthTest*() are always declared but hidden when + * WOLFSSL_FIPS_PRIVATE_API is set (FIPS builds). */ +#ifdef WOLFSSL_FIPS_PRIVATE_API +#define WOLFSSL_RUST_FIPS_PRIVATE_API 1 +#endif /* Sentinels for wolfSSL build configurations that the Rust wrapper does not * support. build.rs fails the build if any of these appear in the generated diff --git a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs index 9690746d25b..a01dccd27bb 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/src/random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/src/random.rs @@ -218,7 +218,7 @@ impl RNG { /// # Example /// /// ```rust - /// #![cfg(random_hashdrbg)] + /// #![cfg(random_health_test)] /// use wolfssl_wolfcrypt::random::RNG; /// let nonce = [99u8, 88, 77, 66]; /// let seed_a = [42u8, 33, 55, 88]; @@ -226,7 +226,7 @@ impl RNG { /// let mut output = [0u8; 128]; /// RNG::health_test(Some(&nonce), &seed_a, Some(&seed_b), &mut output).expect("Error with health_test()"); /// ``` - #[cfg(random_hashdrbg)] + #[cfg(random_health_test)] pub fn health_test(nonce: Option<&[u8]>, seed_a: &[u8], seed_b: Option<&[u8]>, output: &mut [u8]) -> Result<(), i32> { Self::health_test_ex(nonce, seed_a, seed_b, output, None, None) } @@ -251,7 +251,7 @@ impl RNG { /// # Example /// /// ```rust - /// #![cfg(random_hashdrbg)] + /// #![cfg(random_health_test)] /// use wolfssl_wolfcrypt::random::RNG; /// let nonce = [99u8, 88, 77, 66]; /// let seed_a = [42u8, 33, 55, 88]; @@ -259,7 +259,7 @@ impl RNG { /// let mut output = [0u8; 128]; /// RNG::health_test_ex(Some(&nonce), &seed_a, Some(&seed_b), &mut output, None, None).expect("Error with health_test_ex()"); /// ``` - #[cfg(random_hashdrbg)] + #[cfg(random_health_test)] pub fn health_test_ex(nonce: Option<&[u8]>, seed_a: &[u8], seed_b: Option<&[u8]>, output: &mut [u8], heap: Option<*mut core::ffi::c_void>, dev_id: Option) -> Result<(), i32> { let mut nonce_ptr = core::ptr::null(); let mut nonce_size = 0u32; diff --git a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs index 1829239a332..82ef36eecbf 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/tests/test_random.rs @@ -31,7 +31,7 @@ fn test_rng_new_with_nonce_ex_and_drop() { } #[test] -#[cfg(random_hashdrbg)] +#[cfg(random_health_test)] fn test_health_test() { let nonce = [99u8, 88, 77, 66]; let seed_a = [42u8, 33, 55, 88]; From 699aa5cf37a29e88b80e650b1ba8cee48cda982b Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 2 Nov 2026 16:00:09 -0700 Subject: [PATCH 3/6] drop the csharp wc_RNG_HealthTest_fips binding --- .../wolfCrypt-FIPS-Test/KnownAnswerTests.cs | 15 ++------ .../CSharp/wolfCrypt-FIPS-Test/RngTests.cs | 15 -------- wrapper/CSharp/wolfCrypt-FIPS/FipsRng.cs | 36 ------------------- wrapper/CSharp/wolfCrypt-FIPS/Native.cs | 6 ---- wrapper/CSharp/wolfCrypt-FIPS/README.md | 2 +- 5 files changed, 3 insertions(+), 71 deletions(-) diff --git a/wrapper/CSharp/wolfCrypt-FIPS-Test/KnownAnswerTests.cs b/wrapper/CSharp/wolfCrypt-FIPS-Test/KnownAnswerTests.cs index 8b6fba88e95..04327819390 100644 --- a/wrapper/CSharp/wolfCrypt-FIPS-Test/KnownAnswerTests.cs +++ b/wrapper/CSharp/wolfCrypt-FIPS-Test/KnownAnswerTests.cs @@ -29,8 +29,8 @@ namespace wolfSSL.CSharp.Fips.Test /* Embedded known answers. The module CASTs test the module; these test the * wrapper's marshalling, entry point and parameter mapping for each * symmetric service: published vectors (FIPS 180-4 / 202 "abc", - * RFC 4231, SP 800-38A F.1-F.5, RFC 4493, GCM test case 2, RFC 3610, - * CAVS Hash_DRBG) plus .NET cross-checks where .NET has the primitive. */ + * RFC 4231, SP 800-38A F.1-F.5, RFC 4493, GCM test case 2, RFC 3610) + * plus .NET cross-checks where .NET has the primitive. */ internal static class KnownAnswerTests { /* SP 800-38A F.1-F.5: AES-128 key and the first two plaintext blocks */ @@ -196,17 +196,6 @@ public static void Run() T.Bytes(nct, r2.Ciphertext, ".NET ciphertext"); T.Bytes(ntag, r2.Tag, ".NET tag"); }); - - T.Run("Hash_DRBG SHA-256 (CAVS, no reseed) via the health-test service", () => - { - byte[] entropyNonce = T.Hex("a65ad0f345db4e0effe875c3a2e71f42c7129d620ff5c119a9ef55f05185e0fb" + - "8581f9317517276e06e9607ddbcbcc2e"); - byte[] expected = T.Hex("d3e160c35b99f340b2628264d1751060e0045da383ff57a57d73a673d2b8d80d" + - "aaf6a6c35a91bb4579d73fd0c8fed111b0391306828adfed528f018121b3febd" + - "c343e797b87dbb63db1333ded9d1ece177cfa6b71fe8ab1da46624ed6415e51c" + - "cde2c7ca86e283990eeaeb91120415528b2295910281b02dd431f4c9f70427df"); - T.Bytes(expected, FipsRng.HealthTest(false, entropyNonce, null, expected.Length), "output"); - }); } } } diff --git a/wrapper/CSharp/wolfCrypt-FIPS-Test/RngTests.cs b/wrapper/CSharp/wolfCrypt-FIPS-Test/RngTests.cs index fb1a141196b..523e9a02a71 100644 --- a/wrapper/CSharp/wolfCrypt-FIPS-Test/RngTests.cs +++ b/wrapper/CSharp/wolfCrypt-FIPS-Test/RngTests.cs @@ -30,21 +30,6 @@ public static void Run() { T.Section("Hash_DRBG"); - T.Run("HealthTest argument contract (128-byte output, seedB with reseed)", () => - { - byte[] seed = new byte[48]; - foreach (int len in new[] { 64, 127, 129, -1 }) - { - bool threw = false; - try { FipsRng.HealthTest(false, seed, null, len); } catch (ArgumentOutOfRangeException) { threw = true; } - T.True(threw, "outputLen " + len + " accepted"); - } - bool threwB = false; - try { FipsRng.HealthTest(true, seed, null); } catch (ArgumentNullException) { threwB = true; } - T.True(threwB, "reseed without seedB accepted"); - T.Equal(128, FipsRng.HealthTest(false, seed, null).Length, "default length"); - }); - T.Run("instantiate, generate, output is not constant", () => { using var rng = new FipsRng(); diff --git a/wrapper/CSharp/wolfCrypt-FIPS/FipsRng.cs b/wrapper/CSharp/wolfCrypt-FIPS/FipsRng.cs index fccebd0d0f2..d4fc734500c 100644 --- a/wrapper/CSharp/wolfCrypt-FIPS/FipsRng.cs +++ b/wrapper/CSharp/wolfCrypt-FIPS/FipsRng.cs @@ -34,12 +34,6 @@ public sealed class FipsRng : FipsObject * read from the size helper. */ public static int MaxRequest => FipsObject.StructSize(FipsStructType.RngMaxBlockLen); -#if FIPS_TEST_HOOKS - /* Output length of the module's DRBG health test - * (RNG_HEALTH_TEST_CHECK_SIZE in random.c: 4 SHA-256 blocks). */ - internal const int HealthTestOutputSize = 128; -#endif - /* SP 800-90A 8.6.7: a caller nonce needs at least * security_strength / 2 = 128 bits. */ public const int MinNonceSize = 16; @@ -160,35 +154,5 @@ public byte[] Generate(int count) Generate(buf); return buf; } - -#if FIPS_TEST_HOOKS - /* DRBG KAT: seeds with seedA (reseeds with seedB), returns the second block for - * SP 800-90A vectors. Deterministic test output, never key material, so internal. */ - internal static byte[] HealthTest(bool reseed, byte[] seedA, byte[]? seedB, - int outputLen = HealthTestOutputSize) - { - if (seedA == null) - { - throw new ArgumentNullException(nameof(seedA)); - } - - if (reseed && seedB == null) - { - throw new ArgumentNullException(nameof(seedB), "reseed requires seedB"); - } - - if (outputLen != HealthTestOutputSize) - { - throw new ArgumentOutOfRangeException(nameof(outputLen), - "the module DRBG health test returns exactly " + HealthTestOutputSize + " bytes"); - } - - byte[] output = new byte[outputLen]; - WolfCryptFipsException.Check("wc_RNG_HealthTest_fips", - Native.wc_RNG_HealthTest_fips(reseed ? 1 : 0, seedA, (uint)seedA.Length, - seedB, seedB == null ? 0u : (uint)seedB.Length, output, (uint)outputLen)); - return output; - } -#endif } } diff --git a/wrapper/CSharp/wolfCrypt-FIPS/Native.cs b/wrapper/CSharp/wolfCrypt-FIPS/Native.cs index c7ca8258d78..1ecfd3743f3 100644 --- a/wrapper/CSharp/wolfCrypt-FIPS/Native.cs +++ b/wrapper/CSharp/wolfCrypt-FIPS/Native.cs @@ -110,12 +110,6 @@ internal static class Native [DllImport(WOLFSSL, EntryPoint = "wc_RNG_GenerateBlock_fips", CallingConvention = CallingConvention.Cdecl)] internal static extern int wc_RNG_GenerateBlock_fips(FipsHandle rng, byte[] output, uint sz); -#if FIPS_TEST_HOOKS - [DllImport(WOLFSSL, EntryPoint = "wc_RNG_HealthTest_fips", CallingConvention = CallingConvention.Cdecl)] - internal static extern int wc_RNG_HealthTest_fips(int reseed, byte[] seedA, uint seedASz, - byte[]? seedB, uint seedBSz, byte[] output, uint outputSz); -#endif - /* ---- SHA-1, SHA-2, SHA-3 (FIPS 180-4, FIPS 202) ---- */ [DllImport(WOLFSSL, EntryPoint = "wc_InitSha_fips", CallingConvention = CallingConvention.Cdecl)] internal static extern int wc_InitSha_fips(FipsHandle sha); diff --git a/wrapper/CSharp/wolfCrypt-FIPS/README.md b/wrapper/CSharp/wolfCrypt-FIPS/README.md index 11b28c3e1bf..12567c8985d 100644 --- a/wrapper/CSharp/wolfCrypt-FIPS/README.md +++ b/wrapper/CSharp/wolfCrypt-FIPS/README.md @@ -361,7 +361,7 @@ on any failure. Algorithm testing (ACVP) is done on the C module, not through the wrapper. The suite (a dependency-free console runner) tests the wrapper and covers: - embedded known answers (SHA-1/2/3, RFC 4231 HMAC, SP 800-38A AES, - RFC 4493 CMAC, GCM, RFC 3610 CCM, a CAVS Hash_DRBG vector); + RFC 4493 CMAC, GCM, RFC 3610 CCM); - independent checks against .NET (RSA, ECDSA, ECDH, HMAC, AES, GCM, TLS KDF references) and BigInteger references (DH, PKCS#1 v1.5); - forced-failure scenarios (FAILED, DRBG continuous test, and every DEGRADED From 5546d1f928746ab3fc370cd394e17355bb234133 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Tue, 3 Nov 2026 16:18:26 -0700 Subject: [PATCH 4/6] add WOLFSSL_EXPOSE_FIPS_PRIVATE_API opt-in to export FIPS-private funcs --- wolfssl/wolfcrypt/settings.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 5ce5aac302d..216b6f825f1 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4071,8 +4071,11 @@ #endif /* Hide the FIPS module's self-test entry points (wc_RNG_HealthTest*) from - * applications; HAVE_FORCE_FIPS_FAILURE builds expose them for testing. */ -#if defined(HAVE_FIPS) && !defined(HAVE_FORCE_FIPS_FAILURE) + * applications. HAVE_FORCE_FIPS_FAILURE builds expose them for testing, and + * WOLFSSL_EXPOSE_FIPS_PRIVATE_API exposes them in any FIPS build (e.g. for + * CAVP harnesses) without changing module behavior. */ +#if defined(HAVE_FIPS) && !defined(HAVE_FORCE_FIPS_FAILURE) && \ + !defined(WOLFSSL_EXPOSE_FIPS_PRIVATE_API) #define WOLFSSL_FIPS_PRIVATE_API #endif From e69c308ba1b4b31c9d4326b3ea25ae9aaaeaa76f Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Tue, 3 Nov 2026 22:41:14 -0700 Subject: [PATCH 5/6] Add WOLFSSL_EXPOSE_FIPS_PRIVATE_API to known_macro_extras --- .wolfssl_known_macro_extras | 1 + 1 file changed, 1 insertion(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 4c274d56235..0f51747c549 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -995,6 +995,7 @@ WOLFSSL_ESPWROOM32 WOLFSSL_EVP_PRINT WOLFSSL_EXPORT_INT WOLFSSL_EXPORT_SPC_SZ +WOLFSSL_EXPOSE_FIPS_PRIVATE_API WOLFSSL_FALCON_NO_NTT_DSP WOLFSSL_FALCON_SIGN_STATS WOLFSSL_FORCE_OCSP_NONCE_CHECK From c3f1e988c959a4f9ce5e4c04091818d2d4cdd017 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Wed, 4 Nov 2026 10:31:59 -0700 Subject: [PATCH 6/6] Reverse gate polarity and use WOLFSSL_FIPS_EXPORT_LOCAL_APIS instead --- .wolfssl_known_macro_extras | 2 +- tests/api/test_random.c | 10 ++++----- wolfcrypt/test/test.c | 28 ++++++++++++++---------- wolfssl/wolfcrypt/random.h | 18 +++++++-------- wolfssl/wolfcrypt/settings.h | 15 +++++++------ wrapper/rust/wolfssl-wolfcrypt/build.rs | 6 ++--- wrapper/rust/wolfssl-wolfcrypt/headers.h | 9 ++++---- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 0f51747c549..88b63baf6a0 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -995,9 +995,9 @@ WOLFSSL_ESPWROOM32 WOLFSSL_EVP_PRINT WOLFSSL_EXPORT_INT WOLFSSL_EXPORT_SPC_SZ -WOLFSSL_EXPOSE_FIPS_PRIVATE_API WOLFSSL_FALCON_NO_NTT_DSP WOLFSSL_FALCON_SIGN_STATS +WOLFSSL_FIPS_EXPORT_LOCAL_APIS WOLFSSL_FORCE_OCSP_NONCE_CHECK WOLFSSL_FRDM_K64 WOLFSSL_FRDM_K64_JENKINS diff --git a/tests/api/test_random.c b/tests/api/test_random.c index dde88062c83..94c72a7bf63 100644 --- a/tests/api/test_random.c +++ b/tests/api/test_random.c @@ -701,7 +701,7 @@ int test_wc_RNG_TestSeed(void) int test_wc_RNG_HealthTest(void) { EXPECT_DECLS; -#if defined(HAVE_HASHDRBG) && !defined(WOLFSSL_FIPS_PRIVATE_API) +#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS) static const byte test1Seed[] = { 0xa6, 0x5a, 0xd0, 0xf3, 0x45, 0xdb, 0x4e, 0x0e, 0xff, 0xe8, 0x75, 0xc3, 0xa2, 0xe7, 0x1f, 0x42, @@ -892,7 +892,7 @@ int test_wc_RNG_HealthTest(void) int test_wc_RNG_HealthTest_SHA512(void) { EXPECT_DECLS; -#if defined(HAVE_HASHDRBG) && !defined(WOLFSSL_FIPS_PRIVATE_API) && \ +#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS) && \ defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) @@ -1027,7 +1027,7 @@ int test_wc_RNG_HealthTest_SHA512(void) test2SeedB, sizeof(test2SeedB), output, sizeof(output)), 0); ExpectBufEQ(test2Output, output, sizeof(output)); -#endif /* HAVE_HASHDRBG && !WOLFSSL_FIPS_PRIVATE_API && WOLFSSL_DRBG_SHA512 +#endif /* HAVE_HASHDRBG && WOLFSSL_FIPS_EXPORT_LOCAL_APIS && WOLFSSL_DRBG_SHA512 * && !HAVE_SELFTEST && FIPS v7+ */ return EXPECT_RESULT(); } @@ -1042,7 +1042,7 @@ int test_wc_RNG_HealthTest_SHA256_Ext(void) { EXPECT_DECLS; #if !defined(NO_SHA256) && defined(HAVE_HASHDRBG) && \ - !defined(WOLFSSL_FIPS_PRIVATE_API) && !defined(HAVE_SELFTEST) && \ + defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) byte entropyA[48], entropyB[48], entropyC[48]; byte nonce[16], perso[16], addA[16], addB[16], addReseed[16]; @@ -1150,7 +1150,7 @@ int test_wc_RNG_HealthTest_SHA256_Ext(void) int test_wc_RNG_HealthTest_SHA512_Ext(void) { EXPECT_DECLS; -#if defined(HAVE_HASHDRBG) && !defined(WOLFSSL_FIPS_PRIVATE_API) && \ +#if defined(HAVE_HASHDRBG) && defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS) && \ defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) byte entropyA[32], entropyB[32], entropyC[32]; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 193e27e0b34..254107b408f 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -379,6 +379,12 @@ static const byte const_byte_array[] = "A+Gd\0\0\0"; #include #endif #endif +/* wc_RNG_HealthTest*() are WOLFSSL_LOCAL in FIPS builds unless + * WOLFSSL_FIPS_EXPORT_LOCAL_APIS is set, but a kernel module build links the + * test into the module and can always reach them. */ +#if defined(WOLFSSL_KERNEL_MODE) || defined(WOLFSSL_FIPS_EXPORT_LOCAL_APIS) + #define TEST_FIPS_PRIVATE_APIS +#endif #include #include #include @@ -30432,11 +30438,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) { /* SHA-256 Hash_DRBG known-answer vectors. wc_RNG_HealthTest is * gated on !NO_SHA256 in random.h (it operates on DRBG_internal) - * and hidden under WOLFSSL_FIPS_PRIVATE_API, so the data and calls - * are gated together to keep other builds clean of unused-variable - * warnings. SHA-512 vectors below have their own WOLFSSL_DRBG_SHA512 - * guard. */ -#if !defined(NO_SHA256) && !defined(WOLFSSL_FIPS_PRIVATE_API) + * and only reachable with TEST_FIPS_PRIVATE_APIS, so the data and + * calls are gated together to keep other builds clean of + * unused-variable warnings. SHA-512 vectors below have their own + * WOLFSSL_DRBG_SHA512 guard. */ +#if !defined(NO_SHA256) && defined(TEST_FIPS_PRIVATE_APIS) WOLFSSL_SMALL_STACK_STATIC const byte test1Entropy[] = { 0xa6, 0x5a, 0xd0, 0xf3, 0x45, 0xdb, 0x4e, 0x0e, 0xff, 0xe8, 0x75, 0xc3, @@ -30485,8 +30491,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) 0x82, 0xc9, 0x55, 0xa8, 0x19, 0x69, 0xe0, 0x69, 0xfa, 0x8c, 0xe0, 0x07, 0xa1, 0x80, 0x18, 0x3a, 0x07, 0xdf, 0xae, 0x17 }; -#endif /* !NO_SHA256 && !WOLFSSL_FIPS_PRIVATE_API - SHA-256 DRBG KAT - * vector data */ +#endif /* !NO_SHA256 && TEST_FIPS_PRIVATE_APIS - SHA-256 DRBG KAT vector + * data */ byte output[32 * 4]; wc_test_ret_t ret; @@ -30502,7 +30508,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) if (ret != 0) return ret; -#if !defined(NO_SHA256) && !defined(WOLFSSL_FIPS_PRIVATE_API) +#if !defined(NO_SHA256) && defined(TEST_FIPS_PRIVATE_APIS) ret = wc_RNG_HealthTest(0, test1Entropy, sizeof(test1Entropy), NULL, 0, output, sizeof(output)); if (ret != 0) @@ -30521,9 +30527,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) return WC_TEST_RET_ENC_NC; #else (void)ret; /* may not be reassigned before use without the KAT calls */ -#endif /* !NO_SHA256 && !WOLFSSL_FIPS_PRIVATE_API - SHA-256 DRBG KAT calls */ +#endif /* !NO_SHA256 && TEST_FIPS_PRIVATE_APIS - SHA-256 DRBG KAT calls */ -#if defined(WOLFSSL_DRBG_SHA512) && !defined(WOLFSSL_FIPS_PRIVATE_API) && \ +#if defined(WOLFSSL_DRBG_SHA512) && defined(TEST_FIPS_PRIVATE_APIS) && \ !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) /* SHA-512 DRBG Health Tests using NIST CAVP test vectors. * Source: NIST CAVP drbgtestvectors.zip, Hash_DRBG.rsp, [SHA-512], @@ -30856,7 +30862,7 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t random_test(void) return WC_TEST_RET_ENC_NC; } } -#endif /* WOLFSSL_DRBG_SHA512 && !WOLFSSL_FIPS_PRIVATE_API && +#endif /* WOLFSSL_DRBG_SHA512 && TEST_FIPS_PRIVATE_APIS && * !HAVE_SELFTEST && FIPS v7+ */ /* Basic RNG generate block test */ diff --git a/wolfssl/wolfcrypt/random.h b/wolfssl/wolfcrypt/random.h index 9858439a8b7..f5aa8f25609 100644 --- a/wolfssl/wolfcrypt/random.h +++ b/wolfssl/wolfcrypt/random.h @@ -954,10 +954,10 @@ WOLFSSL_API int wc_FreeRng_PreLocked(WC_RNG* rng); #define wc_FreeRng(rng) (void)NOT_COMPILED_IN #endif -#ifdef WOLFSSL_FIPS_PRIVATE_API - #define FIPS_LOCAL_API WOLFSSL_LOCAL +#ifdef WOLFSSL_FIPS_EXPORT_LOCAL_APIS + #define WC_FIPS_LOCAL_API WOLFSSL_API #else - #define FIPS_LOCAL_API WOLFSSL_API + #define WC_FIPS_LOCAL_API WOLFSSL_LOCAL #endif #ifdef WC_RNG_SEED_CB @@ -1019,11 +1019,11 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); /* SHA-256 Hash_DRBG health test entry points. SHA-512-only builds * (NO_SHA256 + WOLFSSL_DRBG_SHA512) use wc_RNG_HealthTest_SHA512_ex * declared below. */ - FIPS_LOCAL_API int wc_RNG_HealthTest(int reseed, + WC_FIPS_LOCAL_API int wc_RNG_HealthTest(int reseed, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, byte* output, word32 outputSz); - FIPS_LOCAL_API int wc_RNG_HealthTest_ex(int reseed, + WC_FIPS_LOCAL_API int wc_RNG_HealthTest_ex(int reseed, const byte* nonce, word32 nonceSz, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, @@ -1035,7 +1035,7 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); /* Extended SHA-256 Hash_DRBG health test per SP 800-90A. * Flexible output size, prediction resistance, personalization * strings, and additional input support. */ - FIPS_LOCAL_API int wc_RNG_HealthTest_SHA256_ex( + WC_FIPS_LOCAL_API int wc_RNG_HealthTest_SHA256_ex( int predResistance, const byte* nonce, word32 nonceSz, const byte* persoString, @@ -1057,11 +1057,11 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); #endif /* !NO_SHA256 && !HAVE_SELFTEST && FIPS v7+ */ #if defined(WOLFSSL_DRBG_SHA512) && !defined(HAVE_SELFTEST) && \ (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) - FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512(int reseed, + WC_FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512(int reseed, const byte* seedA, word32 seedASz, const byte* seedB, word32 seedBSz, byte* output, word32 outputSz); - FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512_ex(int reseed, + WC_FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512_ex(int reseed, const byte* nonce, word32 nonceSz, const byte* persoString, word32 persoStringSz, @@ -1079,7 +1079,7 @@ WOLFSSL_API int wc_RNG_GetFlags(const WC_RNG* rng, word32 *flags); * Generate gets NULL additional input. * predResistance=0: additionalReseed goes to Reseed, additionalA/B go * to Generate calls 1 and 2 respectively. */ - FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512_ex2( + WC_FIPS_LOCAL_API int wc_RNG_HealthTest_SHA512_ex2( int predResistance, const byte* nonce, word32 nonceSz, const byte* persoString, diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 216b6f825f1..c3f566a9661 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -4070,13 +4070,14 @@ #define NO_OLD_TLS #endif -/* Hide the FIPS module's self-test entry points (wc_RNG_HealthTest*) from - * applications. HAVE_FORCE_FIPS_FAILURE builds expose them for testing, and - * WOLFSSL_EXPOSE_FIPS_PRIVATE_API exposes them in any FIPS build (e.g. for - * CAVP harnesses) without changing module behavior. */ -#if defined(HAVE_FIPS) && !defined(HAVE_FORCE_FIPS_FAILURE) && \ - !defined(WOLFSSL_EXPOSE_FIPS_PRIVATE_API) - #define WOLFSSL_FIPS_PRIVATE_API +/* The FIPS module's self-test entry points (wc_RNG_HealthTest*) are internal + * to the module and hidden from applications. Non-FIPS builds and + * HAVE_FORCE_FIPS_FAILURE builds export them; define + * WOLFSSL_FIPS_EXPORT_LOCAL_APIS to export them from any other FIPS build + * (e.g. for CAVP harnesses) without changing module behavior. */ +#if !defined(HAVE_FIPS) || defined(HAVE_FORCE_FIPS_FAILURE) + #undef WOLFSSL_FIPS_EXPORT_LOCAL_APIS + #define WOLFSSL_FIPS_EXPORT_LOCAL_APIS #endif /* Default AES minimum auth tag sz, allow user to override */ diff --git a/wrapper/rust/wolfssl-wolfcrypt/build.rs b/wrapper/rust/wolfssl-wolfcrypt/build.rs index f128aa5a12c..971f32f369d 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/build.rs +++ b/wrapper/rust/wolfssl-wolfcrypt/build.rs @@ -703,10 +703,10 @@ fn scan_cfg() -> Result<()> { /* random */ check_cfg(&binding, "wc_RNG_DRBG_Reseed", "random_hashdrbg"); /* wc_RNG_HealthTest*() are declared regardless of - * WOLFSSL_FIPS_PRIVATE_API but hidden with it (FIPS builds), so also - * require the sentinel from headers.h to be absent. */ + * WOLFSSL_FIPS_EXPORT_LOCAL_APIS but only exported with it, so also + * require the sentinel from headers.h to be present. */ check_cfg_if(&binding, "wc_RNG_HealthTest_ex", "random_health_test", - !has_symbol(&binding, "WOLFSSL_RUST_FIPS_PRIVATE_API")); + has_symbol(&binding, "WOLFSSL_RUST_FIPS_EXPORT_LOCAL_APIS")); check_cfg(&binding, "wc_InitRng", "random"); check_cfg(&binding, "wc_rng_new_ex", "rng_new_ex"); diff --git a/wrapper/rust/wolfssl-wolfcrypt/headers.h b/wrapper/rust/wolfssl-wolfcrypt/headers.h index dd7996a093e..5dc136c4567 100644 --- a/wrapper/rust/wolfssl-wolfcrypt/headers.h +++ b/wrapper/rust/wolfssl-wolfcrypt/headers.h @@ -33,10 +33,11 @@ #ifdef HAVE_COMP_KEY #define WOLFSSL_RUST_HAVE_COMP_KEY 1 #endif -/* wc_RNG_HealthTest*() are always declared but hidden when - * WOLFSSL_FIPS_PRIVATE_API is set (FIPS builds). */ -#ifdef WOLFSSL_FIPS_PRIVATE_API -#define WOLFSSL_RUST_FIPS_PRIVATE_API 1 +/* wc_RNG_HealthTest*() are always declared but only exported when + * WOLFSSL_FIPS_EXPORT_LOCAL_APIS is set (non-FIPS and HAVE_FORCE_FIPS_FAILURE + * builds, or FIPS builds that define it explicitly). */ +#ifdef WOLFSSL_FIPS_EXPORT_LOCAL_APIS +#define WOLFSSL_RUST_FIPS_EXPORT_LOCAL_APIS 1 #endif /* Sentinels for wolfSSL build configurations that the Rust wrapper does not