Merge pull request #11148 from dgarske/fenrir_aes_port_fixes

Fixes for AES in Octeon and TI
pull/11169/head
JacobBarthelmeh 2026-08-13 10:41:38 -06:00 committed by GitHub
commit a0a5b56e3c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 196 additions and 76 deletions

View File

@ -52,6 +52,13 @@
#include <wolfssl/wolfcrypt/aes.h>
#endif
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
#define NOOPT __attribute__((optimize("O0")))
static int devId = 1234;
@ -441,11 +448,17 @@ static NOOPT void Octeon_GHASH_Init(word16 poly, byte* h)
}
/* in may be a caller supplied buffer with no alignment guarantee, so load it
* the same unaligned safe way Octeon_AesGcm_SetAAD does. A plain 64-bit
* dereference of an odd address traps on MIPS64. */
static NOOPT void Octeon_GHASH_Update(byte* in)
{
word64* bigIn = (word64*)in;
CVMX_MT_GFM_XOR0(bigIn[0]);
CVMX_MT_GFM_XORMUL1(bigIn[1]);
word64 in0, in1;
CVMX_LOADUNA_INT64(in0, in, 0);
CVMX_LOADUNA_INT64(in1, in, 8);
CVMX_MT_GFM_XOR0(in0);
CVMX_MT_GFM_XORMUL1(in1);
}
@ -478,7 +491,7 @@ static NOOPT int Octeon_AesGcm_SetKey(Aes* aes)
CVMX_MT_AES_KEYLENGTH((aes->keylen / 8) - 1);
if (!aes->keySet) {
uint64_t* bigH = (uint64_t*)aes->H;
uint64_t* bigH = (uint64_t*)aes->gcm.H;
CVMX_MT_AES_ENC0(0);
CVMX_MT_AES_ENC1(0);
CVMX_MF_AES_RESULT(bigH[0], 0);
@ -506,11 +519,17 @@ static NOOPT int Octeon_AesGcm_SetIV(Aes* aes, byte* iv, word32 ivSz)
}
else {
int blocks, remainder, i;
byte aesBlock[WC_AES_BLOCK_SIZE];
ALIGN16 byte aesBlock[WC_AES_BLOCK_SIZE];
blocks = ivSz / WC_AES_BLOCK_SIZE;
remainder = ivSz % WC_AES_BLOCK_SIZE;
/* Hash the IV against a freshly loaded H. SetKey only computes
* aes->gcm.H in memory, it does not touch the GFM hardware, so
* without this J0 would be derived from whatever GFM state the
* previous operation left behind. */
Octeon_GHASH_Init(0xe100, aes->gcm.H);
for (i = 0; i < blocks; i++, iv += WC_AES_BLOCK_SIZE)
Octeon_GHASH_Update(iv);
@ -527,7 +546,7 @@ static NOOPT int Octeon_AesGcm_SetIV(Aes* aes, byte* iv, word32 ivSz)
aes->y0 = aes->reg[3];
aes->reg[3]++;
Octeon_GHASH_Init(0xe100, aes->H);
Octeon_GHASH_Init(0xe100, aes->gcm.H);
}
return ret;
@ -549,7 +568,7 @@ static NOOPT int Octeon_AesGcm_SetAAD(Aes* aes, byte* aad, word32 aadSz)
blocks = aadSz / WC_AES_BLOCK_SIZE;
remainder = aadSz % WC_AES_BLOCK_SIZE;
Octeon_GHASH_Restore(0xe100, aes->H);
Octeon_GHASH_Restore(0xe100, aes->gcm.H);
p = (word64*)aesBlock;
@ -672,6 +691,9 @@ static int Octeon_AesGcm_SetEncrypt(Aes* aes, byte* in, byte* out, word32 inSz,
}
/* Computes the GCM tag into tag, which must be a private buffer of at least
* WC_AES_BLOCK_SIZE bytes. Never pass a caller supplied tag pointer here, the
* decrypt path must compare rather than overwrite. */
static NOOPT int Octeon_AesGcm_Finalize(Aes* aes, word32 inSz, word32 aadSz,
byte* tag)
{
@ -716,15 +738,24 @@ static NOOPT int Octeon_AesGcm_Finalize(Aes* aes, word32 inSz, word32 aadSz,
static int Octeon_AesGcm_Encrypt(Aes* aes, byte* in, byte* out, word32 inSz,
byte* iv, word32 ivSz, byte* aad, word32 aadSz, byte* tag)
byte* iv, word32 ivSz, byte* aad, word32 aadSz, byte* tag,
word32 tagSz)
{
int ret = 0;
int ret;
ALIGN16 byte calcTag[WC_AES_BLOCK_SIZE];
if (aes == NULL)
ret = BAD_FUNC_ARG;
/* Return before touching any caller owned buffer. Use the same tag size
* policy as the software path rather than a local range check, so an
* unsupported size is rejected here too. */
if (aes == NULL || tag == NULL) {
return BAD_FUNC_ARG;
}
ret = wc_local_AesGcmCheckTagSz(tagSz);
if (ret != 0) {
return ret;
}
if (ret == 0)
ret = Octeon_AesGcm_SetKey(aes);
ret = Octeon_AesGcm_SetKey(aes);
if (ret == 0)
ret = Octeon_AesGcm_SetIV(aes, iv, ivSz);
@ -736,22 +767,40 @@ static int Octeon_AesGcm_Encrypt(Aes* aes, byte* in, byte* out, word32 inSz,
ret = Octeon_AesGcm_SetEncrypt(aes, in, out, inSz, 1);
if (ret == 0)
ret = Octeon_AesGcm_Finalize(aes, inSz, aadSz, tag);
ret = Octeon_AesGcm_Finalize(aes, inSz, aadSz, calcTag);
/* Only tagSz bytes belong to the caller, the tag buffer may be shorter
* than a full block. */
if (ret == 0)
XMEMCPY(tag, calcTag, tagSz);
ForceZero(calcTag, sizeof(calcTag));
return ret;
}
static int Octeon_AesGcm_Decrypt(Aes* aes, byte* in, byte* out, word32 inSz,
byte* iv, word32 ivSz, byte* aad, word32 aadSz, byte* tag)
byte* iv, word32 ivSz, byte* aad, word32 aadSz, const byte* tag,
word32 tagSz)
{
int ret = 0;
int ret;
ALIGN16 byte calcTag[WC_AES_BLOCK_SIZE];
if (aes == NULL)
ret = BAD_FUNC_ARG;
/* Return before touching any caller owned buffer. The output wipe below
* is deliberately conservative: it fires on any failure past this point,
* including a setup failure that never wrote to out. Use the same tag
* size policy as the software path rather than a local range check, so an
* unsupported size is rejected here too. */
if (aes == NULL || tag == NULL) {
return BAD_FUNC_ARG;
}
ret = wc_local_AesGcmCheckTagSz(tagSz);
if (ret != 0) {
return ret;
}
if (ret == 0)
ret = Octeon_AesGcm_SetKey(aes);
ret = Octeon_AesGcm_SetKey(aes);
if (ret == 0)
ret = Octeon_AesGcm_SetIV(aes, iv, ivSz);
@ -762,8 +811,18 @@ static int Octeon_AesGcm_Decrypt(Aes* aes, byte* in, byte* out, word32 inSz,
if (ret == 0)
ret = Octeon_AesGcm_SetEncrypt(aes, in, out, inSz, 0);
/* Finalize into a private buffer, the caller's tag is the one the peer
* sent and must be compared against, never written to. */
if (ret == 0)
ret = Octeon_AesGcm_Finalize(aes, inSz, aadSz, tag);
ret = Octeon_AesGcm_Finalize(aes, inSz, aadSz, calcTag);
if (ret == 0 && ConstantCompare(tag, calcTag, (int)tagSz) != 0)
ret = AES_GCM_AUTH_E;
if (ret != 0 && out != NULL && inSz > 0)
ForceZero(out, inSz);
ForceZero(calcTag, sizeof(calcTag));
return ret;
}
@ -802,7 +861,8 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx)
info->cipher.aesgcm_enc.ivSz,
(byte*)info->cipher.aesgcm_enc.authIn,
info->cipher.aesgcm_enc.authInSz,
(byte*)info->cipher.aesgcm_enc.authTag);
(byte*)info->cipher.aesgcm_enc.authTag,
info->cipher.aesgcm_enc.authTagSz);
}
else {
ret = Octeon_AesGcm_Decrypt(
@ -814,7 +874,8 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx)
info->cipher.aesgcm_dec.ivSz,
(byte*)info->cipher.aesgcm_dec.authIn,
info->cipher.aesgcm_dec.authInSz,
(byte*)info->cipher.aesgcm_dec.authTag);
info->cipher.aesgcm_dec.authTag,
info->cipher.aesgcm_dec.authTagSz);
}
}
#endif /* HAVE_AESGCM */

