Implement the AES-GCM mode (HACE10[6:4] = 0b101) used by the AST2700 crypto engine: decode the GCM selection, read the 96-bit IV from the context buffer, operate on the exact data length (GCM handles a partial final block itself), and write the 128-bit authentication tag to the tag buffer (HACE18/HACE8C). The hardware GCM path is only used without associated data (the driver falls back to software otherwise), so AAD is not modelled and a non-zero HACE14 is reported as unimplemented.
Signed-off-by: Jamin Lin <[email protected]> --- hw/misc/aspeed_hace.c | 70 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index 60dedee6a3..b84f83c0d3 100644 --- a/hw/misc/aspeed_hace.c +++ b/hw/misc/aspeed_hace.c @@ -31,6 +31,9 @@ /* HACE0C[27:0] holds the crypto data length */ #define CRYPT_DATA_LEN_MASK 0x0FFFFFFF #define R_CRYPT_CMD (0x10 / 4) +/* AES-GCM associated data length (HACE14) and tag write buffer (HACE18) */ +#define R_CRYPT_GCM_ADD_LEN (0x14 / 4) +#define R_CRYPT_GCM_TAG (0x18 / 4) /* Crypto engine command register (HACE10) bits */ #define CRYPT_CMD_ENCRYPT BIT(7) #define CRYPT_CMD_ISR_EN BIT(12) @@ -43,6 +46,7 @@ #define CRYPT_CMD_ECB (0x0 << 4) #define CRYPT_CMD_CBC (0x1 << 4) #define CRYPT_CMD_CTR (0x4 << 4) +#define CRYPT_CMD_GCM (0x5 << 4) /* AES key length HACE10[3:2] */ #define CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2) #define CRYPT_CMD_AES256 (0x2 << 2) @@ -58,10 +62,15 @@ #define CRYPT_CTX_KEY_OFFSET 0x10 #define CRYPT_CTX_SIZE 0x30 +/* AES-GCM uses a 96-bit IV and a 128-bit authentication tag */ +#define CRYPT_GCM_IV_LEN 12 +#define CRYPT_GCM_TAG_LEN 16 + /* AST2700 64-bit DMA high address registers for the crypto command */ #define R_CRYPT_SRC_HI (0x80 / 4) #define R_CRYPT_DEST_HI (0x84 / 4) #define R_CRYPT_CONTEXT_HI (0x88 / 4) +#define R_CRYPT_GCM_TAG_HI (0x8c / 4) #define R_STATUS (0x1c / 4) #define HASH_IRQ BIT(9) @@ -598,6 +607,9 @@ static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg, case CRYPT_CMD_CTR: *mode = QCRYPTO_CIPHER_MODE_CTR; break; + case CRYPT_CMD_GCM: + *mode = QCRYPTO_CIPHER_MODE_GCM; + break; default: return false; } @@ -691,11 +703,12 @@ static uint64_t crypt_get_addr(AspeedHACEState *s, int reg, int reg_hi) } /* - * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are - * either single contiguous buffers (direct access mode) or scatter-gather - * lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the IV/key come from - * the context buffer (HACE08). For CBC the resulting chaining IV is written - * back to the context buffer so the driver can continue the chain. + * Perform an AES/DES/3DES ECB/CBC/CTR or AES-GCM operation. The source and + * destination are either single contiguous buffers (direct access mode) or + * scatter-gather lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the + * IV/key come from the context buffer (HACE08). For CBC and CTR the resulting + * chaining state is written back to the context buffer so the driver can + * continue; for GCM the authentication tag is written to the tag buffer. */ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) { @@ -705,6 +718,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) g_autoptr(QCryptoCipher) cipher = NULL; g_autofree uint8_t *src_buf = NULL; g_autofree uint8_t *dst_buf = NULL; + uint8_t tag[CRYPT_GCM_TAG_LEN]; uint8_t ctx[CRYPT_CTX_SIZE]; Error *local_err = NULL; QCryptoCipherMode mode; @@ -713,10 +727,13 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) uint64_t ctx_addr; uint64_t src_addr; uint64_t dst_addr; + uint64_t tag_addr; + uint32_t aad_len; size_t iv_offset; size_t blocklen; size_t buf_len; size_t keylen; + size_t ivlen; bool status; if (len == 0) { @@ -729,6 +746,20 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) return; } + /* GCM uses a 96-bit IV; the block modes use a full-block IV. */ + ivlen = (mode == QCRYPTO_CIPHER_MODE_GCM) ? CRYPT_GCM_IV_LEN : blocklen; + + /* + * The hardware GCM path is only exercised without associated data (the + * driver falls back to software when there is any), so AAD is not modelled. + */ + aad_len = s->regs[R_CRYPT_GCM_ADD_LEN] & CRYPT_DATA_LEN_MASK; + if (mode == QCRYPTO_CIPHER_MODE_GCM && aad_len != 0) { + qemu_log_mask(LOG_UNIMP, + "%s: GCM associated data is not implemented\n", __func__); + return; + } + /* Fetch the IV and key from the context buffer in DRAM. */ ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT, R_CRYPT_CONTEXT_HI); if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED, @@ -753,7 +784,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) } if (mode != QCRYPTO_CIPHER_MODE_ECB && - qcrypto_cipher_setiv(cipher, ctx + iv_offset, blocklen, + qcrypto_cipher_setiv(cipher, ctx + iv_offset, ivlen, &local_err) < 0) { qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv failed: %s\n", __func__, error_get_pretty(local_err)); @@ -764,9 +795,11 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) /* * Round the working buffers up to a whole block. Block modes are already * block-aligned; the stream-like CTR mode may leave a partial final block - * that the engine still processes a full block at a time. + * that the engine still processes a full block at a time. GCM handles a + * partial final block itself, so it operates on the exact length. */ - buf_len = QEMU_ALIGN_UP(len, blocklen); + buf_len = (mode == QCRYPTO_CIPHER_MODE_GCM) ? + len : QEMU_ALIGN_UP(len, blocklen); src_buf = g_malloc0(buf_len); dst_buf = g_malloc0(buf_len); @@ -850,6 +883,24 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n", __func__, ctx_addr + iv_offset); } + } else if (mode == QCRYPTO_CIPHER_MODE_GCM) { + /* + * GCM authenticates the message and writes the resulting tag to the + * dedicated tag buffer (HACE18/HACE8C). + */ + if (qcrypto_cipher_gettag(cipher, tag, sizeof(tag), &local_err) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher gettag failed: " + "%s\n", __func__, error_get_pretty(local_err)); + error_free(local_err); + return; + } + tag_addr = crypt_get_addr(s, R_CRYPT_GCM_TAG, R_CRYPT_GCM_TAG_HI); + if (address_space_write(&s->dram_as, tag_addr, MEMTXATTRS_UNSPECIFIED, + tag, sizeof(tag))) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to write tag, addr=0x%" HWADDR_PRIx "\n", + __func__, tag_addr); + } } } @@ -980,6 +1031,9 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data, case R_CRYPT_CONTEXT_HI: data &= ahc->key_hi_mask; break; + case R_CRYPT_GCM_TAG_HI: + data &= ahc->dest_hi_mask; + break; default: break; } -- 2.43.0
