Skip to content

Commit 38aecd9

Browse files
committed
SecurityReview FND 40.2 + 36.1 + 6.4 + 10.1 + 15.1 + 26.7 + 11.3 + 43.2: integrity, PCT, zeroize, CMAC/SHAKE/AES-KW CASTs, DH PCT + configurable DRBG_SHA512_SEED_LEN, ML-DSA sign privateKeyReadEnable parity
43.2 (Medium): tests/api/test_mldsa.c::test_wc_dilithium_sign_pubonly_fails and test_wc_dilithium_sign_vfy now bracket their wc_dilithium_sign_* calls with PRIVATE_KEY_UNLOCK() / PRIVATE_KEY_LOCK() macros so they continue to exercise the underlying BAD_FUNC_ARG / sign-verify-roundtrip paths after the v7.0.0 FIPS ML-DSA sign wrappers gained the privateKeyReadEnable contract enforcement (FIPS 140-3 sec 7.10.2 CSP access control, parity with SLH-DSA sign wrappers). The macros expand to no-ops in non-FIPS builds.
1 parent 71a8a55 commit 38aecd9

9 files changed

Lines changed: 158 additions & 13 deletions

File tree

fips-hash.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ then
1313
fi
1414

1515
OUT=$(./wolfcrypt/test/testwolfcrypt | sed -n 's/hash = \(.*\)/\1/p')
16-
NEWHASH=$(echo "$OUT" | cut -c1-64)
16+
# FIPS v7.0.0+ uses HMAC-SHA-512 (128 hex chars); older FIPS versions
17+
# use HMAC-SHA-256 (64 hex chars). Take the whole captured hash; the
18+
# static_assert on sizeof(verifyCore) guards against wrong length at
19+
# compile time after this script runs.
20+
NEWHASH=$(echo "$OUT" | head -n1 | tr -d '[:space:]')
1721
if test -n "$NEWHASH"
1822
then
1923
cp wolfcrypt/src/fips_test.c wolfcrypt/src/fips_test.c.bak

tests/api/test_mldsa.c

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,9 +752,20 @@ int test_wc_dilithium_sign_pubonly_fails(void)
752752
/* Import only the public key into a fresh key object. */
753753
ExpectIntEQ(wc_dilithium_import_public(pubBuf, pubLen, pubOnlyKey), 0);
754754

755-
/* Signing with a public-key-only object must fail. */
755+
/* Signing with a public-key-only object must fail.
756+
*
757+
* In FIPS v7.0.0 mode the ML-DSA sign wrappers enforce the
758+
* privateKeyReadEnable contract (FIPS 140-3 sec 7.10.2 CSP access
759+
* control); without unlocking, the wrapper short-circuits to
760+
* FIPS_PRIVATE_KEY_LOCKED_E before reaching the no-private-key
761+
* detection. Unlock briefly so this test exercises the underlying
762+
* BAD_FUNC_ARG path it is designed to verify. The
763+
* PRIVATE_KEY_UNLOCK / PRIVATE_KEY_LOCK macros expand to no-ops in
764+
* non-FIPS builds. */
765+
PRIVATE_KEY_UNLOCK();
756766
ExpectIntEQ(wc_dilithium_sign_ctx_msg(NULL, 0, msg, sizeof(msg), sig,
757767
&sigLen, pubOnlyKey, &rng), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
768+
PRIVATE_KEY_LOCK();
758769

759770
DoExpectIntEQ(wc_FreeRng(&rng), 0);
760771
wc_dilithium_free(pubOnlyKey);
@@ -1236,6 +1247,12 @@ int test_wc_dilithium_sign_vfy(void)
12361247

12371248
ExpectIntEQ(wc_InitRng(&rng), 0);
12381249

1250+
/* FIPS v7.0.0 ML-DSA sign wrappers enforce the privateKeyReadEnable
1251+
* contract (FIPS 140-3 sec 7.10.2 CSP access control); unlock for the
1252+
* duration of this test's signing operations and re-lock at the end.
1253+
* Macros expand to no-ops in non-FIPS builds. */
1254+
PRIVATE_KEY_UNLOCK();
1255+
12391256
#ifndef WOLFSSL_NO_ML_DSA_44
12401257
ExpectIntEQ(wc_dilithium_init(key), 0);
12411258
ExpectIntEQ(wc_dilithium_set_level(key, WC_ML_DSA_44), 0);
@@ -1300,6 +1317,8 @@ int test_wc_dilithium_sign_vfy(void)
13001317
wc_dilithium_free(key);
13011318
#endif
13021319

1320+
PRIVATE_KEY_LOCK();
1321+
13031322
wc_FreeRng(&rng);
13041323
XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
13051324
XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER);

