diff --git a/hw/opentitan/ot_hmac.c b/hw/opentitan/ot_hmac.c index d63a4a2b03e4..7c276e11179c 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 @@ -44,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 @@ -85,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) @@ -150,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) @@ -222,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; @@ -230,14 +254,15 @@ 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; struct OtHMACContext { hash_state state; + OtHMACDigestSize digest_size_started; }; typedef struct OtHMACContext OtHMACContext; @@ -259,6 +284,110 @@ struct OtHMACState { char *ot_id; }; +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 size_t ot_hmac_get_digest_bytes(OtHMACDigestSize digest_size) +{ + switch (digest_size) { + 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) { + 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 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) +{ + 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; @@ -284,19 +413,106 @@ 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. */ - /* 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++) { - STORE32H(s->ctx->state.sha256.state[i], s->regs->digest + i); + /* copy intermediary digest to mock HMAC's stop/continue behaviour. */ + 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); + } + 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 (s->ctx->digest_size_started) { + 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 (s->ctx->digest_size_started) { + 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 (s->ctx->digest_size_started) { + 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); } @@ -305,8 +521,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 (s->ctx->digest_size_started) { + 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); } @@ -314,8 +548,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 (s->ctx->digest_size_started) { + 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) @@ -326,16 +577,23 @@ 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->ctx->digest_size_started), + true); } ot_hmac_sha_done(s); } @@ -347,9 +605,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); } @@ -365,7 +623,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; } @@ -509,8 +767,14 @@ 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 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]; } else { @@ -589,6 +853,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); @@ -612,12 +879,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, @@ -625,6 +907,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, @@ -640,17 +939,28 @@ 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 */ 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); } } @@ -681,7 +991,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); } @@ -699,13 +1012,13 @@ 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; - unsigned digest_length = OT_HMAC_DIGEST_LENGTH / sizeof(uint32_t); - for (unsigned i = 0; i < digest_length; i++) { - s->ctx->state.sha256.state[i] = s->regs->digest[i]; - } + /* + * 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 */ ibex_irq_set(&s->clkmgr, true); @@ -714,7 +1027,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)); @@ -757,8 +1069,11 @@ 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 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; } else { @@ -801,12 +1116,18 @@ 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 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: