Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HMAC stop/continue commands & endian swaps #88

Merged
merged 5 commits into from
Dec 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 179 additions & 21 deletions hw/opentitan/ot_hmac.c
Original file line number Diff line number Diff line change
Expand Up @@ -282,41 +282,92 @@ static void ot_hmac_report_error(OtHMACState *s, uint32_t error)
ot_hmac_update_irqs(s);
}

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);
}
}

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);
if (write_back) {
ot_hmac_writeback_digest_state(s);
}
}

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);
if (write_back) {
ot_hmac_writeback_digest_state(s);
}
}

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);
}

static void ot_hmac_compute_digest(OtHMACState *s)
{
trace_ot_hmac_debug(s->ot_id, __func__);

/* HMAC mode, perform outer hash */
if (s->regs->cfg & R_CFG_HMAC_EN_MASK) {
sha256_done(&s->ctx->state, (uint8_t *)s->regs->digest);
ot_hmac_sha_done(s);

uint64_t opad[8u];
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;
}
sha256_init(&s->ctx->state);
sha256_process(&s->ctx->state, (const uint8_t *)opad, sizeof(opad));
sha256_process(&s->ctx->state, (const uint8_t *)s->regs->digest,
sizeof(s->regs->digest));
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 *)s->regs->digest,
sizeof(s->regs->digest), true);
}

sha256_done(&s->ctx->state, (uint8_t *)s->regs->digest);
ot_hmac_sha_done(s);
}

static void ot_hmac_process_fifo(OtHMACState *s)
{
trace_ot_hmac_debug(s->ot_id, __func__);

if (!fifo8_is_empty(&s->input_fifo)) {
while (!fifo8_is_empty(&s->input_fifo)) {
bool stop = s->regs->cmd & R_CMD_HASH_STOP_MASK;

if (!fifo8_is_empty(&s->input_fifo) &&
(!stop || s->ctx->state.sha256.curlen != 0)) {
while (!fifo8_is_empty(&s->input_fifo) &&
(!stop || s->ctx->state.sha256.curlen != 0)) {
uint8_t value = fifo8_pop(&s->input_fifo);
sha256_process(&s->ctx->state, &value, 1);
ot_hmac_sha_process(s, &value, 1u, false);
}

/* write back updated digest state */
if (fifo8_is_empty(&s->input_fifo) || stop) {
ot_hmac_writeback_digest_state(s);
}

/* assert FIFO Empty IRQ */
s->regs->intr_state |= INTR_FIFO_EMPTY_MASK;
if (fifo8_is_empty(&s->input_fifo)) {
s->regs->intr_state |= INTR_FIFO_EMPTY_MASK;
}
}

if (stop && s->ctx->state.sha256.curlen == 0) {
s->regs->intr_state |= INTR_HMAC_DONE_MASK;
s->regs->cmd = 0;
}

if (s->regs->cmd & R_CMD_HASH_PROCESS_MASK) {
Expand Down Expand Up @@ -435,6 +486,9 @@ static uint64_t ot_hmac_regs_read(void *opaque, hwaddr addr, unsigned size)
val32 |= R_STATUS_FIFO_FULL_MASK;
}
}
if (!(s->regs->cmd)) {
val32 |= R_STATUS_HMAC_IDLE_MASK;
}
} break;
case R_ERR_CODE:
val32 = s->regs->err_code;
Expand All @@ -455,6 +509,8 @@ 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). */
if (s->regs->cfg & R_CFG_DIGEST_SWAP_MASK) {
val32 = s->regs->digest[reg - R_DIGEST_0];
} else {
Expand Down Expand Up @@ -557,8 +613,10 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value,
}

s->regs->cfg =
val32 & (R_CFG_HMAC_EN_MASK | R_CFG_SHA_EN_MASK |
R_CFG_ENDIAN_SWAP_MASK | R_CFG_DIGEST_SWAP_MASK);
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);

/* clear digest when SHA is disabled */
if (!(s->regs->cfg & R_CFG_SHA_EN_MASK)) {
Expand All @@ -582,7 +640,7 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value,

ibex_irq_set(&s->clkmgr, true);

sha256_init(&s->ctx->state);
ot_hmac_sha_init(s, true);

/* HMAC mode, process input padding */
if (s->regs->cfg & R_CFG_HMAC_EN_MASK) {
Expand All @@ -592,13 +650,14 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value,
for (unsigned i = 0; i < ARRAY_SIZE(ipad); i++) {
ipad[i] ^= 0x3636363636363636u;
}
sha256_process(&s->ctx->state, (const uint8_t *)ipad,
sizeof(ipad));
ot_hmac_sha_process(s, (const uint8_t *)ipad, sizeof(ipad),
true);
}
}

if (val32 & R_CMD_HASH_PROCESS_MASK) {
if (!(s->regs->cmd & R_CMD_HASH_START_MASK)) {
if (!(s->regs->cmd &
(R_CMD_HASH_START_MASK | R_CMD_HASH_CONTINUE_MASK))) {
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: CMD.PROCESS requested but hash not started yet\n",
Expand All @@ -618,6 +677,41 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value,
ibex_irq_set(&s->clkmgr, true);
ot_hmac_process_fifo(s);
}

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. */
ibex_irq_set(&s->clkmgr, true);
ot_hmac_process_fifo(s);
}

if (val32 & R_CMD_HASH_CONTINUE_MASK) {
if (!(s->regs->cfg & R_CFG_SHA_EN_MASK)) {
ot_hmac_report_error(s,
R_ERR_CODE_HASH_START_WHEN_SHA_DISABLED);
break;
}
if (s->regs->cmd) {
ot_hmac_report_error(s, R_ERR_CODE_HASH_START_WHEN_ACTIVE);
break;
}

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];
}

/* trigger delayed processing of FIFO */
ibex_irq_set(&s->clkmgr, true);
ot_hmac_process_fifo(s);
}

break;
case R_WIPE_SECRET:
/* TODO ignore write if engine is not idle? */
Expand Down Expand Up @@ -662,10 +756,21 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value,
ot_hmac_report_error(s, R_ERR_CODE_UPDATE_SECRET_KEY_INPROCESS);
break;
}
s->regs->key[reg - R_KEY_0] = bswap32(val32);

/* 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 {
s->regs->key[reg - R_KEY_0] = bswap32(val32);
}
break;
case R_STATUS:
case R_ERR_CODE:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__,
addr, REG_NAME(reg));
break;
case R_DIGEST_0:
case R_DIGEST_1:
case R_DIGEST_2:
Expand All @@ -682,11 +787,61 @@ static void ot_hmac_regs_write(void *opaque, hwaddr addr, uint64_t value,
case R_DIGEST_13:
case R_DIGEST_14:
case R_DIGEST_15:
/* ignore write and report error if engine is not idle */
if (s->regs->cmd) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Cannot W register 0x%02" HWADDR_PRIx
" (%s) whilst non-idle\n",
__func__, addr, REG_NAME(reg));
break;
} else if (s->regs->cfg & R_CFG_SHA_EN_MASK) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Cannot W register 0x%02" HWADDR_PRIx
" (%s) whilst SHA Engine is enabled\n",
__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). */
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;
}
break;
case R_MSG_LENGTH_LOWER:
/* ignore write and report error if engine is not idle */
if (s->regs->cmd) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Cannot W register 0x%02" HWADDR_PRIx
" (%s) whilst non-idle\n",
__func__, addr, REG_NAME(reg));
break;
} else if (s->regs->cfg & R_CFG_SHA_EN_MASK) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Cannot W register 0x%02" HWADDR_PRIx
" (%s) whilst SHA Engine is enabled\n",
__func__, addr, REG_NAME(reg));
}
s->regs->msg_length =
(s->regs->msg_length & (0xFFFFFFFFull << 32u)) | val32;
break;
case R_MSG_LENGTH_UPPER:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: R/O register 0x%02" HWADDR_PRIx " (%s)\n", __func__,
addr, REG_NAME(reg));
/* ignore write and report error if engine is not idle */
if (s->regs->cmd) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Cannot W register 0x%02" HWADDR_PRIx
" (%s) whilst non-idle\n",
__func__, addr, REG_NAME(reg));
break;
} else if (s->regs->cfg & R_CFG_SHA_EN_MASK) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Cannot W register 0x%02" HWADDR_PRIx
" (%s) whilst SHA Engine is enabled\n",
__func__, addr, REG_NAME(reg));
}
s->regs->msg_length =
((uint64_t)val32 << 32u) | (s->regs->msg_length & 0xFFFFFFFFull);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
Expand Down Expand Up @@ -762,12 +917,15 @@ static void ot_hmac_realize(DeviceState *dev, Error **errp)
static void ot_hmac_reset(DeviceState *dev)
{
OtHMACState *s = OT_HMAC(dev);
OtHMACRegisters *r = s->regs;

ibex_irq_set(&s->clkmgr, false);

memset(s->ctx, 0, sizeof(*(s->ctx)));
memset(s->regs, 0, sizeof(*(s->regs)));

r->cfg = 0x4100u;

ot_hmac_update_irqs(s);
ot_hmac_update_alert(s);

Expand Down
Loading