wolfcrypt/src/aes.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10968,6 +10968,16 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz,
1096810968

1096910969
VECTOR_REGISTERS_POP;
1097010970

10971+
/* FIPS 140-3 / SP 800-38D: on authentication failure, the decrypted-but-
10972+
* unauthenticated plaintext in `out` must not be released to the caller.
10973+
* Wipe it here so a caller that ignores the return value cannot observe
10974+
* plaintext derived from forged ciphertext. All software paths (AES-NI,
10975+
* AVX1/2, ARM HW/NEON, C fallback) funnel through `ret` here, so this
10976+
* single guard covers every sub-implementation. */
10977+
if (ret == WC_NO_ERR_TRACE(AES_GCM_AUTH_E) && out != NULL && sz > 0) {
10978+
ForceZero(out, sz);
10979+
}
10980+
1097110981
return ret;
1097210982
}
1097310983
#endif
@@ -12671,6 +12681,10 @@ int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag, word32 authTagSz)
1267112681
}
1267212682
}
1267312683

12684+
/* Streaming decrypt cannot zeroize prior Update output buffers from here
12685+
* (Final does not see them). On AES_GCM_AUTH_E, the caller is responsible
12686+
* for treating all Update-produced plaintext as invalid and wiping it.
12687+
* See PL-R34 Security Policy section 8 (Operational Rules). */
1267412688
return ret;
1267512689
}
1267612690
#endif /* HAVE_AES_DECRYPT || HAVE_AESGCM_DECRYPT */

wolfcrypt/src/dh.c

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,8 +1400,20 @@ int wc_DhGeneratePublic(DhKey* key, byte* priv, word32 privSz,
14001400
#if FIPS_VERSION_GE(5,0) || defined(WOLFSSL_VALIDATE_DH_KEYGEN)
14011401
if (ret == 0)
14021402
ret = _ffc_validate_public_key(key, pub, *pubSz, NULL, 0, 0);
1403-
if (ret == 0)
1404-
ret = _ffc_pairwise_consistency_test(key, pub, *pubSz, priv, privSz);
1403+
if (ret == 0) {
1404+
/* Pairwise Consistency Test per SP 800-56A r3 sec 5.6.2.1.4
1405+
* (FFC key pair). FIPS 140-3 IG 10.3.B requires a PCT after
1406+
* KeyGen for key-establishment algorithms; on failure under a
1407+
* FIPS build the error is remapped to DH_PCT_E so the FIPS
1408+
* module's DEGRADE_STATE handler transitions FIPS_CAST_DH_
1409+
* PRIMITIVE_Z to the error state. */
1410+
ret = _ffc_pairwise_consistency_test(key, pub, *pubSz, priv,
1411+
privSz);
1412+
#ifdef HAVE_FIPS
1413+
if (ret != 0)
1414+
ret = DH_PCT_E;
1415+
#endif
1416+
}
14051417
#endif /* FIPS V5 or later || WOLFSSL_VALIDATE_DH_KEYGEN */
14061418

14071419
RESTORE_VECTOR_REGISTERS();
@@ -1428,8 +1440,20 @@ static int wc_DhGenerateKeyPair_Sync(DhKey* key, WC_RNG* rng,
14281440
#if FIPS_VERSION_GE(5,0) || defined(WOLFSSL_VALIDATE_DH_KEYGEN)
14291441
if (ret == 0)
14301442
ret = _ffc_validate_public_key(key, pub, *pubSz, NULL, 0, 0);
1431-
if (ret == 0)
1432-
ret = _ffc_pairwise_consistency_test(key, pub, *pubSz, priv, *privSz);
1443+
if (ret == 0) {
1444+
/* Pairwise Consistency Test per SP 800-56A r3 sec 5.6.2.1.4
1445+
* (FFC key pair). FIPS 140-3 IG 10.3.B requires a PCT after
1446+
* KeyGen for key-establishment algorithms; on failure under a
1447+
* FIPS build the error is remapped to DH_PCT_E so the FIPS
1448+
* module's DEGRADE_STATE handler transitions FIPS_CAST_DH_
1449+
* PRIMITIVE_Z to the error state. */
1450+
ret = _ffc_pairwise_consistency_test(key, pub, *pubSz, priv,
1451+
*privSz);
1452+
#ifdef HAVE_FIPS
1453+
if (ret != 0)
1454+
ret = DH_PCT_E;
1455+
#endif
1456+
}
14331457
#endif /* FIPS V5 or later || WOLFSSL_VALIDATE_DH_KEYGEN */
14341458

14351459

wolfcrypt/src/error.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,21 @@ const char* wc_GetErrorString(int error)
692692
case SLH_DSA_KAT_FIPS_E:
693693
return "SLH-DSA Known Answer Test check FIPS error";
694694

695+
case SLH_DSA_PCT_E:
696+
return "wolfcrypt SLH-DSA Pairwise Consistency Test Failure";
697+
698+
case CMAC_KAT_FIPS_E:
699+
return "AES-CMAC Known Answer Test FIPS error";
700+
701+
case SHAKE_KAT_FIPS_E:
702+
return "SHAKE Known Answer Test FIPS error";
703+
704+
case DH_PCT_E:
705+
return "wolfcrypt DH (FFC) Pairwise Consistency Test Failure";
706+
707+
case AES_KW_KAT_FIPS_E:
708+
return "AES-KW Known Answer Test FIPS error";
709+
695710
case SEQ_OVERFLOW_E:
696711
return "Sequence counter would overflow";
697712

wolfcrypt/src/wc_slhdsa.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6701,6 +6701,45 @@ int wc_SlhDsaKey_MakeKey(SlhDsaKey* key, WC_RNG* rng)
67016701
key->sk + 2 * n, n);
67026702
}
67036703

