diff --git a/wolfcrypt/src/port/cavium/cavium_octeon_sync.c b/wolfcrypt/src/port/cavium/cavium_octeon_sync.c index 974e9f7c085..a927cbc17cf 100644 --- a/wolfcrypt/src/port/cavium/cavium_octeon_sync.c +++ b/wolfcrypt/src/port/cavium/cavium_octeon_sync.c @@ -52,6 +52,13 @@ #include #endif +#ifdef NO_INLINE + #include +#else + #define WOLFSSL_MISC_INCLUDED + #include +#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; - - if (aes == NULL) - ret = BAD_FUNC_ARG; + int ret; + ALIGN16 byte calcTag[WC_AES_BLOCK_SIZE]; + + /* 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 */ diff --git a/wolfcrypt/src/port/ti/ti-aes.c b/wolfcrypt/src/port/ti/ti-aes.c index 8392cdf1653..4718d97ab26 100644 --- a/wolfcrypt/src/port/ti/ti-aes.c +++ b/wolfcrypt/src/port/ti/ti-aes.c @@ -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; }