View File

@ -51,8 +51,14 @@
#include "driverlib/rom.h"
#define AES_CFG_MODE_CTR_NOCTR (AES_CFG_MODE_CTR + 100)
#define IS_ALIGN16(p) (((unsigned int)(p) & 0xf) == 0)
#define ROUNDUP_16(n) ((n+15) & 0xfffffff0)
/* IS_ALIGN16 tests an address, IS_MULT16 tests a length. Do not confuse the
* two: the ROM AES engine needs both a 16 byte aligned buffer and a whole
* number of 16 byte blocks. ALIGN16_SLACK is the extra room needed to
* hand-align an allocation up to a block boundary. */
#define ALIGN16_SLACK (WC_AES_BLOCK_SIZE - 1)
#define IS_ALIGN16(p) ((((wc_ptr_t)(p)) & (wc_ptr_t)ALIGN16_SLACK) == 0)
#define IS_MULT16(n) (((n) & (word32)ALIGN16_SLACK) == 0)
#define ROUNDUP_16(n) (((n) + ALIGN16_SLACK) & ~(word32)ALIGN16_SLACK)
#ifndef TI_BUFFSIZE
#define TI_BUFFSIZE 1024
#endif
@ -479,6 +485,41 @@ static void AesAuthSetIv(Aes *aes, const byte *nonce, word32 len, word32 L,
}
}
/* Allocates a 16 byte aligned, whole block sized temporary for sz bytes and,
* when src is not NULL, copies src into it. XMALLOC only guarantees alignment
* for fundamental types, which can be as little as 4 bytes, so over-allocate
* and align by hand. *save receives the pointer to free, *aligned the pointer
* to hand to the ROM engine. */
static int AesAuthBounce(const byte* src, word32 sz, void* heap, byte** save,
byte** aligned)
{
byte* p;
/* ROUNDUP_16 wraps within a block of the word32 maximum, which would
* under-allocate. Unreachable with any real buffer, but the size
* arithmetic below must not be able to wrap. */
if (sz > (0xFFFFFFFFU - (2U * (word32)ALIGN16_SLACK))) {
return BAD_FUNC_ARG;
}
p = (byte*)XMALLOC(ROUNDUP_16(sz) + ALIGN16_SLACK, heap,
DYNAMIC_TYPE_TMP_BUFFER);
if (p == NULL) {
return MEMORY_E;
}
*save = p;
*aligned = (byte*)(((wc_ptr_t)p + ALIGN16_SLACK) &
~(wc_ptr_t)ALIGN16_SLACK);
XMEMSET(*aligned, 0, ROUNDUP_16(sz));
if (src != NULL) {
XMEMCPY(*aligned, src, sz);
}
return 0;
}
static int AesAuthEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz,
const byte* nonce, word32 nonceSz,
byte* authTag, word32 authTagSz,
@ -516,33 +557,31 @@ static int AesAuthEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz,
return 0;
}
/* Make sure all pointers are 16 byte aligned */
if (IS_ALIGN16(inSz)) {
in_save = NULL; in_a = (byte*)in;
out_save = NULL; out_a = out;
/* Bounce each buffer that is not 16 byte aligned, and each buffer whose
* length is not a whole number of blocks. The alignment of in, out and
* authIn is independent, so each gets its own temporary. */
if (inSz > 0 && (!IS_ALIGN16(in) || !IS_MULT16(inSz))) {
ret = AesAuthBounce(in, inSz, aes->heap, &in_save, &in_a);
if (ret != 0) { goto exit; }
}
else {
in_save = XMALLOC(ROUNDUP_16(inSz), NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (in_save == NULL) { ret = MEMORY_E; goto exit; }
in_a = in_save;
XMEMSET(in_a, 0, ROUNDUP_16(inSz));
XMEMCPY(in_a, in, inSz);
out_save = XMALLOC(ROUNDUP_16(inSz), NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (out_save == NULL) { ret = MEMORY_E; goto exit; }
out_a = out_save;
in_a = (byte*)in;
}
if (IS_ALIGN16(authInSz)) {
authIn_save = NULL; authIn_a = (byte*)authIn;
if (inSz > 0 && (!IS_ALIGN16(out) || !IS_MULT16(inSz))) {
ret = AesAuthBounce(NULL, inSz, aes->heap, &out_save, &out_a);
if (ret != 0) { goto exit; }
}
else {
authIn_save = XMALLOC(ROUNDUP_16(authInSz), NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (authIn_save == NULL) { ret = MEMORY_E; goto exit; }
out_a = out;
}
authIn_a = authIn_save;
XMEMSET(authIn_a, 0, ROUNDUP_16(authInSz));
XMEMCPY(authIn_a, authIn, authInSz);
if (authInSz > 0 && (!IS_ALIGN16(authIn) || !IS_MULT16(authInSz))) {
ret = AesAuthBounce(authIn, authInSz, aes->heap, &authIn_save, &authIn_a);
if (ret != 0) { goto exit; }
}
else {
authIn_a = (byte*)authIn;
}
/* Do AES-CCM/GCM Cipher with Auth */
@ -565,20 +604,31 @@ static int AesAuthEncrypt(Aes* aes, byte* out, const byte* in, word32 inSz,
wolfSSL_TI_unlockCCM();
if (ret == false) {
XMEMSET(out, 0, inSz);
XMEMSET(authTag, 0, authTagSz);
/* out is NULL for the GMAC case, where inSz is zero. */
if (out != NULL)
ForceZero(out, inSz);
ForceZero(authTag, authTagSz);
ret = AES_GCM_AUTH_E;
}
else {
XMEMCPY(out, out_a, inSz);
if (out_save != NULL)
XMEMCPY(out, out_a, inSz);
XMEMCPY(authTag, tmpTag, authTagSz);
ret = 0;
}
exit:
XFREE(in_save, NULL, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(out_save, NULL, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(authIn_save, NULL, DYNAMIC_TYPE_TMP_BUFFER);
/* Whichever of the two data bounce buffers holds plaintext depends on
* direction, so both are wiped. On the tag failure path the caller's out
* has been cleared but out_save has not. authIn_save is exempt because
* AAD is not secret. */
if (in_save != NULL)
ForceZero(in_save, ROUNDUP_16(inSz) + ALIGN16_SLACK);
if (out_save != NULL)
ForceZero(out_save, ROUNDUP_16(inSz) + ALIGN16_SLACK);
XFREE(in_save, aes->heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(out_save, aes->heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(authIn_save, aes->heap, DYNAMIC_TYPE_TMP_BUFFER);
return ret;
}
@ -622,33 +672,31 @@ static int AesAuthDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz,
return ret;
}
/* Make sure all pointers are 16 byte aligned */
if (IS_ALIGN16(inSz)) {
in_save = NULL; in_a = (byte*)in;
out_save = NULL; out_a = out;
/* Bounce each buffer that is not 16 byte aligned, and each buffer whose
* length is not a whole number of blocks. The alignment of in, out and
* authIn is independent, so each gets its own temporary. */
if (inSz > 0 && (!IS_ALIGN16(in) || !IS_MULT16(inSz))) {
ret = AesAuthBounce(in, inSz, aes->heap, &in_save, &in_a);
if (ret != 0) { goto exit; }
}
else {
in_save = XMALLOC(ROUNDUP_16(inSz), NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (in_save == NULL) { ret = MEMORY_E; goto exit; }
in_a = in_save;
XMEMSET(in_a, 0, ROUNDUP_16(inSz));
XMEMCPY(in_a, in, inSz);
out_save = XMALLOC(ROUNDUP_16(inSz), NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (out_save == NULL) { ret = MEMORY_E; goto exit; }
out_a = out_save;
in_a = (byte*)in;
}
if (IS_ALIGN16(authInSz)) {
authIn_save = NULL; authIn_a = (byte*)authIn;
if (inSz > 0 && (!IS_ALIGN16(out) || !IS_MULT16(inSz))) {
ret = AesAuthBounce(NULL, inSz, aes->heap, &out_save, &out_a);
if (ret != 0) { goto exit; }
}
else {
authIn_save = XMALLOC(ROUNDUP_16(authInSz), NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (authIn_save == NULL) { ret = MEMORY_E; goto exit; }
out_a = out;
}
authIn_a = authIn_save;
XMEMSET(authIn_a, 0, ROUNDUP_16(authInSz));
XMEMCPY(authIn_a, authIn, authInSz);
if (authInSz > 0 && (!IS_ALIGN16(authIn) || !IS_MULT16(authInSz))) {
ret = AesAuthBounce(authIn, authInSz, aes->heap, &authIn_save, &authIn_a);
if (ret != 0) { goto exit; }
}
else {
authIn_a = (byte*)authIn;
}
/* Do AES-CCM/GCM Cipher with Auth */
@ -670,18 +718,29 @@ static int AesAuthDecrypt(Aes* aes, byte* out, const byte* in, word32 inSz,
wolfSSL_TI_unlockCCM();
if ((ret == false) || (ConstantCompare(authTag, tmpTag, authTagSz) != 0)) {
XMEMSET(out, 0, inSz);
/* out is NULL for the GMAC case, where inSz is zero. */
if (out != NULL)
ForceZero(out, inSz);
ret = AES_GCM_AUTH_E;
}
else {
XMEMCPY(out, out_a, inSz);
if (out_save != NULL)
XMEMCPY(out, out_a, inSz);
ret = 0;
}
exit:
XFREE(in_save, NULL, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(out_save, NULL, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(authIn_save, NULL, DYNAMIC_TYPE_TMP_BUFFER);
/* Whichever of the two data bounce buffers holds plaintext depends on
* direction, so both are wiped. On the tag failure path the caller's out
* has been cleared but out_save has not. authIn_save is exempt because
* AAD is not secret. */
if (in_save != NULL)
ForceZero(in_save, ROUNDUP_16(inSz) + ALIGN16_SLACK);
if (out_save != NULL)
ForceZero(out_save, ROUNDUP_16(inSz) + ALIGN16_SLACK);
XFREE(in_save, aes->heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(out_save, aes->heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(authIn_save, aes->heap, DYNAMIC_TYPE_TMP_BUFFER);
return ret;
}