Skip to content

Commit a43af0f

Browse files
committed
SecurityReview FND 40.2 + 36.1 + 6.4 + 10.1 + 15.1 + 26.7: integrity, PCT, zeroize, CMAC/SHAKE CASTs, DH PCT + configurable DRBG_SHA512_SEED_LEN
Six findings from the v7.0.0 security review, squashed into one commit per the Part3 branch invariant. FND 40.2 (in-core integrity HMAC upgraded to SHA-512) - wolfssl/wolfcrypt/fips_test.h: add v7+ branch that selects SHA-512 / 64-byte digest / 512-bit key / 64-byte verify-size. Older versions (v5.3, v6.x) keep HMAC-SHA-256. - fips-hash.sh: drop the hardcoded cut -c1-64 so the script works for SHA-512 (128 hex chars) as well as SHA-256. Companion fips changes update verifyCore placeholder, coreKey, and static_assert on sizeof(verifyCore). PL-R34 section 5.1 tracked- changes edit says HMAC-SHA2-512 with a 512-bit key. FND 36.1 (SLH-DSA KeyGen now runs a Pairwise Consistency Test) - wolfssl/wolfcrypt/error-crypt.h: add SLH_DSA_PCT_E = -1019. - wolfcrypt/src/error.c: description string. - wolfcrypt/src/wc_slhdsa.c wc_SlhDsaKey_MakeKey: sign with SignDeterministic + verify under HAVE_FIPS. Heap-allocated sig buffer (SLH-DSA sigs can be ~50 KB), ForceZero + free on failure. Companion fips: DEGRADE_STATE handler + optest case_1019. FND 6.4 (AES-GCM decrypt zeroizes output on authentication failure) - wolfcrypt/src/aes.c wc_AesGcmDecrypt: ForceZero(out, sz) after VECTOR_REGISTERS_POP when ret == AES_GCM_AUTH_E. All software sub-implementations funnel through that ret. - wc_AesGcmDecryptFinal: comment pointing callers at PL-R34 §2.7 operational rule on streaming API. PL-R34 §2.7 tracked-changes paragraph documents the new rule. FND 10.1 (AES-CMAC vendor-elected dedicated CAST) The v7.0.0 module now performs a dedicated AES-CMAC KAT at POST. FIPS 140-3 IG 10.3.A permits a single authenticated-mode KAT (AES-GCM, the "more complex" composition) to cover AES-CMAC as well and the prior v6.0.0 validation was approved on that basis. For v7.0.0 the vendor determined that the CMAC subkey-derivation path (K1/K2 via GF(2^128) doubling, final-block padding) is structurally distinct from GHASH and wished to exercise it with a dedicated CAST for additional assurance. Voluntary enhancement exceeding the IG 10.3.A minimum. - wolfssl/wolfcrypt/fips_test.h: FIPS_CAST_AES_CMAC = 26. - wolfssl/wolfcrypt/error-crypt.h: CMAC_KAT_FIPS_E = -1020. - wolfcrypt/src/error.c: description string. Companion fips: AesCmac_KnownAnswerTest (SP 800-38B D.1 empty-msg KAT), FIPS_CAST_AES_CMAC case in DoCAST, DEGRADE_STATE handler, CastIdToStr entry, optest case_1020. PL-R36 tracked-changes paragraph documents the vendor-elected framing. FND 15.1 (SHAKE vendor-elected dedicated CAST) SHAKE was an approved algorithm in the v6.0.0 module; self-testing was covered by the SHA3-256 CAST (embedded in HMAC-SHA3-256 CAST) under IG 10.3.B via the shared Keccak-f[1600] permutation. For v7.0.0 the vendor has elected to refactor SHAKE self-testing into a dedicated FIPS_CAST_SHAKE that exercises SHAKE's distinct 0x1F domain separator (vs SHA3's 0x06) and arbitrary-length squeeze. A single CAST identifier covers both SHAKE-128 and SHAKE-256 to minimize code footprint. Voluntary enhancement exceeding the IG 10.3.B minimum. - wolfssl/wolfcrypt/fips_test.h: FIPS_CAST_SHAKE = 27, bump FIPS_CAST_COUNT = 28. - wolfssl/wolfcrypt/error-crypt.h: SHAKE_KAT_FIPS_E = -1021. - wolfcrypt/src/error.c: description string. Companion fips: Shake_KnownAnswerTest (variant + msg + outLen + expected parameters), FIPS_CAST_SHAKE case exercising SHAKE-128 and SHAKE-256 with ACVTS vectors (vsId 2836391 tcId 1 for SHAKE-128, vsId 2836392 tcId 1 for SHAKE-256), DEGRADE_STATE handler, CastIdToStr entry, optest case_1021. All 8 SHAKE wrappers in fips.c (Shake128/256 Update/Final/Absorb/SqueezeBlocks) now gate on FIPS_CAST_SHAKE and return SHAKE_KAT_FIPS_E on failure; a block comment above the SHAKE wrapper section records the v6->v7 vendor-elected refactor rationale. PL-R36 tracked-changes paragraph documents the framing and the updated CAST tally (28 total: 26 baseline + 2 vendor-elected). Colleague request (not a finding): make DRBG_SHA512_SEED_LEN overridable at build time to mirror the existing DRBG_SEED_LEN pattern so a downstream consumer can pre-define it. - wolfssl/wolfcrypt/random.h: wrap #define DRBG_SHA512_SEED_LEN in #ifndef/#endif and add the matching "/* Size of the DRBG seed (SHA-512) */" header comment. FND 26.7 (DH KeyGen now flags PCT failure as DH_PCT_E) Per SP 800-56A r3 sec 5.6.2.1.4 the DH (FFC) public key derived from a freshly-generated private key must equal the supplied public key (method (b) PCT). wc_DhGenerateKeyPair_Sync (wolfcrypt/src/dh.c) has invoked _ffc_pairwise_consistency_test under FIPS since v5.0, but the math-layer error code that bubbled out of the test was not recognized by the FIPS module's degrade-state handler -- FIPS 140-3 IG 10.3.B requires the module to enter the error state on PCT failure. - wolfssl/wolfcrypt/error-crypt.h: add DH_PCT_E = -1022; bump WC_SPAN2_LAST_E and WC_LAST_E to -1022. Comment cites SP 800-56A r3 sec 5.6.2.1.4 and FIPS 140-3 IG 10.3.B. - wolfcrypt/src/error.c: description string for DH_PCT_E. - wolfcrypt/src/dh.c wc_DhGeneratePublic + wc_DhGenerateKeyPair_Sync: on _ffc_pairwise_consistency_test failure under HAVE_FIPS, remap the math error to DH_PCT_E so the FIPS module recognizes it. Inline comment at the remap sites cites SP 800-56A r3 sec 5.6.2.1.4 and FIPS 140-3 IG 10.3.B. Companion fips changes add the DEGRADE_STATE(FIPS_CAST_DH_PRIMITIVE_Z) handler for DH_PCT_E and the optest case_1022. Paperwork (PL-R36 Compliance Summary): tracked-changes paragraph inserted after the existing DH PCT description, noting the v7.0.0 enhancement (DH_PCT_E error code, degrade-state transition per IG 10.3.B). Verified: make + fips-hash.sh + make; make check all pass (5 pass, 3 skip, 0 fail) with default configure, AND with the CI-representative configure: --enable-fips=ready --enable-opensslall --enable-opensslextra --enable-crl --enable-harden --enable-asio --enable-certreq --enable-certgen --enable-certext --enable-aesni CFLAGS="-DHAVE_EX_DATA -DOPENSSL_COMPATIBLE_DEFAULTS -DWOLFSSL_ALT_NAMES -DWC_RNG_SEED_CB -DWOLFSSL_CUSTOM_OID -DWOLFSSL_FPKI -DASN_TEMPLATE_SKIP_ISCA_CHECK" (9 pass, 3 skip, 0 fail).
1 parent cf2db42 commit a43af0f

8 files changed

Lines changed: 132 additions & 12 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

wolfcrypt/src/aes.c

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

1096310963
VECTOR_REGISTERS_POP;
1096410964

10965+
/* FIPS 140-3 / SP 800-38D: on authentication failure, the decrypted-but-
10966+
* unauthenticated plaintext in `out` must not be released to the caller.
10967+
* Wipe it here so a caller that ignores the return value cannot observe
10968+
* plaintext derived from forged ciphertext. All software paths (AES-NI,
10969+
* AVX1/2, ARM HW/NEON, C fallback) funnel through `ret` here, so this
10970+
* single guard covers every sub-implementation. */
10971+
if (ret == WC_NO_ERR_TRACE(AES_GCM_AUTH_E) && out != NULL && sz > 0) {
10972+
ForceZero(out, sz);
10973+
}
10974+
1096510975
return ret;
1096610976
}
1096710977
#endif
@@ -12665,6 +12675,10 @@ int wc_AesGcmDecryptFinal(Aes* aes, const byte* authTag, word32 authTagSz)
1266512675
}
1266612676
}
1266712677

12678+
/* Streaming decrypt cannot zeroize prior Update output buffers from here
12679+
* (Final does not see them). On AES_GCM_AUTH_E, the caller is responsible
12680+
* for treating all Update-produced plaintext as invalid and wiping it.
12681+
* See PL-R34 Security Policy section 8 (Operational Rules). */
1266812682
return ret;
1266912683
}
1267012684
#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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,18 @@ 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+
695707
case SEQ_OVERFLOW_E:
696708
return "Sequence counter would overflow";
697709

