From a137245ff85d07c49e41c4020b4a3d635ca4bc35 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 5 Nov 2026 09:20:26 -0800 Subject: [PATCH] Add AES-256-ECB to the PolarFire SoC Athena port for AES-XTS and carry its CTR counter past the engine's 32-bit wrap --- .github/workflows/mpfs-athena-compile.yml | 8 + configure.ac | 8 + wolfcrypt/src/aes.c | 21 ++ wolfcrypt/src/port/microchip/README.md | 56 ++- wolfcrypt/src/port/microchip/mpfs_athena.c | 345 ++++++++++++++++-- .../wolfcrypt/port/microchip/mpfs_athena.h | 12 +- wolfssl/wolfcrypt/settings.h | 3 +- 7 files changed, 420 insertions(+), 33 deletions(-) diff --git a/.github/workflows/mpfs-athena-compile.yml b/.github/workflows/mpfs-athena-compile.yml index 82adc906934..a96fcfc1b9c 100644 --- a/.github/workflows/mpfs-athena-compile.yml +++ b/.github/workflows/mpfs-athena-compile.yml @@ -79,6 +79,14 @@ jobs: extra: '-DWOLFSSL_AES_COUNTER' - leg: sha384-only extra: '-DWOLFSSL_SHA512 -DWOLFSSL_SHA384' + # The cipher case is shared by CTR and ECB, so each must build with + # the other gate off. + - leg: aesecb-only + extra: '-DHAVE_AES_ECB' + # The XTS build the ECB path exists for, with a raised minimum so the + # self-test takes its larger-buffer branch. + - leg: aesxts + extra: '-DWOLFSSL_SHA512 -DWOLFSSL_SHA384 -DWOLFSSL_AES_COUNTER -DHAVE_AES_ECB -DWOLFSSL_AES_XTS -DWOLFSSL_AES_DIRECT -DWC_MPFS_ATHENA_AES_MIN_SZ=512' - leg: no-selftest extra: '-DWOLFSSL_SHA512 -DWOLFSSL_SHA384 -DWOLFSSL_AES_COUNTER -DWC_MPFS_ATHENA_NO_SELFTEST' steps: diff --git a/configure.ac b/configure.ac index 8504097da7f..2eeb124a360 100644 --- a/configure.ac +++ b/configure.ac @@ -12509,6 +12509,14 @@ then if test "x$ENABLED_AESCTR" = "xno"; then AC_MSG_ERROR([--with-mpfs-athena needs AES-CTR; add --enable-aesctr]) fi + # AES-XTS reaches the engine only through the bulk ECB call that + # HAVE_AES_ECB compiles in; without it every XTS block is its own engine + # transaction, slower than software. + if test "x$ENABLED_AESXTS" = "xyes" && test "x$ENABLED_AESECB" = "xno"; then + ENABLED_AESECB=yes + AM_CFLAGS="$AM_CFLAGS -DHAVE_AES_ECB" + AM_CCASFLAGS="$AM_CCASFLAGS -DHAVE_AES_ECB" + fi fi if test "x$ENABLED_PKCS11" = "xyes" || test "x$ENABLED_WOLFTPM" = "xyes" || test "$ENABLED_CAAM" != "no" || test "$ENABLED_SEC_QORIQ" != "no" || test "x$ENABLED_RTL8735B" != "xno" || test "x$ENABLED_SILABS_CRYPTOCB" != "xno" || test "x$ENABLED_VAULTIC" = "xyes" || test "x$ENABLED_ALTERA_FCS" = "xyes" || test "x$ENABLED_ELE" != "xno" || test "x$ENABLED_MPFS_ATHENA" = "xyes" || test "x$ENABLED_ELS_PKC" = "xyes" diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 8659e65f12f..de259ae24ba 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -9991,6 +9991,9 @@ static void GMULT(byte *x, byte m[256][WC_AES_BLOCK_SIZE], XMEMCPY(x, Z + 1, WC_AES_BLOCK_SIZE); #endif #else +/* The 64-bit word path reads Z at odd offsets, so it needs unaligned loads + * just as the 32-bit one does. */ +#ifndef WOLFSSL_USE_ALIGN byte* Z = ws->b; byte a; word64* pZ; @@ -10017,6 +10020,24 @@ static void GMULT(byte *x, byte m[256][WC_AES_BLOCK_SIZE], pZ = (word64*)(Z + 1); pm = (word64*)(m[x[0]]); px[0] = pZ[0] ^ pm[0]; px[1] = pZ[1] ^ pm[1]; +#else + byte* Z = ws->b; + byte a; + int i; + + XMEMCPY(Z + 16, m[x[15]], WC_AES_BLOCK_SIZE); + a = Z[16 + 15]; + Z[15] = R[a][0]; + Z[16] ^= R[a][1]; + for (i = 14; i > 0; i--) { + xorbuf(Z + i + 1, m[x[i]], WC_AES_BLOCK_SIZE); + a = Z[16 + i]; + Z[i] = R[a][0]; + Z[i+1] ^= R[a][1]; + } + xorbuf(Z + 1, m[x[0]], WC_AES_BLOCK_SIZE); + XMEMCPY(x, Z + 1, WC_AES_BLOCK_SIZE); +#endif #endif } #endif diff --git a/wolfcrypt/src/port/microchip/README.md b/wolfcrypt/src/port/microchip/README.md index 3047dcc64a2..689b75aaeeb 100644 --- a/wolfcrypt/src/port/microchip/README.md +++ b/wolfcrypt/src/port/microchip/README.md @@ -1,7 +1,8 @@ # Microchip PolarFire SoC Athena F5200 (TeraFire) -SHA-384 and AES-256-CTR offload on the Athena F5200 "TeraFire" user -cryptoprocessor, through the wolfCrypt crypto callback. +SHA-384, AES-256-CTR and AES-256-ECB offload on the Athena F5200 "TeraFire" +user cryptoprocessor, through the wolfCrypt crypto callback. The ECB path is +what carries AES-256-XTS. ## Hardware @@ -75,9 +76,39 @@ it by turning on `WOLF_CRYPTO_CB`, stock `wolfcrypt_test()` and `benchmark()` drive the engine with no argument. Which engines are offloaded follows the ordinary wolfCrypt feature gates rather -than port-specific ones: `WOLFSSL_SHA384` for the hash, and -`WOLFSSL_AES_COUNTER` with `!NO_AES` for AES-256-CTR. AES-128 and AES-192 -requests are declined to software, as are all other hash types. +than port-specific ones: `WOLFSSL_SHA384` for the hash, `WOLFSSL_AES_COUNTER` +with `!NO_AES` for AES-256-CTR, and `HAVE_AES_ECB` with `!NO_AES` for +AES-256-ECB. AES-128 and AES-192 requests are declined to software, as are all +other hash types. + +### AES-XTS + +XTS is AES-ECB over tweak-masked data, and `_AesXtsHelper()` already issues one +bulk `wc_AesEcbEncrypt()`/`wc_AesEcbDecrypt()` per XTS call, which lands on this +port's ECB case. An `XtsAes` tagged with the port's devId is therefore offloaded +with no XTS code here, given: + +- `HAVE_AES_ECB` (`--enable-aesecb`; configure adds it for `--with-mpfs-athena` + with `--enable-aesxts`). Without it XTS runs a block at a time through + `wc_AesEncryptDirect()`, slower than software. +- Distinct input and output buffers; the helper is not used in place. +- A 64-byte XTS key (AES-256). AES-128-XTS stays in software. + +The tweak encrypt and the stealing tail arrive as single-block calls and stay in +software, as do the GF(2^128) tweak multiply and both XORs. XTS therefore gains +less than bulk CTR, and improves with call size since the engine setup is paid +once per call - measure at the size the application uses. + +Stock benchmark, MPFS250TS Video Kit, E51 at 600 MHz, bare metal; the software +column is wolfCrypt's default cache-line-touch-hardened C AES for RISC-V: + +| Call size | AES-256-XTS software | AES-256-XTS engine | AES-256-CTR engine | SHA-384 engine | +|---|---|---|---|---| +| 512 B | 48 KiB/s | 1.23 MiB/s | 12.9 MiB/s | 11.4 MiB/s | +| 1 KiB | 49 KiB/s | 2.06 MiB/s | 14.6 MiB/s | 13.2 MiB/s | +| 4 KiB | 49 KiB/s | 4.2 MiB/s | 16.1 MiB/s | 14.8 MiB/s | + +Software SHA-384 is 5.8 MiB/s. ## Using it @@ -115,6 +146,7 @@ except `WC_MPFS_ATHENA_DEVID`, which `settings.h` sets so it can also drive |---|---|---| | `WC_MPFS_ATHENA_DEVID` | `0xA7` | crypto callback device id | | `WC_MPFS_ATHENA_HASH_SLOTS` | 4 | concurrent offloaded SHA-384 contexts when there is no heap | +| `WC_MPFS_ATHENA_AES_MIN_SZ` | 32 | smallest AES-ECB request the engine serves; self-test needs twice this as scratch | | `WC_MPFS_ATHENA_BASE_ADDR` | `0x22000000` | user-crypto base, for `g_user_crypto_base_addr` | | `WC_MPFS_ATHENA_REG_BASE` | `0x20127000` | `ATHENAREG` | | `WC_MPFS_ATHENA_SYSREG_BASE` | `0x20002000` | MSS SYSREG | @@ -146,6 +178,10 @@ None of this is in the CAL headers. report success. - **The AES counter is advanced in place**, so consecutive calls on one context continue the keystream. +- **The engine's CTR counter is 32 bits wide.** It carries nothing past the low + word, where `wc_AesCtrEncrypt()` counts over all 128. The port stops each + transfer at that wrap and recomputes the counter itself; the self-test covers + a stream across it. - **The DMA entry points do nothing.** `CALHashDMA()` and `aesf5200dma()` are declared, but the shipped archive is built with `USE_X52EXEC_DMA 0` and they return success without moving data. The port uses the non-DMA calls. @@ -178,7 +214,9 @@ None of this is in the CAL headers. ## Not yet offloaded -The CAL also exposes SHA-1/224/256/512, AES-128/192, AES-GCM/CCM/key-wrap, HMAC, -DRBG/NRBG, ECDSA, ECDH, RSA, DSA and DH. None of those have been run on this -silicon, so they are left in software rather than shipped untested. ECDSA P-384 -verify is the one most worth adding next. +The CAL also exposes SHA-1/224/256/512, AES-128/192, AES-CBC/CFB/OFB, +AES-GCM/CCM/key-wrap, HMAC, DRBG/NRBG, ECDSA, ECDH, RSA, DSA and DH. None of +those have been run on this silicon, so they are left in software rather than +shipped untested. ECDSA P-384 verify is the one most worth adding next; +AES-128-ECB (for AES-128-XTS) is a one-line `SATSYMTYPE_AES128` lookup once +wanted. diff --git a/wolfcrypt/src/port/microchip/mpfs_athena.c b/wolfcrypt/src/port/microchip/mpfs_athena.c index e3d66df0c48..1c1f8b77e26 100644 --- a/wolfcrypt/src/port/microchip/mpfs_athena.c +++ b/wolfcrypt/src/port/microchip/mpfs_athena.c @@ -19,9 +19,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* SHA-384 and AES-256-CTR on the PolarFire SoC Athena F5200, through the - * wolfCrypt crypto callback. See wolfssl/wolfcrypt/port/microchip/mpfs_athena.h - * and the README in this directory. */ +/* SHA-384, AES-256-CTR and AES-256-ECB on the PolarFire SoC Athena F5200, + * through the wolfCrypt crypto callback. See + * wolfssl/wolfcrypt/port/microchip/mpfs_athena.h and the README in this + * directory. */ #ifdef HAVE_CONFIG_H #include @@ -263,6 +264,43 @@ static int wc_MpfsAthenaAesCtr(const word32* key, byte* ctr, const byte* in, } #endif /* WOLFSSL_AES_COUNTER && !NO_AES */ +#if defined(HAVE_AES_ECB) && !defined(NO_AES) +/* sz must be whole blocks. ECB carries no chaining state, so each call is + * self-contained and the direction comes from the caller. */ +static int wc_MpfsAthenaAesEcb(const word32* key, const byte* in, byte* out, + word32 sz, int enc) +{ + SATR rc; + int ret; + + ret = wolfSSL_CryptHwMutexLock(); + if (ret != 0) { + return ret; + } + if (enc) { + rc = CALSymEncrypt(SATSYMTYPE_AES256, (const SATUINT32_t*)key, + SATSYMMODE_ECB, NULL, SAT_FALSE, in, out, (SATUINT32_t)sz); + } + else { + rc = CALSymDecrypt(SATSYMTYPE_AES256, (const SATUINT32_t*)key, + SATSYMMODE_ECB, NULL, SAT_FALSE, in, out, (SATUINT32_t)sz); + } + if (rc == SATR_SUCCESS) { + /* As with CTR, the call above only starts the transfer. */ + rc = CALSymTrfRes(SAT_TRUE); + } + if (rc != SATR_SUCCESS) { + mpfsAthenaCalRc = (int)rc; + } + wolfSSL_CryptHwMutexUnLock(); + + if (rc != SATR_SUCCESS) { + return WC_HW_E; + } + return 0; +} +#endif /* HAVE_AES_ECB && !NO_AES */ + #ifdef WOLFSSL_SHA384 /* Hangs on wc_Sha384's devCtx; the callback's free and copy events own its @@ -621,16 +659,32 @@ static int wc_MpfsAthenaShaCopyDev(wc_CryptoInfo* info) #endif /* WOLFSSL_SHA384 */ #if defined(WOLFSSL_AES_COUNTER) && !defined(NO_AES) +/* Big-endian add over the whole counter block, the increment wolfCrypt's own + * CTR does. */ +static void wc_MpfsAthenaCtrAdd(byte* ctr, word32 blocks) +{ + word32 carry = blocks; + int i; + + for (i = WC_AES_BLOCK_SIZE - 1; i >= 0 && carry != 0; i--) { + word32 sum = (word32)ctr[i] + (carry & 0xFFU); + ctr[i] = (byte)sum; + carry = (carry >> 8) + (sum >> 8); + } +} + /* AES-256-CTR, one path for both directions (info->cipher.enc unread). * Keystream state is wolfCrypt's own: aes->tmp holds one front-aligned block, * aes->left counts its unconsumed tail, aes->reg is already past it. Every - * bail-out is above the first engine call, or the counter desynchronises. */ + * decline is above the first engine call; after one, a failure returns with + * aes->reg still matching the output already written. */ static int wc_MpfsAthenaAesCtrDev(wc_CryptoInfo* info) { Aes* aes = info->cipher.aesctr.aes; const byte* in = info->cipher.aesctr.in; byte* out = info->cipher.aesctr.out; word32 sz = info->cipher.aesctr.sz; + byte ctr[WC_AES_BLOCK_SIZE]; word32 whole; word32 processed; int ret; @@ -661,15 +715,31 @@ static int wc_MpfsAthenaAesCtrDev(wc_CryptoInfo* info) sz -= processed; } + /* The engine carries only the low 32 bits of the counter block; + * wc_AesCtrEncrypt() counts over all 128. Stop each transfer at that wrap + * and recompute the counter here. */ whole = sz - (sz % WC_AES_BLOCK_SIZE); - if (whole > 0) { - ret = wc_MpfsAthenaAesCtr(aes->devKey, (byte*)aes->reg, in, out, whole); + while (whole > 0) { + word32 run = whole; + word32 low = ((word32)((byte*)aes->reg)[12] << 24) | + ((word32)((byte*)aes->reg)[13] << 16) | + ((word32)((byte*)aes->reg)[14] << 8) | + (word32)((byte*)aes->reg)[15]; + word32 toWrap = ~low + 1U; /* blocks left before the low word wraps */ + + if (toWrap != 0 && run / WC_AES_BLOCK_SIZE > toWrap) { + run = toWrap * WC_AES_BLOCK_SIZE; + } + XMEMCPY(ctr, aes->reg, WC_AES_BLOCK_SIZE); + ret = wc_MpfsAthenaAesCtr(aes->devKey, ctr, in, out, run); if (ret != 0) { return ret; } - in += whole; - out += whole; - sz -= whole; + wc_MpfsAthenaCtrAdd((byte*)aes->reg, run / WC_AES_BLOCK_SIZE); + in += run; + out += run; + sz -= run; + whole -= run; } if (sz > 0) { @@ -680,12 +750,13 @@ static int wc_MpfsAthenaAesCtrDev(wc_CryptoInfo* info) byte ks[WC_AES_BLOCK_SIZE]; XMEMSET(ks, 0, sizeof(ks)); - ret = wc_MpfsAthenaAesCtr(aes->devKey, (byte*)aes->reg, ks, ks, - WC_AES_BLOCK_SIZE); + XMEMCPY(ctr, aes->reg, WC_AES_BLOCK_SIZE); + ret = wc_MpfsAthenaAesCtr(aes->devKey, ctr, ks, ks, WC_AES_BLOCK_SIZE); if (ret != 0) { ForceZero(ks, sizeof(ks)); return ret; } + wc_MpfsAthenaCtrAdd((byte*)aes->reg, 1U); xorbufout(out, in, ks, sz); XMEMCPY(aes->tmp, ks, WC_AES_BLOCK_SIZE); aes->left = WC_AES_BLOCK_SIZE - sz; @@ -697,6 +768,45 @@ static int wc_MpfsAthenaAesCtrDev(wc_CryptoInfo* info) } #endif /* WOLFSSL_AES_COUNTER && !NO_AES */ +#if defined(HAVE_AES_ECB) && !defined(NO_AES) +/* AES-256-ECB, which is what carries AES-XTS: _AesXtsHelper() masks the whole + * buffer with the tweak chain and issues one ECB call for it, so the engine + * gets one bulk transfer per XTS call. Needs HAVE_AES_ECB; without it XTS runs + * block-at-a-time through wc_AesEncryptDirect(), slower than software. */ +static int wc_MpfsAthenaAesEcbDev(wc_CryptoInfo* info) +{ + Aes* aes = info->cipher.aesecb.aes; + const byte* in = info->cipher.aesecb.in; + byte* out = info->cipher.aesecb.out; + word32 sz = info->cipher.aesecb.sz; + int ret; + + if (aes == NULL || in == NULL || out == NULL) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* AES-256 only, as for CTR: the engine does 128 and 192, but only 256 has + * been run on silicon here. */ + if (aes->keylen != 32) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + /* A partial block has no ECB meaning; leave it to software to reject. */ + if (sz == 0 || (sz % WC_AES_BLOCK_SIZE) != 0) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + if (sz < WC_MPFS_ATHENA_AES_MIN_SZ) { + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + } + + ret = wc_MpfsAthenaAesEcb(aes->devKey, in, out, sz, info->cipher.enc); + if (ret != 0) { + return ret; + } + + wc_MpfsAthenaCountCall(); + return 0; +} +#endif /* HAVE_AES_ECB && !NO_AES */ + int wc_MpfsAthena_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) { (void)devId; @@ -719,11 +829,19 @@ int wc_MpfsAthena_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) break; #endif -#if defined(WOLFSSL_AES_COUNTER) && !defined(NO_AES) +#if (defined(WOLFSSL_AES_COUNTER) || defined(HAVE_AES_ECB)) && \ + !defined(NO_AES) case WC_ALGO_TYPE_CIPHER: +#if defined(WOLFSSL_AES_COUNTER) if (info->cipher.type == WC_CIPHER_AES_CTR) { return wc_MpfsAthenaAesCtrDev(info); } +#endif +#if defined(HAVE_AES_ECB) + if (info->cipher.type == WC_CIPHER_AES_ECB) { + return wc_MpfsAthenaAesEcbDev(info); + } +#endif break; #endif @@ -837,6 +955,17 @@ static const byte mpfsAthenaKatAesCtr[32] = { 0x70, 0xe2, 0x02, 0xc6, 0xd7, 0x90, 0x4e, 0x4a, 0x4d, 0x0f, 0xe1, 0x4a, 0x6e, 0xf8, 0x3e, 0xd0 }; + +/* AES-256-CTR of the 48 bytes 0x00..0x2F under the same key, counter block + * 0x00..0x0B followed by ff ff ff fe. */ +static const byte mpfsAthenaKatAesCtrWrap[48] = { + 0x8b, 0x65, 0xb1, 0x2c, 0xf3, 0xb6, 0x96, 0x55, + 0xb3, 0xa0, 0x70, 0x5f, 0x80, 0xd8, 0x41, 0x6b, + 0x8d, 0x43, 0xf8, 0x94, 0x09, 0x22, 0xf4, 0x11, + 0xae, 0x57, 0x8a, 0x36, 0x01, 0x98, 0x60, 0x5b, + 0xfa, 0x87, 0x6a, 0xdc, 0xe3, 0xea, 0xe5, 0x81, + 0xf1, 0xdf, 0x7e, 0xb2, 0xd3, 0x26, 0x39, 0xae +}; #endif #ifdef WOLFSSL_SHA384 @@ -845,9 +974,10 @@ static const byte mpfsAthenaKatAesCtr[32] = { static int wc_MpfsAthenaKatSha384(byte* buf, byte* digest) { wc_Sha384 sha; + word32 calls; int ret; - mpfsAthenaCbCalls = 0; + calls = mpfsAthenaCbCalls; ret = wc_InitSha384_ex(&sha, NULL, WC_MPFS_ATHENA_DEVID); if (ret != 0) { @@ -875,7 +1005,7 @@ static int wc_MpfsAthenaKatSha384(byte* buf, byte* digest) return ret; } - if (mpfsAthenaCbCalls == 0) { + if (mpfsAthenaCbCalls == calls) { mpfsAthenaFailWhat = "SHA-384 never reached the engine (software)"; return WC_HW_E; } @@ -964,6 +1094,7 @@ static int wc_MpfsAthenaKatAesCtr(const byte* in) byte key[32]; byte ctr[WC_AES_BLOCK_SIZE]; byte out[32]; + word32 calls; int ret; int i; @@ -975,9 +1106,9 @@ static int wc_MpfsAthenaKatAesCtr(const byte* in) } /* One 32-byte call: two whole blocks in a single engine transfer. */ - mpfsAthenaCbCalls = 0; + calls = mpfsAthenaCbCalls; ret = wc_MpfsAthenaKatAesRun(key, ctr, in, out, 32); - if (ret == 0 && mpfsAthenaCbCalls == 0) { + if (ret == 0 && mpfsAthenaCbCalls == calls) { mpfsAthenaFailWhat = "AES-256-CTR never reached the engine (software)"; ret = WC_HW_E; } @@ -1012,6 +1143,20 @@ static int wc_MpfsAthenaKatAesCtr(const byte* in) } } + /* Counter 0x...fffffffe for 48 bytes: the third block is past the low + * word's wrap, which the engine does not carry on its own. */ + if (ret == 0) { + byte wrap[48]; + ctr[12] = 0xff; ctr[13] = 0xff; ctr[14] = 0xff; ctr[15] = 0xfe; + ret = wc_MpfsAthenaKatAesRun(key, ctr, in, wrap, sizeof(wrap)); + if (ret == 0 && + XMEMCMP(wrap, mpfsAthenaKatAesCtrWrap, sizeof(wrap)) != 0) { + mpfsAthenaFailWhat = "AES-256-CTR counter does not carry past " + "32 bits"; + ret = WC_HW_E; + } + } + if (ret != 0 && mpfsAthenaFailWhat == NULL) { mpfsAthenaFailWhat = "AES-256-CTR"; } @@ -1020,13 +1165,166 @@ static int wc_MpfsAthenaKatAesCtr(const byte* in) } #endif /* WOLFSSL_AES_COUNTER && !NO_AES */ -/* 256 message bytes, plus room for a digest after them when SHA-384 is in the - * build. WC_SHA384_DIGEST_SIZE only exists under WOLFSSL_SHA384, so an AES-only - * build must not name it. */ +#if defined(HAVE_AES_ECB) && !defined(NO_AES) +/* AES-256-ECB of 0x00..0x1F under the key 0x00..0x1F. Blocks are independent, + * so this is also the first 32 bytes of the longer run. */ +static const byte mpfsAthenaKatAesEcb[32] = { + 0x5a, 0x6e, 0x04, 0x57, 0x08, 0xfb, 0x71, 0x96, + 0xf0, 0x2e, 0x55, 0x3d, 0x02, 0xc3, 0xa6, 0x92, + 0xe9, 0xc3, 0xef, 0x8a, 0xb2, 0x34, 0x53, 0xe6, + 0xf0, 0x74, 0x9c, 0xd6, 0x36, 0xe7, 0xa8, 0x8e +}; + +/* At least WC_MPFS_ATHENA_AES_MIN_SZ, or the engine declines and the count + * check means nothing; and at least the two blocks the vector covers. */ +#if WC_MPFS_ATHENA_AES_MIN_SZ > 32 + /* 16, not WC_AES_BLOCK_SIZE: that is an enum, which the preprocessor + * reads as 0. */ + #define WC_MPFS_ATHENA_KAT_ECB_SZ \ + (((WC_MPFS_ATHENA_AES_MIN_SZ + 15) / 16) * 16) +#else + #define WC_MPFS_ATHENA_KAT_ECB_SZ 32 +#endif + +/* in and out are WC_MPFS_ATHENA_KAT_ECB_SZ scratch each. Both shapes are run: + * distinct buffers, and in place, which is what XTS sends. */ +static int wc_MpfsAthenaKatAesEcb(byte* in, byte* out) +{ + Aes enc; +#ifdef HAVE_AES_DECRYPT + Aes dec; +#endif + byte key[32]; + word32 calls; + int ret; + int i; + + for (i = 0; i < (int)sizeof(key); i++) { + key[i] = (byte)i; + } + /* The SHA-384 KAT leaves its digest in the shared buffer, so the pattern + * is laid down here rather than assumed. */ + for (i = 0; i < WC_MPFS_ATHENA_KAT_ECB_SZ; i++) { + in[i] = (byte)i; + } + + ret = wc_AesInit(&enc, NULL, WC_MPFS_ATHENA_DEVID); + if (ret != 0) { + ForceZero(key, sizeof(key)); + return ret; + } +#ifdef HAVE_AES_DECRYPT + ret = wc_AesInit(&dec, NULL, WC_MPFS_ATHENA_DEVID); + if (ret != 0) { + wc_AesFree(&enc); + ForceZero(key, sizeof(key)); + return ret; + } +#endif + ret = wc_AesSetKey(&enc, key, sizeof(key), NULL, AES_ENCRYPTION); +#ifdef HAVE_AES_DECRYPT + if (ret == 0) { + ret = wc_AesSetKey(&dec, key, sizeof(key), NULL, AES_DECRYPTION); + } +#endif + + /* One call of the minimum size: a single engine transfer. */ + if (ret == 0) { + calls = mpfsAthenaCbCalls; + ret = wc_AesEcbEncrypt(&enc, out, in, WC_MPFS_ATHENA_KAT_ECB_SZ); + if (ret == 0 && mpfsAthenaCbCalls == calls) { + mpfsAthenaFailWhat = + "AES-256-ECB never reached the engine (software)"; + ret = WC_HW_E; + } + if (ret == 0 && XMEMCMP(out, mpfsAthenaKatAesEcb, + sizeof(mpfsAthenaKatAesEcb)) != 0) { + mpfsAthenaFailWhat = "AES-256-ECB known-answer mismatch"; + ret = WC_HW_E; + } + } + +#ifdef HAVE_AES_DECRYPT + /* Decrypt in place and expect the pattern back. */ + if (ret == 0) { + calls = mpfsAthenaCbCalls; + ret = wc_AesEcbDecrypt(&dec, out, out, WC_MPFS_ATHENA_KAT_ECB_SZ); + if (ret == 0 && mpfsAthenaCbCalls == calls) { + mpfsAthenaFailWhat = + "AES-256-ECB decrypt never reached the engine (software)"; + ret = WC_HW_E; + } + for (i = 0; ret == 0 && i < WC_MPFS_ATHENA_KAT_ECB_SZ; i++) { + if (out[i] != (byte)i) { + mpfsAthenaFailWhat = "AES-256-ECB in-place decrypt mismatch"; + ret = WC_HW_E; + } + } + } +#endif + + /* Encrypt in place, the XTS shape, from a fresh copy of the pattern. */ + if (ret == 0) { + for (i = 0; i < WC_MPFS_ATHENA_KAT_ECB_SZ; i++) { + out[i] = (byte)i; + } + calls = mpfsAthenaCbCalls; + ret = wc_AesEcbEncrypt(&enc, out, out, WC_MPFS_ATHENA_KAT_ECB_SZ); + if (ret == 0 && mpfsAthenaCbCalls == calls) { + mpfsAthenaFailWhat = + "AES-256-ECB in-place encrypt never reached the engine"; + ret = WC_HW_E; + } + if (ret == 0 && XMEMCMP(out, mpfsAthenaKatAesEcb, + sizeof(mpfsAthenaKatAesEcb)) != 0) { + mpfsAthenaFailWhat = "AES-256-ECB in-place encrypt mismatch"; + ret = WC_HW_E; + } + } + +#if WC_MPFS_ATHENA_AES_MIN_SZ > 16 + /* One block is below the minimum: software must serve it on this same + * context, with no engine call, and still get the vector right. */ + if (ret == 0) { + calls = mpfsAthenaCbCalls; + ret = wc_AesEcbEncrypt(&enc, out, in, WC_AES_BLOCK_SIZE); + if (ret == 0 && mpfsAthenaCbCalls != calls) { + mpfsAthenaFailWhat = "AES-256-ECB one block went to the engine"; + ret = WC_HW_E; + } + if (ret == 0 && XMEMCMP(out, mpfsAthenaKatAesEcb, + WC_AES_BLOCK_SIZE) != 0) { + mpfsAthenaFailWhat = "AES-256-ECB software block mismatch"; + ret = WC_HW_E; + } + } +#endif + + if (ret != 0 && mpfsAthenaFailWhat == NULL) { + mpfsAthenaFailWhat = "AES-256-ECB"; + } +#ifdef HAVE_AES_DECRYPT + wc_AesFree(&dec); +#endif + wc_AesFree(&enc); + ForceZero(key, sizeof(key)); + return ret; +} +#endif /* HAVE_AES_ECB && !NO_AES */ + +/* 256 message bytes plus room for a 48-byte SHA-384 digest (a literal: + * WC_SHA384_DIGEST_SIZE is an enum, which the #if below reads as 0). The ECB + * run's in/out pair only exceeds that with a raised AES minimum. */ #ifdef WOLFSSL_SHA384 - #define WC_MPFS_ATHENA_KAT_BUF_SZ (256 + WC_SHA384_DIGEST_SIZE) + #define WC_MPFS_ATHENA_KAT_MSG_SZ (256 + 48) +#else + #define WC_MPFS_ATHENA_KAT_MSG_SZ (256) +#endif +#if defined(HAVE_AES_ECB) && !defined(NO_AES) && \ + (2 * WC_MPFS_ATHENA_KAT_ECB_SZ) > WC_MPFS_ATHENA_KAT_MSG_SZ + #define WC_MPFS_ATHENA_KAT_BUF_SZ (2 * WC_MPFS_ATHENA_KAT_ECB_SZ) #else - #define WC_MPFS_ATHENA_KAT_BUF_SZ (256) + #define WC_MPFS_ATHENA_KAT_BUF_SZ WC_MPFS_ATHENA_KAT_MSG_SZ #endif int wc_MpfsAthena_SelfTest(void) @@ -1064,6 +1362,11 @@ int wc_MpfsAthena_SelfTest(void) ret = wc_MpfsAthenaKatAesCtr(buf); } #endif +#if defined(HAVE_AES_ECB) && !defined(NO_AES) + if (ret == 0) { + ret = wc_MpfsAthenaKatAesEcb(buf, buf + WC_MPFS_ATHENA_KAT_ECB_SZ); + } +#endif #ifdef WOLFSSL_SMALL_STACK XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfssl/wolfcrypt/port/microchip/mpfs_athena.h b/wolfssl/wolfcrypt/port/microchip/mpfs_athena.h index 4ca6660529a..f51e839e4ef 100644 --- a/wolfssl/wolfcrypt/port/microchip/mpfs_athena.h +++ b/wolfssl/wolfcrypt/port/microchip/mpfs_athena.h @@ -19,8 +19,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* SHA-384 and AES-256-CTR on the Athena F5200 "TeraFire" user cryptoprocessor - * of the PolarFire SoC "S" grade parts, through the wolfCrypt crypto callback. +/* SHA-384, AES-256-CTR and AES-256-ECB on the Athena F5200 "TeraFire" user + * cryptoprocessor of the PolarFire SoC "S" grade parts, through the wolfCrypt + * crypto callback. The ECB path is what carries AES-256-XTS. * * The engine is driven by Microchip's CAL (Crypto Abstraction Library), which * is referenced by include path and never vendored: it carries a Mercury @@ -97,6 +98,13 @@ #define WC_MPFS_ATHENA_HASH_SLOTS 4 #endif +/* Smallest AES-ECB request the engine serves; shorter ones go to software. + * XTS sends its tweak and stealing tail as one-block calls, and a CAL round + * trip per block loses to software. Self-test needs twice this as scratch. */ +#ifndef WC_MPFS_ATHENA_AES_MIN_SZ + #define WC_MPFS_ATHENA_AES_MIN_SZ 32 +#endif + WOLFSSL_API int wc_MpfsAthena_Init(void); WOLFSSL_API int wc_MpfsAthena_RegisterDevice(int devId); WOLFSSL_API void wc_MpfsAthena_UnRegisterDevice(int devId); diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 6564f4aa52a..b26bab7b152 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -498,7 +498,8 @@ #ifndef WOLF_CRYPTO_CB_FREE #define WOLF_CRYPTO_CB_FREE #endif - /* Serves SHA-384 and AES-256-CTR only; software covers the rest. */ + /* Serves SHA-384, AES-256-CTR and AES-256-ECB only; software covers the + * rest. */ #if defined(WOLF_CRYPTO_CB_ONLY_SHA512) || defined(WOLF_CRYPTO_CB_ONLY_AES) #error "WOLFSSL_MPFS_ATHENA needs software SHA-512 and AES" #endif