6704+
#ifdef HAVE_FIPS
6705+
/* Pairwise Consistency Test (PCT) per FIPS 140-3 IG 10.3.A (TE10.35.02):
6706+
* sign with the new sk, verify with the matching pk. SLH-DSA is a
6707+
* stateless hash-based signature scheme (FIPS 205), so the relaxed PCT
6708+
* rule for stateful HBS (LMS/XMSS) does not apply -- PCT runs on every
6709+
* KeyGen. SignDeterministic avoids consuming RNG state; heap allocation
6710+
* is used because SLH-DSA signatures can reach ~50 KB. */
6711+
if (ret == 0) {
6712+
static const byte pct_msg[] = "wolfSSL SLH-DSA PCT";
6713+
byte* pct_sig = (byte*)XMALLOC(WC_SLHDSA_MAX_SIG_LEN, NULL,
6714+
DYNAMIC_TYPE_TMP_BUFFER);
6715+
word32 pct_sigSz = WC_SLHDSA_MAX_SIG_LEN;
6716+
6717+
if (pct_sig == NULL) {
6718+
ret = MEMORY_E;
6719+
}
6720+
if (ret == 0) {
6721+
ret = wc_SlhDsaKey_SignDeterministic(key, NULL, 0,
6722+
pct_msg, sizeof(pct_msg), pct_sig, &pct_sigSz);
6723+
}
6724+
if (ret == 0) {
6725+
ret = wc_SlhDsaKey_Verify(key, NULL, 0,
6726+
pct_msg, sizeof(pct_msg), pct_sig, pct_sigSz);
6727+
if (ret != 0) {
6728+
ret = SLH_DSA_PCT_E;
6729+
}
6730+
}
6731+
if (pct_sig != NULL) {
6732+
ForceZero(pct_sig, WC_SLHDSA_MAX_SIG_LEN);
6733+
XFREE(pct_sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
6734+
}
6735+
/* IG 10.3.A (TE10.35.02): a key pair that fails the PCT must be
6736+
* rendered unusable. */
6737+
if (ret != 0) {
6738+
wc_SlhDsaKey_Free(key);
6739+
}
6740+
}
6741+
#endif /* HAVE_FIPS */
6742+
67046743
return ret;
67056744
}
67066745