wolfcrypt/src/wc_slhdsa.c

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

6573+
#ifdef HAVE_FIPS
6574+
/* Pairwise Consistency Test (PCT) per FIPS 140-3 IG 10.3.A (TE10.35.02):
6575+
* sign with the new sk, verify with the matching pk. SLH-DSA is a
6576+
* stateless hash-based signature scheme (FIPS 205), so the relaxed PCT
6577+
* rule for stateful HBS (LMS/XMSS) does not apply -- PCT runs on every
6578+
* KeyGen. SignDeterministic avoids consuming RNG state; heap allocation
6579+
* is used because SLH-DSA signatures can reach ~50 KB. */
6580+
if (ret == 0) {
6581+
static const byte pct_msg[] = "wolfSSL SLH-DSA PCT";
6582+
byte* pct_sig = (byte*)XMALLOC(WC_SLHDSA_MAX_SIG_LEN, NULL,
6583+
DYNAMIC_TYPE_TMP_BUFFER);
6584+
word32 pct_sigSz = WC_SLHDSA_MAX_SIG_LEN;
6585+
6586+
if (pct_sig == NULL) {
6587+
ret = MEMORY_E;
6588+
}
6589+
if (ret == 0) {
6590+
ret = wc_SlhDsaKey_SignDeterministic(key, NULL, 0,
6591+
pct_msg, sizeof(pct_msg), pct_sig, &pct_sigSz);
6592+
}
6593+
if (ret == 0) {
6594+
ret = wc_SlhDsaKey_Verify(key, NULL, 0,
6595+
pct_msg, sizeof(pct_msg), pct_sig, pct_sigSz);
6596+
if (ret != 0) {
6597+
ret = SLH_DSA_PCT_E;
6598+
}
6599+
}
6600+
if (pct_sig != NULL) {
6601+
ForceZero(pct_sig, WC_SLHDSA_MAX_SIG_LEN);
6602+
XFREE(pct_sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
6603+
}
6604+
/* IG 10.3.A (TE10.35.02): a key pair that fails the PCT must be
6605+
* rendered unusable. */
6606+
if (ret != 0) {
6607+
wc_SlhDsaKey_Free(key);
6608+
}
6609+
}
6610+
#endif /* HAVE_FIPS */
6611+
65736612
return ret;
65746613
}
65756614

wolfssl/wolfcrypt/error-crypt.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,15 @@ 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+
337+
WC_SPAN2_LAST_E = -1022, /* Update to indicate last used error code */
338+
WC_LAST_E = -1022, /* the last code used either here or in
333339
* error-ssl.h */
334340

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

wolfssl/wolfcrypt/fips_test.h

Lines changed: 20 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,9 @@ 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_COUNT = 28
84101
};
85102

86103
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)