From 6faac3accbd11e3a0a75a2247d7a93400a7f39d3 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Fri, 13 Dec 2024 14:55:36 +0000 Subject: [PATCH 1/6] [ot] hw/opentitan: ot_hmac: Add copyright/author information --- hw/opentitan/ot_hmac.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index d63a4a2b03e4..993292e97f1e 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -2,9 +2,11 @@ * QEMU OpenTitan HMAC device * * Copyright (c) 2022-2024 Rivos, Inc. + * Copyright (c) 2024 lowRISC contributors. * * Author(s): * Loïc Lefort + * Alex Jones * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 0385aee97072d8f6e1a59344f186d011c97670e8 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 12 Dec 2024 13:58:25 +0000 Subject: [PATCH 2/6] [ot] hw/opentitan: ot_hmac: Remove remaining unnecessary TODO This removes the last remaining TODO from `ot_hmac` (after the multi-mode SHA384/SHA512 support is added). Looking at the RTL, it wipes the secret key regardless of whether the engine is active or not, even if mid-operation. So this TODO can be safely removed to mimic the real hardware. Also fixes a comment line length issue from a previous commit. --- hw/opentitan/ot_hmac.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 993292e97f1e..51bcd9ffa85f 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -683,7 +683,10 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, if (val32 & R_CMD_HASH_STOP_MASK) { s->regs->cmd = R_CMD_HASH_STOP_MASK; - /* trigger delayed processing of FIFO until the next block is processed. */ + /* + * trigger delayed processing of FIFO until the next block is + * processed. + */ ibex_irq_set(&s->clkmgr, true); ot_hmac_process_fifo(s); } @@ -716,7 +719,6 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, break; case R_WIPE_SECRET: - /* TODO ignore write if engine is not idle? */ s->regs->wipe_secret = val32; ot_hmac_wipe_buffer(s, s->regs->key, ARRAY_SIZE(s->regs->key)); ot_hmac_wipe_buffer(s, s->regs->digest, ARRAY_SIZE(s->regs->digest)); From c4039889f458e5da40ca6ef67b4f84ce07108846 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 12 Dec 2024 13:34:30 +0000 Subject: [PATCH 3/6] [ot] hw/opentitan: ot_hmac: Implement key/digest size regs & checks This commit implements initial register interface support for the key length and digest size fields in the config register of OpenTitan's HMAC. It checks that the values written are valid one-hot encodings, and maps them as is done in current hardware if not. This also implements some additional checks that the combination of (key length, digest size, HMAC enable) is valid when attempting to run the START/CONTINUE command, otherwise producing a (new type of) software error. Also fixes some comment formatting (code style). --- hw/opentitan/ot_hmac.c | 122 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 10 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 51bcd9ffa85f..4c65e8395ab5 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -87,6 +87,7 @@ REG32(ERR_CODE, 0x1cu) #define R_ERR_CODE_UPDATE_SECRET_KEY_INPROCESS 0x00000003u #define R_ERR_CODE_HASH_START_WHEN_ACTIVE 0x00000004u #define R_ERR_CODE_PUSH_MSG_WHEN_DISALLOWED 0x00000005u +#define R_ERR_CODE_INVALID_CONFIG 0x00000006u REG32(WIPE_SECRET, 0x20u) REG32(KEY_0, 0x24u) REG32(KEY_1, 0x28u) @@ -152,6 +153,11 @@ REG32(MSG_LENGTH_UPPER, 0xe8u) /* length of the whole device MMIO region */ #define OT_HMAC_WHOLE_SIZE (OT_HMAC_FIFO_BASE + OT_HMAC_FIFO_SIZE) +/* value representing 'SHA2_NONE' in the config digest size field */ +#define OT_HMAC_CFG_DIGEST_SHA2_NONE 0x8u +/* value representing 'KEY_NONE' in the config key length field */ +#define OT_HMAC_CFG_KEY_LENGTH_NONE 0x20u + #define R32_OFF(_r_) ((_r_) / sizeof(uint32_t)) #define R_LAST_REG (R_MSG_LENGTH_UPPER) @@ -261,6 +267,62 @@ struct OtHMACState { char *ot_id; }; +typedef enum OtHMACDigestSize { + HMAC_SHA2_NONE, + HMAC_SHA2_256, + HMAC_SHA2_384, + HMAC_SHA2_512, +} OtHMACDigestSize; + +typedef enum OtHMACKeyLength { + HMAC_KEY_NONE, + HMAC_KEY_128, + HMAC_KEY_256, + HMAC_KEY_384, + HMAC_KEY_512, + HMAC_KEY_1024, +} OtHMACKeyLength; + +static inline OtHMACDigestSize ot_hmac_get_digest_size(uint32_t cfg_reg) +{ + switch ((cfg_reg & R_CFG_DIGEST_SIZE_MASK) >> R_CFG_DIGEST_SIZE_SHIFT) { + case 0x1u: + return HMAC_SHA2_256; + case 0x2u: + return HMAC_SHA2_384; + case 0x4u: + return HMAC_SHA2_512; + case 0x8u: + default: + return HMAC_SHA2_NONE; + } +} + +static inline OtHMACKeyLength ot_hmac_get_key_length(uint32_t cfg_reg) +{ + switch ((cfg_reg & R_CFG_KEY_LENGTH_MASK) >> R_CFG_KEY_LENGTH_SHIFT) { + case 0x01u: + return HMAC_KEY_128; + case 0x02u: + return HMAC_KEY_256; + case 0x04u: + return HMAC_KEY_384; + case 0x08u: + return HMAC_KEY_512; + case 0x10u: + return HMAC_KEY_1024; + case 0x20u: + default: + return HMAC_KEY_NONE; + } +} + +static inline bool ot_hmac_key_length_supported(OtHMACDigestSize digest_size, + OtHMACKeyLength key_length) +{ + return !(digest_size == HMAC_SHA2_256 && key_length == HMAC_KEY_1024); +} + static void ot_hmac_update_irqs(OtHMACState *s) { uint32_t levels = s->regs->intr_state & s->regs->intr_enable; @@ -286,8 +348,7 @@ static void ot_hmac_report_error(OtHMACState *s, uint32_t error) static void ot_hmac_writeback_digest_state(OtHMACState *s) { - /* copy intermediary digest to mock HMAC operation for stop/continue - behaviour. */ + /* copy intermediary digest to mock HMAC's stop/continue behaviour. */ /* TODO: add support for SHA2-384 and SHA2-512 */ unsigned digest_length = OT_HMAC_DIGEST_LENGTH / sizeof(uint32_t); for (unsigned i = 0; i < digest_length; i++) { @@ -511,8 +572,10 @@ static uint64_t ot_hmac_regs_read(void *opaque, hwaddr addr, unsigned size) case R_DIGEST_13: case R_DIGEST_14: case R_DIGEST_15: - /* We use a sha library in little endian by default, so we only need to - swap if the swap config is 1 (big endian digest). */ + /* + * We use a sha library in little endian by default, so we only need to + * swap if the swap config is 1 (big endian digest). + */ if (s->regs->cfg & R_CFG_DIGEST_SWAP_MASK) { val32 = s->regs->digest[reg - R_DIGEST_0]; } else { @@ -591,6 +654,9 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, uint32_t pc = ibex_get_current_pc(); trace_ot_hmac_io_write(s->ot_id, (uint32_t)addr, REG_NAME(reg), val32, pc); + OtHMACDigestSize digest_size; + OtHMACKeyLength key_length; + switch (reg) { case R_INTR_STATE: s->regs->intr_state &= ~(val32 & INTR_MASK); @@ -614,12 +680,27 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, break; } - s->regs->cfg = - val32 & + val32 &= (R_CFG_HMAC_EN_MASK | R_CFG_SHA_EN_MASK | R_CFG_ENDIAN_SWAP_MASK | R_CFG_DIGEST_SWAP_MASK | R_CFG_KEY_SWAP_MASK | R_CFG_DIGEST_SIZE_MASK | R_CFG_KEY_LENGTH_MASK); + /* If the digest size is invalid, it gets mapped to SHA2_NONE. */ + digest_size = ot_hmac_get_digest_size(val32); + if (digest_size == HMAC_SHA2_NONE) { + val32 &= ~R_CFG_DIGEST_SIZE_MASK; + val32 |= OT_HMAC_CFG_DIGEST_SHA2_NONE << R_CFG_DIGEST_SIZE_SHIFT; + } + + /* If the key length is invalid, it gets mapped to KEY_NONE. */ + key_length = ot_hmac_get_key_length(val32); + if (key_length == HMAC_KEY_NONE) { + val32 &= ~R_CFG_KEY_LENGTH_MASK; + val32 |= OT_HMAC_CFG_KEY_LENGTH_NONE << R_CFG_KEY_LENGTH_SHIFT; + } + + s->regs->cfg = val32; + /* clear digest when SHA is disabled */ if (!(s->regs->cfg & R_CFG_SHA_EN_MASK)) { ot_hmac_wipe_buffer(s, s->regs->digest, @@ -627,6 +708,23 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, } break; case R_CMD: + if (val32 & (R_CMD_HASH_START_MASK | R_CMD_HASH_CONTINUE_MASK)) { + digest_size = ot_hmac_get_digest_size(s->regs->cfg); + if (digest_size == HMAC_SHA2_NONE) { + ot_hmac_report_error(s, R_ERR_CODE_INVALID_CONFIG); + break; + } + + if (s->regs->cfg & R_CFG_HMAC_EN_MASK) { + key_length = ot_hmac_get_key_length(s->regs->cfg); + if (key_length == HMAC_KEY_NONE || + !ot_hmac_key_length_supported(digest_size, key_length)) { + ot_hmac_report_error(s, R_ERR_CODE_INVALID_CONFIG); + break; + } + } + } + if (val32 & R_CMD_HASH_START_MASK) { if (!(s->regs->cfg & R_CFG_SHA_EN_MASK)) { ot_hmac_report_error(s, @@ -761,8 +859,10 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, break; } - /* We use a sha library in little endian by default, so we only need to - swap if the swap config is 0 (i.e. use big endian key). */ + /* + * We use a sha library in little endian by default, so we only need to + * swap if the swap config is 0 (i.e. use big endian key). + */ if (s->regs->cfg & R_CFG_KEY_SWAP_MASK) { s->regs->key[reg - R_KEY_0] = val32; } else { @@ -805,8 +905,10 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, __func__, addr, REG_NAME(reg)); } - /* We use a sha library in little endian by default, so we only need to - swap if the swap config is 1 (big endian digest). */ + /* + * We use a sha library in little endian by default, so we only need to + * swap if the swap config is 1 (big endian digest). + */ if (s->regs->cfg & R_CFG_DIGEST_SWAP_MASK) { s->regs->digest[reg - R_DIGEST_0] = bswap32(val32); } else { From 8cd81807dd3d6823f66c0611caffc9ef331a9283 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 12 Dec 2024 13:39:45 +0000 Subject: [PATCH 4/6] [ot] hw/opentitan: ot_hmac: Make HMAC outer hash independent of key/digest size This commit updates the size of the key and digest registers to their new maximum sizes in support of HMAC Multi-Mode (SHA-2 256/384/512 Engine), which is 64 bytes for the digest and 128 bytes for the key. The current HMAC logic which processes the input padding and performs the outer hash (using the outer padding) by default assumes that the key length is 64 bytes (512 bits), and that you are hence using the entire set of registers. They also assume that the digest is 32 bytes (256 bits), and that you are again hence using the entire register set. The new logic now uses the key_length and digest_size config register fields to determine the exact padding and digest sizes that should be used in the HMAC calculations. --- hw/opentitan/ot_hmac.c | 114 +++++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 21 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 4c65e8395ab5..0e0fc578b29c 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -46,11 +46,11 @@ /* Input FIFO length is 64 bytes (16 x 32 bits) */ #define OT_HMAC_FIFO_LENGTH 64u -/* Digest length is 32 bytes (256 bits) */ -#define OT_HMAC_DIGEST_LENGTH 32u +/* Maximum digest length is 64 bytes (512 bits) */ +#define OT_HMAC_MAX_DIGEST_LENGTH 64u -/* HMAC key length is 32 bytes (256 bits) */ -#define OT_HMAC_KEY_LENGTH 32u +/* Maximum key length is 128 bytes (1024 bits) */ +#define OT_HMAC_MAX_KEY_LENGTH 128u #define PARAM_NUM_IRQS 3u @@ -238,8 +238,8 @@ struct OtHMACRegisters { uint32_t cmd; uint32_t err_code; uint32_t wipe_secret; - uint32_t key[OT_HMAC_KEY_LENGTH / sizeof(uint32_t)]; - uint32_t digest[OT_HMAC_DIGEST_LENGTH / sizeof(uint32_t)]; + uint32_t key[OT_HMAC_MAX_KEY_LENGTH / sizeof(uint32_t)]; + uint32_t digest[OT_HMAC_MAX_DIGEST_LENGTH / sizeof(uint32_t)]; uint64_t msg_length; }; typedef struct OtHMACRegisters OtHMACRegisters; @@ -298,6 +298,45 @@ static inline OtHMACDigestSize ot_hmac_get_digest_size(uint32_t cfg_reg) } } +static size_t ot_hmac_get_digest_bytes(OtHMACState *s) +{ + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + return 32u; + case HMAC_SHA2_384: + return 48u; + case HMAC_SHA2_512: + return 64u; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + return 0u; + } +} + +static size_t ot_hmac_get_block_size_bytes(OtHMACState *s) +{ + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + return 64u; + case HMAC_SHA2_384: + case HMAC_SHA2_512: + return 128u; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + return 0u; + } +} + static inline OtHMACKeyLength ot_hmac_get_key_length(uint32_t cfg_reg) { switch ((cfg_reg & R_CFG_KEY_LENGTH_MASK) >> R_CFG_KEY_LENGTH_SHIFT) { @@ -317,6 +356,31 @@ static inline OtHMACKeyLength ot_hmac_get_key_length(uint32_t cfg_reg) } } +static size_t ot_hmac_get_key_bytes(OtHMACState *s) +{ + switch (ot_hmac_get_key_length(s->regs->cfg)) { + case HMAC_KEY_128: + return 16u; + case HMAC_KEY_256: + return 32u; + case HMAC_KEY_384: + return 48u; + case HMAC_KEY_512: + return 64u; + case HMAC_KEY_1024: + return 128u; + case HMAC_KEY_NONE: + default: + /* + * Should never happen: key length was validated when calling start / + * continue to begin operation if HMAC was enabled, and HMAC cannot be + * enabled while the SHA engine is in operation. + */ + g_assert_not_reached(); + return 0u; + } +} + static inline bool ot_hmac_key_length_supported(OtHMACDigestSize digest_size, OtHMACKeyLength key_length) { @@ -350,8 +414,7 @@ static void ot_hmac_writeback_digest_state(OtHMACState *s) { /* copy intermediary digest to mock HMAC's stop/continue behaviour. */ /* TODO: add support for SHA2-384 and SHA2-512 */ - unsigned digest_length = OT_HMAC_DIGEST_LENGTH / sizeof(uint32_t); - for (unsigned i = 0; i < digest_length; i++) { + for (unsigned i = 0; i < 8u; i++) { STORE32H(s->ctx->state.sha256.state[i], s->regs->digest + i); } } @@ -389,16 +452,21 @@ static void ot_hmac_compute_digest(OtHMACState *s) if (s->regs->cfg & R_CFG_HMAC_EN_MASK) { ot_hmac_sha_done(s); - uint64_t opad[8u]; + size_t key_length_b = ot_hmac_get_key_bytes(s); + size_t block_size_b = ot_hmac_get_block_size_bytes(s); + /* pad key to right with 0s when it is smaller than the block size. */ + size_t pad_length_b = MAX(key_length_b, block_size_b); + size_t pad_length_w = pad_length_b / sizeof(uint64_t); + uint64_t opad[OT_HMAC_MAX_KEY_LENGTH / sizeof(uint64_t)]; memset(opad, 0, sizeof(opad)); - memcpy(opad, s->regs->key, sizeof(s->regs->key)); - for (unsigned i = 0; i < ARRAY_SIZE(opad); i++) { - opad[i] ^= 0x5c5c5c5c5c5c5c5cull; + memcpy(opad, s->regs->key, key_length_b); + for (size_t idx = 0; idx < pad_length_w; idx++) { + opad[idx] ^= 0x5c5c5c5c5c5c5c5cull; } ot_hmac_sha_init(s, false); - ot_hmac_sha_process(s, (const uint8_t *)opad, sizeof(opad), false); + ot_hmac_sha_process(s, (const uint8_t *)opad, pad_length_b, false); ot_hmac_sha_process(s, (const uint8_t *)s->regs->digest, - sizeof(s->regs->digest), true); + ot_hmac_get_digest_bytes(s), true); } ot_hmac_sha_done(s); } @@ -744,13 +812,18 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, /* HMAC mode, process input padding */ if (s->regs->cfg & R_CFG_HMAC_EN_MASK) { - uint64_t ipad[8u]; + size_t key_length_b = ot_hmac_get_key_bytes(s); + size_t block_size_b = ot_hmac_get_block_size_bytes(s); + /* pad key to right with 0s if smaller than the block size. */ + size_t pad_length_b = MAX(key_length_b, block_size_b); + size_t pad_length_w = pad_length_b / sizeof(uint64_t); + uint64_t ipad[OT_HMAC_MAX_KEY_LENGTH / sizeof(uint64_t)]; memset(ipad, 0, sizeof(ipad)); - memcpy(ipad, s->regs->key, sizeof(s->regs->key)); - for (unsigned i = 0; i < ARRAY_SIZE(ipad); i++) { - ipad[i] ^= 0x3636363636363636u; + memcpy(ipad, s->regs->key, key_length_b); + for (size_t idx = 0; idx < pad_length_w; idx++) { + ipad[idx] ^= 0x3636363636363636ull; } - ot_hmac_sha_process(s, (const uint8_t *)ipad, sizeof(ipad), + ot_hmac_sha_process(s, (const uint8_t *)ipad, pad_length_b, true); } } @@ -805,8 +878,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, /* Restore SHA256 context */ s->ctx->state.sha256.curlen = 0; s->ctx->state.sha256.length = s->regs->msg_length; - unsigned digest_length = OT_HMAC_DIGEST_LENGTH / sizeof(uint32_t); - for (unsigned i = 0; i < digest_length; i++) { + for (unsigned i = 0; i < 8u; i++) { s->ctx->state.sha256.state[i] = s->regs->digest[i]; } From 434973ad2cac54603a2c9ae74b968d3540bb9b8d Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 12 Dec 2024 13:45:50 +0000 Subject: [PATCH 5/6] [ot] hw/opentitan: ot_hmac: Add HMAC SHA-2 384/512 & key size support This commit introduces full support for using the SHA-2 384 and SHA-2 512 algorithms for hashing alongside the existing SHA-2 256 algorithm, by setting the `digest_size` field in the configuration register. It likewise adds support for using a range of key sizes (128/256/384/512/ 1024), and not just always using a 512 bit key - this is set in the `key_length` field in the configuration register. The existing tomcrypt library is used to support the additional hash functionality, which uses `sha512` functionality for both sha384 and sha512 in its implementation. This commit also fixes an issue previously introduced where two of the `bswap`s on the digest were the wrong way around, cancelling each other out but technically producing an incorrect intermediary result. This fixes that behaviour, and updates the documentation to make the reasoning behind each endian swap clear. --- hw/opentitan/ot_hmac.c | 180 +++++++++++++++++++++++++++++++++++------ 1 file changed, 154 insertions(+), 26 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 0e0fc578b29c..60f82677dd30 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -413,16 +413,105 @@ static void ot_hmac_report_error(OtHMACState *s, uint32_t error) static void ot_hmac_writeback_digest_state(OtHMACState *s) { /* copy intermediary digest to mock HMAC's stop/continue behaviour. */ - /* TODO: add support for SHA2-384 and SHA2-512 */ - for (unsigned i = 0; i < 8u; i++) { - STORE32H(s->ctx->state.sha256.state[i], s->regs->digest + i); + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + for (unsigned idx = 0; idx < 8u; idx++) { + STORE32H(s->ctx->state.sha256.state[idx], s->regs->digest + idx); + } + break; + case HMAC_SHA2_384: + /* + * Even though SHA384 only uses the first six uint64_t values of + * the SHA512 digest, we must store all for intermediary computation. + */ + case HMAC_SHA2_512: + for (unsigned idx = 0; idx < 8u; idx++) { + STORE64H(s->ctx->state.sha512.state[idx], + s->regs->digest + 2 * idx); + } + break; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + } +} + +static void ot_hmac_restore_context(OtHMACState *s) +{ + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + s->ctx->state.sha256.curlen = 0; + s->ctx->state.sha256.length = s->regs->msg_length; + for (unsigned idx = 0; idx < 8u; idx++) { + LOAD32H(s->ctx->state.sha256.state[idx], s->regs->digest + idx); + } + break; + case HMAC_SHA2_384: + /* + * Even though SHA384 only uses the first six uint64_t values of + * the SHA512 digest, we must restore all for intermediary computation. + */ + case HMAC_SHA2_512: + s->ctx->state.sha512.curlen = 0; + s->ctx->state.sha512.length = s->regs->msg_length; + for (unsigned idx = 0; idx < 8u; idx++) { + LOAD64H(s->ctx->state.sha512.state[idx], s->regs->digest + 2 * idx); + } + break; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when receiving the + * continue command to (re-)begin operation. + */ + g_assert_not_reached(); + } +} + +static size_t ot_hmac_get_curlen(OtHMACState *s) +{ + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + return s->ctx->state.sha256.curlen; + case HMAC_SHA2_384: + case HMAC_SHA2_512: + return s->ctx->state.sha512.curlen; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + return 0u; } } static void ot_hmac_sha_init(OtHMACState *s, bool write_back) { - /* TODO: add support for SHA2-384 and SHA2-512 */ - sha256_init(&s->ctx->state); + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + sha256_init(&s->ctx->state); + break; + case HMAC_SHA2_384: + sha384_init(&s->ctx->state); + break; + case HMAC_SHA2_512: + sha512_init(&s->ctx->state); + break; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + return; + } if (write_back) { ot_hmac_writeback_digest_state(s); } @@ -431,8 +520,26 @@ static void ot_hmac_sha_init(OtHMACState *s, bool write_back) static void ot_hmac_sha_process(OtHMACState *s, const uint8_t *in, size_t inlen, bool write_back) { - /* TODO: add support for SHA2-384 and SHA2-512 */ - sha256_process(&s->ctx->state, in, inlen); + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + sha256_process(&s->ctx->state, in, inlen); + break; + /* NOLINTNEXTLINE */ + case HMAC_SHA2_384: + sha384_process(&s->ctx->state, in, inlen); + break; + case HMAC_SHA2_512: + sha512_process(&s->ctx->state, in, inlen); + break; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + return; + } if (write_back) { ot_hmac_writeback_digest_state(s); } @@ -440,8 +547,25 @@ static void ot_hmac_sha_process(OtHMACState *s, const uint8_t *in, size_t inlen, static void ot_hmac_sha_done(OtHMACState *s) { - /* TODO: add support for SHA2-384 and SHA2-512 */ - sha256_done(&s->ctx->state, (uint8_t *)s->regs->digest); + switch (ot_hmac_get_digest_size(s->regs->cfg)) { + case HMAC_SHA2_256: + sha256_done(&s->ctx->state, (uint8_t *)s->regs->digest); + return; + case HMAC_SHA2_384: + sha384_done(&s->ctx->state, (uint8_t *)s->regs->digest); + return; + case HMAC_SHA2_512: + sha512_done(&s->ctx->state, (uint8_t *)s->regs->digest); + return; + case HMAC_SHA2_NONE: + default: + /* + * Should never happen: digest size was validated when calling start / + * continue to begin operation. + */ + g_assert_not_reached(); + return; + } } static void ot_hmac_compute_digest(OtHMACState *s) @@ -478,9 +602,9 @@ static void ot_hmac_process_fifo(OtHMACState *s) bool stop = s->regs->cmd & R_CMD_HASH_STOP_MASK; if (!fifo8_is_empty(&s->input_fifo) && - (!stop || s->ctx->state.sha256.curlen != 0)) { + (!stop || ot_hmac_get_curlen(s) != 0)) { while (!fifo8_is_empty(&s->input_fifo) && - (!stop || s->ctx->state.sha256.curlen != 0)) { + (!stop || ot_hmac_get_curlen(s) != 0)) { uint8_t value = fifo8_pop(&s->input_fifo); ot_hmac_sha_process(s, &value, 1u, false); } @@ -496,7 +620,7 @@ static void ot_hmac_process_fifo(OtHMACState *s) } } - if (stop && s->ctx->state.sha256.curlen == 0) { + if (stop && ot_hmac_get_curlen(s) == 0) { s->regs->intr_state |= INTR_HMAC_DONE_MASK; s->regs->cmd = 0; } @@ -641,8 +765,12 @@ static uint64_t ot_hmac_regs_read(void *opaque, hwaddr addr, unsigned size) case R_DIGEST_14: case R_DIGEST_15: /* - * We use a sha library in little endian by default, so we only need to - * swap if the swap config is 1 (big endian digest). + * We use a SHA library that computes in native (little) endian-ness, + * but produces a big-endian digest upon termination. To ensure + * consistency between digests that are read/written, we make sure the + * value internally in s->regs is always big endian, to match the final + * digest. So, we only need to swap if the swap config is 0 (i.e. the + * digest should be output in little endian). */ if (s->regs->cfg & R_CFG_DIGEST_SWAP_MASK) { val32 = s->regs->digest[reg - R_DIGEST_0]; @@ -875,12 +1003,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, s->regs->cmd = R_CMD_HASH_CONTINUE_MASK; - /* Restore SHA256 context */ - s->ctx->state.sha256.curlen = 0; - s->ctx->state.sha256.length = s->regs->msg_length; - for (unsigned i = 0; i < 8u; i++) { - s->ctx->state.sha256.state[i] = s->regs->digest[i]; - } + ot_hmac_restore_context(s); /* trigger delayed processing of FIFO */ ibex_irq_set(&s->clkmgr, true); @@ -932,8 +1055,9 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, } /* - * We use a sha library in little endian by default, so we only need to - * swap if the swap config is 0 (i.e. use big endian key). + * We use a SHA library that operates in native (little) endian-ness, + * so we only need to swap if the swap config is 0 (i.e. the input key + * is big endian), to ensure the value in s->regs is little endian. */ if (s->regs->cfg & R_CFG_KEY_SWAP_MASK) { s->regs->key[reg - R_KEY_0] = val32; @@ -978,13 +1102,17 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, } /* - * We use a sha library in little endian by default, so we only need to - * swap if the swap config is 1 (big endian digest). + * We use a SHA library that computes in native (little) endian-ness, + * but produces a big-endian digest upon termination. To ensure + * consistency between digests that are read/written, we make sure the + * value internally in s->regs is always big endian, to match the final + * digest. So, we only need to swap if the swap config is 0 (i.e. the + * input digest is little endian). */ if (s->regs->cfg & R_CFG_DIGEST_SWAP_MASK) { - s->regs->digest[reg - R_DIGEST_0] = bswap32(val32); - } else { s->regs->digest[reg - R_DIGEST_0] = val32; + } else { + s->regs->digest[reg - R_DIGEST_0] = bswap32(val32); } break; case R_MSG_LENGTH_LOWER: From 81e51813e3068ad27e01ef3581b849cd899aeee9 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Thu, 12 Dec 2024 14:56:42 +0000 Subject: [PATCH 6/6] [ot] hw/opentitan: ot_hmac: Digest size write should not affect current hash This commit updates the functionality for the HMAC's digest size configuration register field, updating it to match the current hardware behaviour. The hardware uses a `digest_size_started` field to allow the user to write a digest size at any time, while ensuring that if a hash is in progress it will carry on using the existing digest size. A similar field is introduced into the context/state struct to allow the QEMU HMAC model to keep track of the digest size when the last START/CONTINUE command was sent, and use that in place of checking the register value every time. --- hw/opentitan/ot_hmac.c | 65 ++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index 60f82677dd30..7c276e11179c 100644 --- a/hw/opentitan/ot_hmac.c +++ b/hw/opentitan/ot_hmac.c @@ -230,6 +230,22 @@ static const char *REG_NAMES[REGS_COUNT] = { }; #undef REG_NAME_ENTRY +typedef enum OtHMACDigestSize { + HMAC_SHA2_NONE, + HMAC_SHA2_256, + HMAC_SHA2_384, + HMAC_SHA2_512, +} OtHMACDigestSize; + +typedef enum OtHMACKeyLength { + HMAC_KEY_NONE, + HMAC_KEY_128, + HMAC_KEY_256, + HMAC_KEY_384, + HMAC_KEY_512, + HMAC_KEY_1024, +} OtHMACKeyLength; + struct OtHMACRegisters { uint32_t intr_state; uint32_t intr_enable; @@ -246,6 +262,7 @@ typedef struct OtHMACRegisters OtHMACRegisters; struct OtHMACContext { hash_state state; + OtHMACDigestSize digest_size_started; }; typedef struct OtHMACContext OtHMACContext; @@ -267,22 +284,6 @@ struct OtHMACState { char *ot_id; }; -typedef enum OtHMACDigestSize { - HMAC_SHA2_NONE, - HMAC_SHA2_256, - HMAC_SHA2_384, - HMAC_SHA2_512, -} OtHMACDigestSize; - -typedef enum OtHMACKeyLength { - HMAC_KEY_NONE, - HMAC_KEY_128, - HMAC_KEY_256, - HMAC_KEY_384, - HMAC_KEY_512, - HMAC_KEY_1024, -} OtHMACKeyLength; - static inline OtHMACDigestSize ot_hmac_get_digest_size(uint32_t cfg_reg) { switch ((cfg_reg & R_CFG_DIGEST_SIZE_MASK) >> R_CFG_DIGEST_SIZE_SHIFT) { @@ -298,9 +299,9 @@ static inline OtHMACDigestSize ot_hmac_get_digest_size(uint32_t cfg_reg) } } -static size_t ot_hmac_get_digest_bytes(OtHMACState *s) +static size_t ot_hmac_get_digest_bytes(OtHMACDigestSize digest_size) { - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (digest_size) { case HMAC_SHA2_256: return 32u; case HMAC_SHA2_384: @@ -413,7 +414,7 @@ static void ot_hmac_report_error(OtHMACState *s, uint32_t error) static void ot_hmac_writeback_digest_state(OtHMACState *s) { /* copy intermediary digest to mock HMAC's stop/continue behaviour. */ - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (s->ctx->digest_size_started) { case HMAC_SHA2_256: for (unsigned idx = 0; idx < 8u; idx++) { STORE32H(s->ctx->state.sha256.state[idx], s->regs->digest + idx); @@ -442,7 +443,7 @@ static void ot_hmac_writeback_digest_state(OtHMACState *s) static void ot_hmac_restore_context(OtHMACState *s) { - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (s->ctx->digest_size_started) { case HMAC_SHA2_256: s->ctx->state.sha256.curlen = 0; s->ctx->state.sha256.length = s->regs->msg_length; @@ -474,7 +475,7 @@ static void ot_hmac_restore_context(OtHMACState *s) static size_t ot_hmac_get_curlen(OtHMACState *s) { - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (s->ctx->digest_size_started) { case HMAC_SHA2_256: return s->ctx->state.sha256.curlen; case HMAC_SHA2_384: @@ -493,7 +494,7 @@ static size_t ot_hmac_get_curlen(OtHMACState *s) static void ot_hmac_sha_init(OtHMACState *s, bool write_back) { - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (s->ctx->digest_size_started) { case HMAC_SHA2_256: sha256_init(&s->ctx->state); break; @@ -520,7 +521,7 @@ static void ot_hmac_sha_init(OtHMACState *s, bool write_back) static void ot_hmac_sha_process(OtHMACState *s, const uint8_t *in, size_t inlen, bool write_back) { - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (s->ctx->digest_size_started) { case HMAC_SHA2_256: sha256_process(&s->ctx->state, in, inlen); break; @@ -547,7 +548,7 @@ static void ot_hmac_sha_process(OtHMACState *s, const uint8_t *in, size_t inlen, static void ot_hmac_sha_done(OtHMACState *s) { - switch (ot_hmac_get_digest_size(s->regs->cfg)) { + switch (s->ctx->digest_size_started) { case HMAC_SHA2_256: sha256_done(&s->ctx->state, (uint8_t *)s->regs->digest); return; @@ -590,7 +591,9 @@ static void ot_hmac_compute_digest(OtHMACState *s) ot_hmac_sha_init(s, false); ot_hmac_sha_process(s, (const uint8_t *)opad, pad_length_b, false); ot_hmac_sha_process(s, (const uint8_t *)s->regs->digest, - ot_hmac_get_digest_bytes(s), true); + ot_hmac_get_digest_bytes( + s->ctx->digest_size_started), + true); } ot_hmac_sha_done(s); } @@ -936,6 +939,12 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, ibex_irq_set(&s->clkmgr, true); + /* + * Hold the previous digest size until the HMAC is started with the + * new digest size configured + */ + s->ctx->digest_size_started = ot_hmac_get_digest_size(s->regs->cfg); + ot_hmac_sha_init(s, true); /* HMAC mode, process input padding */ @@ -1003,6 +1012,12 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value, s->regs->cmd = R_CMD_HASH_CONTINUE_MASK; + /* + * Hold the previous digest size until the HMAC is started with the + * new digest size configured + */ + s->ctx->digest_size_started = ot_hmac_get_digest_size(s->regs->cfg); + ot_hmac_restore_context(s); /* trigger delayed processing of FIFO */