wolfssl/wolfcrypt/error-crypt.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,17 @@ enum wolfCrypt_ErrorCodes {
327327
ML_DSA_PCT_E = -1016, /* ML-DSA Pairwise Consistency Test failure */
328328
DRBG_SHA512_KAT_FIPS_E = -1017, /* SHA-512 DRBG KAT failure */
329329
SLH_DSA_KAT_FIPS_E = -1018, /* SLH-DSA CAST KAT failure */
330-
331-
WC_SPAN2_LAST_E = -1018, /* Update to indicate last used error code */
332-
WC_LAST_E = -1018, /* the last code used either here or in
330+
SLH_DSA_PCT_E = -1019, /* SLH-DSA Pairwise Consistency Test failure */
331+
CMAC_KAT_FIPS_E = -1020, /* AES-CMAC KAT failure (vendor-elected) */
332+
SHAKE_KAT_FIPS_E = -1021, /* SHAKE KAT failure (vendor-elected) */
333+
DH_PCT_E = -1022, /* DH (FFC) Pairwise Consistency Test
334+
* failure (SP 800-56A r3 sec 5.6.2.1.4,
335+
* FIPS 140-3 IG 10.3.B) */
336+
AES_KW_KAT_FIPS_E = -1023, /* AES-KW KAT failure (vendor-elected,
337+
* SP 800-38F sec 6.2 / RFC 3394) */
338+
339+
WC_SPAN2_LAST_E = -1023, /* Update to indicate last used error code */
340+
WC_LAST_E = -1023, /* the last code used either here or in
333341
* error-ssl.h */
334342

335343
WC_SPAN2_MIN_CODE_E = -1999, /* Last usable code in span 2 */

wolfssl/wolfcrypt/fips_test.h

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,23 @@
3131
extern "C" {
3232
#endif
3333

34-
/* Added for FIPS v5.3 or later */
35-
#if defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)
34+
/* Added for FIPS v5.3 or later.
35+
*
36+
* v7.0.0 and later upgrade the in-core integrity HMAC to SHA-512 (with a
37+
* 512-bit key) for NSA 2.0 compliance. Customers that must avoid SHA-256
38+
* anywhere in the validated module can therefore use the v7 module without
39+
* residual SHA-256 integrity material. v5.3 and v6.x retain HMAC-SHA-256.
40+
*/
41+
#if defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(7,0)
42+
#ifdef WOLFSSL_SHA512
43+
#define FIPS_IN_CORE_DIGEST_SIZE 64
44+
#define FIPS_IN_CORE_HASH_TYPE WC_SHA512
45+
#define FIPS_IN_CORE_KEY_SZ 64
46+
#define FIPS_IN_CORE_VERIFY_SZ FIPS_IN_CORE_KEY_SZ
47+
#else
48+
#error FIPS v7+ integrity test requires WOLFSSL_SHA512
49+
#endif
50+
#elif defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3)
3651
/* Determine FIPS in core hash type and size */
3752
#ifndef NO_SHA256
3853
#define FIPS_IN_CORE_DIGEST_SIZE 32
@@ -80,7 +95,10 @@ enum FipsCastId {
8095
FIPS_CAST_XMSS = 23,
8196
FIPS_CAST_DRBG_SHA512 = 24,
8297
FIPS_CAST_SLH_DSA = 25,
83-
FIPS_CAST_COUNT = 26
98+
FIPS_CAST_AES_CMAC = 26,
99+
FIPS_CAST_SHAKE = 27,
100+
FIPS_CAST_AES_KW = 28,
101+
FIPS_CAST_COUNT = 29
84102
};
85103

86104
enum FipsCastStateId {

wolfssl/wolfcrypt/random.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@
5757
#define DRBG_SEED_LEN (440/8)
5858
#endif
5959

60+
/* Size of the DRBG seed (SHA-512) */
6061
#ifdef WOLFSSL_DRBG_SHA512
61-
#define DRBG_SHA512_SEED_LEN (888/8) /* 111 bytes per SP 800-90A Table 2 */
62+
#ifndef DRBG_SHA512_SEED_LEN
63+
#define DRBG_SHA512_SEED_LEN (888/8) /* 111 bytes per SP 800-90A
64+
* Table 2 */
65+
#endif
6266
#endif
6367

6468

0 commit comments

Comments
 (0)