diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index eabd1f217e9..b28c33a3c27 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -785,9 +785,7 @@ WOLFSSL_ARMASM_NEON_NO_TABLE_LOOKUP WOLFSSL_ARM_ARCH_NEON_64BIT WOLFSSL_ASCON_UNROLL WOLFSSL_ASN_EXTRA -WOLFSSL_ASN_TEMPLATE_NEED_SET_INT32 WOLFSSL_ASN_TEMPLATE_TYPE_CHECK -WOLFSSL_ASYNC_CERT_YIELD WOLFSSL_ATECC508 WOLFSSL_ATECC508A_NOSOFTECC WOLFSSL_ATECC508A_TLS @@ -881,7 +879,6 @@ WOLFSSL_MANUALLY_SELECT_DEVICE_CONFIG WOLFSSL_MDK5 WOLFSSL_MEM_FAIL_COUNT WOLFSSL_MICROCHIP_AESGCM -WOLFSSL_MLKEM_DYNAMIC_KEYS WOLFSSL_MLKEM_INVNTT_UNROLL WOLFSSL_MLKEM_NO_MALLOC WOLFSSL_MLKEM_NTT_UNROLL diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index c8a0e5f8fb4..d085b9cc2b4 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -11319,6 +11319,10 @@ int WARN_UNUSED_RESULT AES_GCM_decrypt_C( ALIGN16 byte Tprime[WC_AES_BLOCK_SIZE]; ALIGN16 byte EKY0[WC_AES_BLOCK_SIZE]; volatile sword32 res; +#ifndef WC_AES_GCM_DEC_AUTH_EARLY + byte mask; + word32 i; +#endif if (ivSz == GCM_NONCE_MID_SZ) { /* Counter is IV with bottom 4 bytes set to: 0x00,0x00,0x00,0x01. */ @@ -11438,10 +11442,16 @@ int WARN_UNUSED_RESULT AES_GCM_decrypt_C( */ ret = (ret & ~res); ret |= (res & WC_NO_ERR_TRACE(AES_GCM_AUTH_E)); -#endif - if (ret != 0) { - ForceZero(out, sz); + /* Mask the output on auth failure instead of branching, to keep the tag + * compare constant time. res is all-ones on mismatch, zero on match. A + * single vectorizable pass is cheaper than folding the mask into the + * decrypt loop. Not needed for WC_AES_GCM_DEC_AUTH_EARLY: there the tag is + * checked before decryption, so out is never written on a mismatch. */ + mask = (byte)res; + for (i = 0; i < sz; i++) { + out[i] &= (byte)~mask; } +#endif return ret; } #elif (defined(__aarch64__) || defined(WOLFSSL_ARMASM_NO_HW_CRYPTO)) || \ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 914cdb2f0c0..4baaaf2e267 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -19170,6 +19170,51 @@ static wc_test_ret_t aesgcm_setiv_test(Aes* enc, Aes* dec) ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); if (XMEMCMP(p, resultP, sizeof(p))) ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* A tampered tag must fail decryption. On the software C path the output + * buffer is additionally cleared so unauthenticated plaintext is never + * released. The AES-NI/asm paths do not clear it, so the zero-check is + * limited to the C path, which is forced here by disabling AES-NI for + * this one call. */ + { + #ifdef WOLFSSL_AESNI + int saved_use_aesni = dec->use_aesni; + dec->use_aesni = 0; + #endif + resultT[0] ^= 0xB5; + XMEMSET(resultP, 0x5A, sizeof(p)); + ret = wc_AesGcmDecrypt(dec, resultP, resultC, sizeof(p), + randIV, sizeof(randIV), resultT, 16, a, sizeof(a)); + #if defined(WOLFSSL_ASYNC_CRYPT) + ret = wc_AsyncWait(ret, &dec->asyncDev, WC_ASYNC_FLAG_NONE); + #endif + resultT[0] ^= 0xB5; + #ifdef WOLFSSL_AESNI + dec->use_aesni = saved_use_aesni; + #endif + if (ret != WC_NO_ERR_TRACE(AES_GCM_AUTH_E)) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + #if !defined(WOLFSSL_ARMASM) && !defined(WOLFSSL_RISCV_ASM) && \ + !defined(WOLFSSL_PPC64_ASM) && \ + !defined(WOLFSSL_PPC32_ASM) && !defined(STM32_CRYPTO_AES_GCM) && \ + !defined(WOLFSSL_AFALG_XILINX_AES) && !defined(WOLFSSL_XILINX_CRYPT) && \ + !defined(WOLFSSL_KCAPI_AES) && !defined(WOLFSSL_DEVCRYPTO_AES) && \ + !defined(WOLFSSL_ESP32_CRYPT) && !defined(WOLFSSL_IMXRT_DCP) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(HAVE_FIPS) && \ + !defined(WC_AES_GCM_DEC_AUTH_EARLY) && !defined(WOLFSSL_ASYNC_CRYPT) + { + /* C path clears the output buffer on authentication failure. + * The FIPS module's AES-GCM decrypt does not clear it, so skip + * this check under HAVE_FIPS. */ + word32 z; + for (z = 0; z < (word32)sizeof(p); z++) { + if (resultP[z] != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + } + #endif + ret = 0; + } #endif /* HAVE_AES_DECRYPT */ out: