ML-KEM/ML-DSA: harden against fault attacks

ML-DSA: check pointer to the y parameter has not be faulted.
ML-KEM: to harden against faultiong, use a different buffer for private
seed, sigma, and add a check that the buffer was copied correctly.
SHA-3: fix size of check variables.
This commit is contained in:
Sean Parkinson
2026-02-03 11:11:32 +10:00
parent c807903088
commit 65a1a68877
4 changed files with 78 additions and 24 deletions
+1 -1
View File
@@ -2726,7 +2726,7 @@ AC_ARG_ENABLE([faultharden],
if test "$ENABLED_FAULTHARDEN" = "yes"
then
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CHECK_SIG_FAULTS -DWOLFSSL_CHECK_VER_FAULTS -DWC_SHA3_HARDEN"
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_CHECK_SIG_FAULTS -DWOLFSSL_CHECK_VER_FAULTS -DWC_SHA3_FAULT_HARDEN -DWC_MLKEM_FAULT_HARDEN -DWC_MLDSA_FAULT_HARDEN"
fi
AC_ARG_ENABLE([compileharden],
+39
View File
@@ -8138,6 +8138,9 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
sword32* ct0 = NULL;
byte priv_rand_seed[DILITHIUM_Y_SEED_SZ];
byte* h = sig + params->lambda / 4 + params->zEncSz;
#ifdef WC_MLDSA_FAULT_HARDEN
sword32* y_check;
#endif
/* Check the signature buffer isn't too small. */
if (*sigLen < params->sigSz) {
@@ -8202,6 +8205,9 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
ret = MEMORY_E;
}
else {
#ifdef WC_MLDSA_FAULT_HARDEN
y_check = y;
#endif
w0 = y + params->s1Sz / sizeof(*y);
w1 = w0 + params->s2Sz / sizeof(*w0);
c = w1 + params->s2Sz / sizeof(*w1);
@@ -8270,6 +8276,14 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
{
/* Step 13: NTT-1(A o NTT(y)) */
XMEMCPY(y_ntt, y, params->s1Sz);
#ifdef WC_MLDSA_FAULT_HARDEN
if (y_check != y) {
valid = 0;
ret = BAD_COND_E;
}
}
if (ret == 0) {
#endif
dilithium_vec_ntt_full(y_ntt, params->l);
dilithium_matrix_mul(w, a, y_ntt, params->k, params->l);
dilithium_vec_invntt_full(w, params->k);
@@ -8411,6 +8425,9 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
byte maxK = (byte)min(WOLFSSL_DILITHIUM_SIGN_SMALL_MEM_PRECALC_A,
params->k);
#endif
#ifdef WC_MLDSA_FAULT_HARDEN
sword32* y_check;
#endif
/* Check the signature buffer isn't too small. */
if ((ret == 0) && (*sigLen < params->sigSz)) {
@@ -8442,6 +8459,9 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
ret = MEMORY_E;
}
else {
#ifdef WC_MLDSA_FAULT_HARDEN
y_check = y;
#endif
w0 = y + params->s1Sz / sizeof(*y_ntt);
w1 = w0 + params->s2Sz / sizeof(*w0);
blocks = (byte*)(w1 + params->s2Sz / sizeof(*w1));
@@ -8557,6 +8577,16 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
#else
sword32* y_ntt_t = y_ntt;
#endif
#ifdef WC_MLDSA_FAULT_HARDEN
sword32* yt_check = yt;
#endif
#ifdef WC_MLDSA_FAULT_HARDEN
if (y_check != y) {
valid = 0;
ret = BAD_COND_E;
break;
}
#endif
/* Put r/i into buffer to be hashed. */
aseed[DILITHIUM_PUB_SEED_SZ + 1] = r;
@@ -8571,6 +8601,12 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
break;
}
XMEMCPY(y_ntt_t, yt, DILITHIUM_POLY_SIZE);
#ifdef WC_MLDSA_FAULT_HARDEN
if (yt_check + s * DILITHIUM_N != yt) {
ret = BAD_COND_E;
break;
}
#endif
dilithium_ntt_full(y_ntt_t);
/* Matrix multiply. */
#ifndef WOLFSSL_DILITHIUM_SMALL_MEM_POLY64
@@ -8669,6 +8705,9 @@ static int dilithium_sign_with_seed_mu(dilithium_key* key,
/* Next polynomial. */
yt += DILITHIUM_N;
}
if (ret != 0) {
break;
}
#ifdef WOLFSSL_DILITHIUM_SMALL_MEM_POLY64
for (e = 0; e < DILITHIUM_N; e++) {
wt[e] = dilithium_mont_red(t64[e]);
+23 -23
View File
@@ -595,18 +595,18 @@ static word64 Load64BitLittleEndian(const byte* a)
return n;
}
#elif defined(WC_SHA3_HARDEN)
#elif defined(WC_SHA3_FAULT_HARDEN)
static WC_INLINE word64 Load64Unaligned(const unsigned char *a)
{
#ifdef WC_64BIT_CPU
return *(word64*)a;
#elif defined(WC_32BIT_CPU)
return (((word64)((word32*)a)[1]) << 32) ||
return (((word64)((word32*)a)[1]) << 32) |
((word32*)a)[0];
#else
return (((word64)((word16*)a)[3]) << 48) ||
(((word64)((word16*)a)[2]) << 32) ||
(((word64)((word16*)a)[1]) << 16) ||
return (((word64)((word16*)a)[3]) << 48) |
(((word64)((word16*)a)[2]) << 32) |
(((word64)((word16*)a)[1]) << 16) |
((word16*)a)[0];
#endif
}
@@ -712,9 +712,9 @@ static int Sha3Update(wc_Sha3* sha3, const byte* data, word32 len, byte p)
{
word32 i;
word32 blocks;
#ifdef WC_SHA3_HARDEN
byte check = 0;
byte total_check = 0;
#ifdef WC_SHA3_FAULT_HARDEN
word32 check = 0;
word32 total_check = 0;
#endif
#if defined(WOLFSSL_USE_SAVE_VECTOR_REGISTERS) && defined(USE_INTEL_SPEEDUP)
@@ -732,11 +732,11 @@ static int Sha3Update(wc_Sha3* sha3, const byte* data, word32 len, byte p)
t = &sha3->t[sha3->i];
for (i = 0; i < l; i++) {
t[i] = data[i];
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
check++;
#endif
}
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
if (check != l) {
return BAD_COND_E;
}
@@ -747,16 +747,16 @@ static int Sha3Update(wc_Sha3* sha3, const byte* data, word32 len, byte p)
sha3->i = (byte)(sha3->i + i);
if (sha3->i == p * 8) {
#if !defined(BIG_ENDIAN_ORDER) && !defined(WC_SHA3_HARDEN)
#if !defined(BIG_ENDIAN_ORDER) && !defined(WC_SHA3_FAULT_HARDEN)
xorbuf(sha3->s, sha3->t, (word32)(p * 8));
#else
for (i = 0; i < p; i++) {
sha3->s[i] ^= Load64BitLittleEndian(sha3->t + 8 * i);
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
check++;
#endif
}
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
if (check != p + l) {
return BAD_COND_E;
}
@@ -780,20 +780,20 @@ static int Sha3Update(wc_Sha3* sha3, const byte* data, word32 len, byte p)
blocks = 0;
}
#endif
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
total_check += blocks * p;
#endif
for (; blocks > 0; blocks--) {
#if !defined(BIG_ENDIAN_ORDER) && !defined(WC_SHA3_HARDEN)
#if !defined(BIG_ENDIAN_ORDER) && !defined(WC_SHA3_FAULT_HARDEN)
xorbuf(sha3->s, data, (word32)(p * 8));
#else
for (i = 0; i < p; i++) {
sha3->s[i] ^= Load64Unaligned(data + 8 * i);
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
check++;
#endif
}
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
if (check != total_check - ((blocks - 1) * p)) {
return BAD_COND_E;
}
@@ -807,7 +807,7 @@ static int Sha3Update(wc_Sha3* sha3, const byte* data, word32 len, byte p)
len -= p * 8U;
data += p * 8U;
}
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
if (check != total_check) {
return BAD_COND_E;
}
@@ -837,14 +837,14 @@ static int Sha3Final(wc_Sha3* sha3, byte padChar, byte* hash, byte p, word32 l)
{
word32 rate = p * 8U;
word32 j;
#if defined(BIG_ENDIAN_ORDER) || defined(WC_SHA3_HARDEN)
#if defined(BIG_ENDIAN_ORDER) || defined(WC_SHA3_FAULT_HARDEN)
word32 i;
#endif
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
int check = 0;
#endif
#if !defined(BIG_ENDIAN_ORDER) && !defined(WC_SHA3_HARDEN)
#if !defined(BIG_ENDIAN_ORDER) && !defined(WC_SHA3_FAULT_HARDEN)
xorbuf(sha3->s, sha3->t, sha3->i);
#ifdef WOLFSSL_HASH_FLAGS
if ((p == WC_SHA3_256_COUNT) && (sha3->flags & WC_HASH_SHA3_KECCAK256)) {
@@ -867,11 +867,11 @@ static int Sha3Final(wc_Sha3* sha3, byte padChar, byte* hash, byte p, word32 l)
}
for (i = 0; i < p; i++) {
sha3->s[i] ^= Load64BitLittleEndian(sha3->t + 8 * i);
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
check++;
#endif
}
#ifdef WC_SHA3_HARDEN
#ifdef WC_SHA3_FAULT_HARDEN
if (check != p) {
return BAD_COND_E;
}
+15
View File
@@ -434,7 +434,11 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand,
{
byte buf[2 * WC_ML_KEM_SYM_SZ + 1];
byte* rho = buf;
#ifndef WC_MLKEM_FAULT_HARDEN
byte* sigma = buf + WC_ML_KEM_SYM_SZ;
#else
byte sigma[WC_ML_KEM_SYM_SZ + 1];
#endif
#ifndef WOLFSSL_NO_MALLOC
sword16* e = NULL;
#else
@@ -565,6 +569,17 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand,
}
#endif
}
#ifdef WC_MLKEM_FAULT_HARDEN
if (ret == 0) {
XMEMCPY(sigma, buf + WC_ML_KEM_SYM_SZ, WC_ML_KEM_SYM_SZ);
if (XMEMCMP(sigma, rho, WC_ML_KEM_SYM_SZ) == 0) {
ret = BAD_COND_E;
}
if (XMEMCMP(sigma, rho + WC_ML_KEM_SYM_SZ, WC_ML_KEM_SYM_SZ) != 0) {
ret = BAD_COND_E;
}
}
#endif
if (ret == 0) {
const byte* z = rand + WC_ML_KEM_SYM_SZ;
s = key->priv;