From 90a92fae0871ef1a95392ff2b022efba048ec7ea Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sat, 30 Aug 2025 14:52:09 +0100 Subject: [PATCH 1/7] [ot] hw/opentitan: ot_keymgr: Add links to lc_ctrl, rom & flash These device links will be needed for retrieving values used in the KDF (key derivation function) for keymgr advance operations. Signed-off-by: Alex Jones --- hw/opentitan/ot_keymgr.c | 15 +++++++++++++++ hw/riscv/ot_earlgrey.c | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_keymgr.c b/hw/opentitan/ot_keymgr.c index f893cccfd530f..1d5220a0b92f7 100644 --- a/hw/opentitan/ot_keymgr.c +++ b/hw/opentitan/ot_keymgr.c @@ -34,10 +34,13 @@ #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" +#include "hw/opentitan/ot_flash.h" #include "hw/opentitan/ot_keymgr.h" #include "hw/opentitan/ot_kmac.h" +#include "hw/opentitan/ot_lc_ctrl.h" #include "hw/opentitan/ot_otp.h" #include "hw/opentitan/ot_prng.h" +#include "hw/opentitan/ot_rom_ctrl.h" #include "hw/qdev-properties.h" #include "hw/registerfields.h" #include "hw/riscv/ibex_common.h" @@ -404,7 +407,10 @@ typedef struct OtKeyMgrState { /* properties */ char *ot_id; OtKeyMgrEDN edn; + OtFlashState *flash_ctrl; + OtLcCtrlState *lc_ctrl; OtOTPState *otp_ctrl; + OtRomCtrlState *rom_ctrl; char *seed_xstrs[KEYMGR_SEED_COUNT]; } OtKeyMgrState; @@ -1378,8 +1384,14 @@ static Property ot_keymgr_properties[] = { DEFINE_PROP_LINK("edn", OtKeyMgrState, edn.device, TYPE_OT_EDN, OtEDNState *), DEFINE_PROP_UINT8("edn-ep", OtKeyMgrState, edn.ep, UINT8_MAX), + DEFINE_PROP_LINK("flash_ctrl", OtKeyMgrState, flash_ctrl, TYPE_OT_FLASH, + OtFlashState *), + DEFINE_PROP_LINK("lc_ctrl", OtKeyMgrState, lc_ctrl, TYPE_OT_LC_CTRL, + OtLcCtrlState *), DEFINE_PROP_LINK("otp_ctrl", OtKeyMgrState, otp_ctrl, TYPE_OT_OTP, OtOTPState *), + DEFINE_PROP_LINK("rom_ctrl", OtKeyMgrState, rom_ctrl, TYPE_OT_ROM_CTRL, + OtRomCtrlState *), DEFINE_PROP_STRING("lfsr_seed", OtKeyMgrState, seed_xstrs[KEYMGR_SEED_LFSR]), DEFINE_PROP_STRING("revision_seed", OtKeyMgrState, @@ -1428,7 +1440,10 @@ static void ot_keymgr_reset_enter(Object *obj, ResetType type) g_assert(s->edn.device); g_assert(s->edn.ep != UINT8_MAX); + g_assert(s->flash_ctrl); + g_assert(s->lc_ctrl); g_assert(s->otp_ctrl); + g_assert(s->rom_ctrl); /* reset registers */ memset(s->regs, 0u, sizeof(s->regs)); diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index c53d0f21d5370..392c7c77d3d0d 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1187,7 +1187,10 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { ), .link = IBEXDEVICELINKDEFS( OT_EG_SOC_DEVLINK("edn", EDN0), - OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL) + OT_EG_SOC_DEVLINK("flash_ctrl", FLASH_CTRL), + OT_EG_SOC_DEVLINK("lc_ctrl", LC_CTRL), + OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL), + OT_EG_SOC_DEVLINK("rom_ctrl", ROM_CTRL) ), .prop = IBEXDEVICEPROPDEFS( IBEX_DEV_UINT_PROP("edn-ep", 0u) From d687b3597483f9bc1021e1743323e0e84a8250e1 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sat, 30 Aug 2025 14:57:24 +0100 Subject: [PATCH 2/7] [ot] hw/opentitan: ot_keymgr: Add KMAC KDF advance input computation This commit introduces the logic relevant to computing the KDF `AdvData` input for the `Advance` operation, which depends on both the current keymgr stage and the CDI. This logic is adapted from the keymgr_dpe, but with many differences: - `keymgr` has no slots, and encodes stages in its main FSM states. - `keymgr` has 2 CDIs: one for sealing and one for attestation. Normally, you can select the CDI, but advance operations advance both (sealing, then attestation). - `keymgr` uses different KDF inputs to `keymgr_dpe`. It only has 1 ROM digest, uses creator/owner seeds in different phases, and concatenates its inputs in a different order. - `keymgr` gets creator/owner seeds from flash, instead of OTP. - `keymgr` chooses to use either sealing or attestation SW bindings based on the CDI, `keymgr_dpe` only has 1 SW binding. For now, many parts are still stubbed out - we do not yet send the KDF request to KMAC, and we do not properly handle wiping data on disablement, or invalid inputs. This commit also adds minimal handling for the `disable` operation. Signed-off-by: Alex Jones --- hw/opentitan/ot_keymgr.c | 284 +++++++++++++++++++++++++++++++++++++- hw/opentitan/trace-events | 5 +- 2 files changed, 283 insertions(+), 6 deletions(-) diff --git a/hw/opentitan/ot_keymgr.c b/hw/opentitan/ot_keymgr.c index 1d5220a0b92f7..2c3768a1d6cb4 100644 --- a/hw/opentitan/ot_keymgr.c +++ b/hw/opentitan/ot_keymgr.c @@ -95,6 +95,8 @@ static_assert(KEYMGR_ID_DATA_BYTES <= KEYMGR_KDF_BUFFER_BYTES, "KeyMgr ID data does not fit in KDF buffer"); static_assert(KEYMGR_GEN_DATA_BYTES <= KEYMGR_KDF_BUFFER_BYTES, "KeyMgr GEN data does not fit in KDF buffer"); +static_assert((KEYMGR_KDF_BUFFER_BYTES % OT_KMAC_APP_MSG_BYTES) == 0u, + "KeyMgr KDF buffer not a multiple of KMAC message size"); /* NOLINTBEGIN(misc-redundant-expression) */ static_assert(KEYMGR_KEY_BYTES == OT_OTP_KEYMGR_SECRET_SIZE, "KeyMgr key size does not match OTP KeyMgr secret size"); @@ -262,6 +264,13 @@ typedef enum { #define KEY_SINK_OFFSET 1 +typedef enum { + KEYMGR_STAGE_CREATOR, + KEYMGR_STAGE_OWNER_INT, + KEYMGR_STAGE_OWNER, + KEYMGR_STAGE_DISABLE, +} OtKeyMgrStage; + /* values for CONTROL_SHADOWED.OPERATION */ typedef enum { KEYMGR_OP_ADVANCE = 0, @@ -373,8 +382,16 @@ typedef struct { typedef struct { bool op_req; bool op_ack; + OtKeyMgrStage stage; + OtKeyMgrCdi adv_cdi_cnt; } OtKeyMgrOpState; +typedef struct { + uint8_t *data; + unsigned offset; /* current read offset (in bytes ) */ + unsigned length; /* current length (in bytes) */ +} OtKeyMgrKdfBuffer; + typedef struct OtKeyMgrState { SysBusDevice parent_obj; @@ -382,6 +399,7 @@ typedef struct OtKeyMgrState { IbexIRQ irq; IbexIRQ alerts[ALERT_COUNT]; QEMUTimer *fsm_tick_timer; + OtKeyMgrKdfBuffer kdf_buf; uint32_t regs[REGS_COUNT]; OtShadowReg control; @@ -489,6 +507,26 @@ static const char *REG_NAMES[REGS_COUNT] = { #define REG_NAME(_reg_) \ ((((_reg_) <= REGS_COUNT) && REG_NAMES[_reg_]) ? REG_NAMES[_reg_] : "?") +#define STAGE_ENTRY(_st_) [KEYMGR_STAGE_##_st_] = stringify(_st_) +static const char *STAGE_NAMES[] = { + STAGE_ENTRY(CREATOR), + STAGE_ENTRY(OWNER_INT), + STAGE_ENTRY(OWNER), + STAGE_ENTRY(DISABLE), +}; +#undef STAGE_ENTRY +#define STAGE_NAME(_st_) \ + (((unsigned)(_st_)) < ARRAY_SIZE(STAGE_NAMES) ? STAGE_NAMES[(_st_)] : "?") + +#define CDI_ENTRY(_cd_) [KEYMGR_CDI_##_cd_] = stringify(_cd_) +static const char *CDI_NAMES[] = { + CDI_ENTRY(SEALING), + CDI_ENTRY(ATTESTATION), +}; +#undef CDI_ENTRY +#define CDI_NAME(_cd_) \ + (((unsigned)(_cd_)) < ARRAY_SIZE(CDI_NAMES) ? CDI_NAMES[(_cd_)] : "?") + #define OP_ENTRY(_op_) [KEYMGR_OP_##_op_] = stringify(_op_) static const char *OP_NAMES[] = { OP_ENTRY(ADVANCE), @@ -556,6 +594,22 @@ static const char *FST_NAMES[] = { #define ot_keymgr_dump_bigint(_s_, _b_, _l_) \ ot_common_lhexdump(_b_, _l_, true, (_s_)->hexstr, OT_KEYMGR_HEXSTR_SIZE) +static void ot_keymgr_dump_kdf_buf(const OtKeyMgrState *s, const char *op) +{ + if (trace_event_get_state(TRACE_OT_KEYMGR_DUMP_KDF_BUF)) { + size_t msgs = (s->kdf_buf.length + OT_KMAC_APP_MSG_BYTES - 1u) / + OT_KMAC_APP_MSG_BYTES; + for (size_t ix = 0u; ix < msgs; ix++) { + trace_ot_keymgr_dump_kdf_buf( + s->ot_id, op, ix, + ot_keymgr_dump_bigint(s, + &s->kdf_buf + .data[ix * OT_KMAC_APP_MSG_BYTES], + OT_KMAC_APP_MSG_BYTES)); + } + } +} + static void ot_keymgr_update_irq(OtKeyMgrState *s) { bool level = (bool)(s->regs[R_INTR_STATE] & s->regs[R_INTR_ENABLE]); @@ -728,6 +782,129 @@ static void ot_keymgr_request_entropy(OtKeyMgrState *s) } } +/* check that 'data' is not all zeros or all ones */ +static bool ot_keymgr_valid_data_check(const uint8_t *data, size_t len) +{ + size_t popcount = 0u; + for (unsigned ix = 0u; ix < len; ix++) { + popcount += __builtin_popcount(data[ix]); + } + return (popcount && popcount != (len * BITS_PER_BYTE)); +} + +static void ot_keymgr_reset_kdf_buffer(OtKeyMgrState *s) +{ + memset(s->kdf_buf.data, 0u, KEYMGR_KDF_BUFFER_BYTES); + s->kdf_buf.offset = 0u; + s->kdf_buf.length = 0u; +} + +static void ot_keymgr_kdf_push_bytes(OtKeyMgrState *s, const uint8_t *data, + size_t len) +{ + g_assert(s->kdf_buf.length + len <= KEYMGR_KDF_BUFFER_BYTES); + + memcpy(&s->kdf_buf.data[s->kdf_buf.length], data, len); + s->kdf_buf.length += len; +} + +static void ot_keymgr_dump_kdf_material( + const OtKeyMgrState *s, const char *what, const uint8_t *buf, size_t len) +{ + if (trace_event_get_state(TRACE_OT_KEYMGR_DUMP_KDF_MATERIAL)) { + const char *hexstr = ot_keymgr_dump_bigint(s, buf, len); + trace_ot_keymgr_dump_kdf_material(s->ot_id, what, hexstr); + } +} + +static size_t ot_keymgr_kdf_append_rev_seed(OtKeyMgrState *s) +{ + ot_keymgr_kdf_push_bytes(s, s->seeds[KEYMGR_SEED_REV], KEYMGR_SEED_BYTES); + ot_keymgr_dump_kdf_material(s, "REV_SEED", s->seeds[KEYMGR_SEED_REV], + KEYMGR_SEED_BYTES); + return KEYMGR_SEED_BYTES; +} + +static size_t ot_keymgr_kdf_append_rom_digest(OtKeyMgrState *s, bool *dvalid) +{ + uint8_t rom_digest[OT_ROM_DIGEST_BYTES] = { 0u }; + + OtRomCtrlClass *rcc = OT_ROM_CTRL_GET_CLASS(s->rom_ctrl); + rcc->get_rom_digest(s->rom_ctrl, rom_digest); + + ot_keymgr_kdf_push_bytes(s, rom_digest, OT_ROM_DIGEST_BYTES); + *dvalid &= ot_keymgr_valid_data_check(rom_digest, OT_ROM_DIGEST_BYTES); + + ot_keymgr_dump_kdf_material(s, "ROM_DIGEST", rom_digest, + OT_ROM_DIGEST_BYTES); + return OT_ROM_DIGEST_BYTES; +} + +static size_t ot_keymgr_kdf_append_km_div(OtKeyMgrState *s, bool *dvalid) +{ + OtLcCtrlKeyMgrDiv km_div = { 0u }; + + OtLcCtrlClass *lc = OT_LC_CTRL_GET_CLASS(s->lc_ctrl); + lc->get_keymgr_div(s->lc_ctrl, &km_div); + + ot_keymgr_kdf_push_bytes(s, km_div.data, OT_LC_KEYMGR_DIV_BYTES); + *dvalid &= ot_keymgr_valid_data_check(km_div.data, OT_LC_KEYMGR_DIV_BYTES); + + ot_keymgr_dump_kdf_material(s, "KM_DIV", km_div.data, + OT_LC_KEYMGR_DIV_BYTES); + return OT_LC_KEYMGR_DIV_BYTES; +} + +static size_t ot_keymgr_kdf_append_dev_id(OtKeyMgrState *s, bool *dvalid) +{ + OtOTPClass *otp_oc = OBJECT_GET_CLASS(OtOTPClass, s->otp_ctrl, TYPE_OT_OTP); + const OtOTPHWCfg *hw_cfg = otp_oc->get_hw_cfg(s->otp_ctrl); + + ot_keymgr_kdf_push_bytes(s, hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES); + *dvalid &= ot_keymgr_valid_data_check(hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES); + + ot_keymgr_dump_kdf_material(s, "DEVICE_ID", hw_cfg->device_id, + OT_OTP_HWCFG_DEVICE_ID_BYTES); + return OT_OTP_HWCFG_DEVICE_ID_BYTES; +} + +static size_t +ot_keymgr_kdf_append_seed(OtKeyMgrState *s, OtFlashKeyMgrSecretType type, + const char *seed_name, bool *dvalid) +{ + OtFlashKeyMgrSecret seed = { 0u }; + + OtFlashClass *fc = OT_FLASH_GET_CLASS(s->flash_ctrl); + fc->get_keymgr_secret(s->flash_ctrl, type, &seed); + + ot_keymgr_kdf_push_bytes(s, seed.secret, OT_FLASH_KEYMGR_SECRET_BYTES); + *dvalid &= + ot_keymgr_valid_data_check(seed.secret, OT_FLASH_KEYMGR_SECRET_BYTES); + *dvalid &= seed.valid; + + ot_keymgr_dump_kdf_material(s, seed_name, seed.secret, + OT_FLASH_KEYMGR_SECRET_BYTES); + return OT_FLASH_KEYMGR_SECRET_BYTES; +} + +static size_t ot_keymgr_kdf_append_sw_binding(OtKeyMgrState *s, OtKeyMgrCdi cdi) +{ + uint8_t *sw_binding = (cdi == KEYMGR_CDI_SEALING) ? s->sealing_sw_binding : + s->attest_sw_binding; + + ot_keymgr_kdf_push_bytes(s, sw_binding, KEYMGR_SW_BINDING_BYTES); + + char buf[32u]; + const char *binding_suffix = "_SW_BINDING"; + const char *cdi_name = CDI_NAME(cdi); + g_assert(strlen(binding_suffix) + strlen(cdi_name) + 1 < sizeof(buf)); + snprintf(buf, sizeof(buf), "%s%s", cdi_name, binding_suffix); + ot_keymgr_dump_kdf_material(s, buf, sw_binding, KEYMGR_SW_BINDING_BYTES); + return KEYMGR_SW_BINDING_BYTES; +} + static void ot_keymgr_get_root_key(OtKeyMgrState *s, OtOTPKeyMgrSecret *share0, OtOTPKeyMgrSecret *share1) { @@ -764,22 +941,112 @@ static void ot_keymgr_xchange_main_fsm_state(OtKeyMgrState *s, } } +static void ot_keymgr_operation_advance(OtKeyMgrState *s, OtKeyMgrStage stage, + OtKeyMgrCdi cdi) +{ + trace_ot_keymgr_advance(s->ot_id, STAGE_NAME(stage), (int)stage, + CDI_NAME(cdi), (int)cdi); + + bool dvalid = true; + + /* @todo: do we need to check for any error states here? */ + + ot_keymgr_reset_kdf_buffer(s); + + size_t expected_kdf_len = 0u; + + switch (stage) { + case KEYMGR_STAGE_CREATOR: + /* Revision Seed (which is a netlist constant) */ + expected_kdf_len += ot_keymgr_kdf_append_rev_seed(s); + + /* Rom Digest (from rom_ctrl) */ + expected_kdf_len += ot_keymgr_kdf_append_rom_digest(s, &dvalid); + + /* KeyManager Diversification (from lc_ctrl) */ + expected_kdf_len += ot_keymgr_kdf_append_km_div(s, &dvalid); + + /* Device ID (from OTP) */ + expected_kdf_len += ot_keymgr_kdf_append_dev_id(s, &dvalid); + break; + case KEYMGR_STAGE_OWNER_INT: + /* Creator Seed (from flash) */ + expected_kdf_len += + ot_keymgr_kdf_append_seed(s, FLASH_KEYMGR_SECRET_CREATOR_SEED, + "CREATOR_SEED", &dvalid); + break; + case KEYMGR_STAGE_OWNER: + /* Owner Seed (from flash) */ + expected_kdf_len += + ot_keymgr_kdf_append_seed(s, FLASH_KEYMGR_SECRET_OWNER_SEED, + "OWNER_SEED", &dvalid); + break; + case KEYMGR_STAGE_DISABLE: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: %s: advance called in the %s key stage\n.", __func__, + s->ot_id, STAGE_NAME(stage)); + return; + default: + /* should only be called with a valid stage */ + g_assert_not_reached(); + } + + /* Software Binding (software-provided via {x_SW_BINDING_y registers) */ + expected_kdf_len += ot_keymgr_kdf_append_sw_binding(s, cdi); + + /* check that we have pushed all expected KDF data */ + g_assert(s->kdf_buf.length == expected_kdf_len); + + /* + * @todo: store `dvalid` somewhere and, if the data is invalid, replace the + * KMAC response with decoy data (a random permutation of entropy share 1). + */ + if (!dvalid) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_KMAC_INPUT_MASK; + } + + g_assert(s->kdf_buf.length <= KEYMGR_ADV_DATA_BYTES); + s->kdf_buf.length = KEYMGR_ADV_DATA_BYTES; + + ot_keymgr_dump_kdf_buf(s, "adv"); + + /* + * @todo: send the KMAC KDF request and handle the response. When we get a + * response, transition the main FSM to `KEYMGR_ST_CREATOR_ROOT_KEY`. + */ +} + +static void ot_keymgr_operation_disable(OtKeyMgrState *s) +{ + /* @todo: wipe the current keys with some dummy entropy data */ + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_DISABLED); + s->op_state.op_ack = true; + ot_keymgr_schedule_fsm(s); +} + static void ot_keymgr_start_operation(OtKeyMgrState *s) { uint32_t ctrl = ot_shadow_reg_peek(&s->control); int op = (int)FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION); - trace_ot_keymgr_operation(s->ot_id, OP_NAME(op), op); + trace_ot_keymgr_operation(s->ot_id, STAGE_NAME(s->op_state.stage), + OP_NAME(op), op); switch (op) { case KEYMGR_OP_ADVANCE: + s->op_state.adv_cdi_cnt = (OtKeyMgrCdi)0; + ot_keymgr_operation_advance(s, s->op_state.stage, + s->op_state.adv_cdi_cnt); + /* + * @todo: when we get a KMAC response with the sealing key, we should + * then send a request for the attestation CDI before completing. + */ + break; case KEYMGR_OP_DISABLE: - /* @todo: implement keymgr operations */ - qemu_log_mask(LOG_UNIMP, "%s: %s, Operation %s is not implemented.\n", - __func__, s->ot_id, OP_NAME(op)); + ot_keymgr_operation_disable(s); break; default: - /* should only be called with a valid init operation */ + /* should only be called with a valid operation */ g_assert_not_reached(); } } @@ -880,6 +1147,9 @@ static bool ot_keymgr_main_fsm_tick(OtKeyMgrState *s) if (!valid_op) { s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; } else if (!s->op_state.op_req) { + s->op_state.stage = + (op == KEYMGR_OP_ADVANCE) ? KEYMGR_STAGE_CREATOR : + KEYMGR_STAGE_DISABLE; s->op_state.op_req = true; ot_keymgr_start_operation(s); } @@ -1470,6 +1740,9 @@ static void ot_keymgr_reset_enter(Object *obj, ResetType type) s->prng.reseed_cnt = 0u; s->op_state.op_req = false; s->op_state.op_ack = false; + s->op_state.stage = KEYMGR_STAGE_DISABLE; + s->op_state.adv_cdi_cnt = 0u; + ot_keymgr_reset_kdf_buffer(s); /* reset key state */ memset(s->key_states, 0u, NUM_CDIS * sizeof(OtKeyMgrKey)); @@ -1522,6 +1795,7 @@ static void ot_keymgr_init(Object *obj) s->prng.state = ot_prng_allocate(); + s->kdf_buf.data = g_new0(uint8_t, KEYMGR_KDF_BUFFER_BYTES); s->salt = g_new0(uint8_t, KEYMGR_SALT_BYTES); s->sealing_sw_binding = g_new0(uint8_t, KEYMGR_SW_BINDING_BYTES); s->attest_sw_binding = g_new0(uint8_t, KEYMGR_SW_BINDING_BYTES); diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 56d6263c6faf1..bc1a0d40c8907 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -278,10 +278,13 @@ ot_ibex_wrapper_unmap(const char *id, unsigned slot) "%s: region %u" ot_ibex_wrapper_update_exec(const char *id, uint32_t bm, bool esc_rx, bool halted, bool in_reset, bool cpu_en) "%s: bm:0x%x esc:%u halted:%u in_reset:%u -> CPU enable %u" # ot_keymgr.c +ot_keymgr_advance(const char *id, const char *stage, int nstage, const char *cdi, int ncdi) "%s: [%s:%d], [%s:%d]" ot_keymgr_change_main_fsm_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_change_op_status(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_change_working_state(const char *id, int line, const char *old, int nold, const char *new, int nnew) "%s: @ %d [%s:%d] -> [%s:%d]" ot_keymgr_dump_creator_root_key(const char *id, unsigned idx, bool valid, const char *hexstr) "%s: share:%u valid:%u %s" +ot_keymgr_dump_kdf_buf(const char *id, const char *op, size_t idx, const char *hexstr) "%s: %s: kdf[%02zu] %s" +ot_keymgr_dump_kdf_material(const char *id, const char *what, const char *hexstr) "%s: %s %s" ot_keymgr_entropy(const char *id, unsigned reseed_cnt, bool resched) "%s: reseed_cnt: %u, resched: %u" ot_keymgr_error(const char *id, const char *msg) "%s: %s" ot_keymgr_go_idle(const char *id) "%s" @@ -290,7 +293,7 @@ ot_keymgr_io_write(const char *id, uint32_t addr, const char *regname, uint32_t ot_keymgr_irq(const char *id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" ot_keymgr_lc_signal(const char *id, int level) "%s: LC signal level:%d" ot_keymgr_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" -ot_keymgr_operation(const char *id, const char *op, int nop) "%s: op:[%s:%d]" +ot_keymgr_operation(const char *id, const char *stage, const char *op, int nop) "%s: stage:%s op:[%s:%d]" ot_keymgr_reset(const char *id, const char *stage) "%s: %s" ot_keymgr_schedule_fsm(const char *id, const char *func, int line) "%s @ %s:%d" ot_keymgr_seed_missing(const char *id, unsigned ix) "%s: #%u" From 70b564e5d44eee83929bde48024d9ff4a8b7ef4e Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sun, 31 Aug 2025 14:49:33 +0100 Subject: [PATCH 3/7] [ot] hw/opentitan: ot_keymgr: Implement KMAC KDF requests This is mostly based on the equivalent keymgr_dpe implementation. Adds a connection from the keymgr to the KMAC via the KMAC app interface, and add logic for sending a KMAC request containing the contents of the KDF buffer. Also stub out a function for handling the KMAC response - the resulting logic will depend on the current operation, but for now is stubbed. Signed-off-by: Alex Jones --- hw/opentitan/ot_keymgr.c | 87 +++++++++++++++++++++++++++++++++++++++ hw/opentitan/trace-events | 2 + hw/riscv/ot_earlgrey.c | 4 +- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/hw/opentitan/ot_keymgr.c b/hw/opentitan/ot_keymgr.c index 2c3768a1d6cb4..7dd7369d52b0e 100644 --- a/hw/opentitan/ot_keymgr.c +++ b/hw/opentitan/ot_keymgr.c @@ -425,6 +425,8 @@ typedef struct OtKeyMgrState { /* properties */ char *ot_id; OtKeyMgrEDN edn; + OtKMACState *kmac; + uint8_t kmac_app; OtFlashState *flash_ctrl; OtLcCtrlState *lc_ctrl; OtOTPState *otp_ctrl; @@ -437,6 +439,9 @@ struct OtKeyMgrClass { ResettablePhases parent_phases; }; +/* see kmac_pkg::AppCfg in `kmac_pkg.sv` */ +static const OtKMACAppCfg KMAC_APP_CFG = OT_KMAC_CONFIG(KMAC, 256u, "KMAC", ""); + #define REG_NAME_ENTRY(_reg_) [R_##_reg_] = stringify(_reg_) static const char *REG_NAMES[REGS_COUNT] = { REG_NAME_ENTRY(INTR_STATE), @@ -941,6 +946,33 @@ static void ot_keymgr_xchange_main_fsm_state(OtKeyMgrState *s, } } +static void ot_keymgr_send_kmac_req(OtKeyMgrState *s) +{ + g_assert(s->kdf_buf.length); + g_assert(s->kdf_buf.offset < s->kdf_buf.length); + + unsigned msg_len = s->kdf_buf.length - s->kdf_buf.offset; + if (msg_len > OT_KMAC_APP_MSG_BYTES) { + msg_len = OT_KMAC_APP_MSG_BYTES; + } + + unsigned next_offset = s->kdf_buf.offset + msg_len; + + OtKMACAppReq req = { + .last = next_offset == s->kdf_buf.length, + .msg_len = msg_len, + }; + memcpy(req.msg_data, &s->kdf_buf.data[s->kdf_buf.offset], msg_len); + + trace_ot_keymgr_kmac_req(s->ot_id, s->kdf_buf.length, s->kdf_buf.offset, + req.msg_len, req.last); + + s->kdf_buf.offset = next_offset; + + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->app_request(s->kmac, s->kmac_app, &req); +} + static void ot_keymgr_operation_advance(OtKeyMgrState *s, OtKeyMgrStage stage, OtKeyMgrCdi cdi) { @@ -1051,6 +1083,52 @@ static void ot_keymgr_start_operation(OtKeyMgrState *s) } } +static void +ot_keymgr_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) +{ + OtKeyMgrState *s = OT_KEYMGR(opaque); + + if (trace_event_get_state(TRACE_OT_KEYMGR_KMAC_RSP)) { + /* if tracing, trace the unmasked KMAC response key */ + if (rsp->done) { + uint8_t key[OT_KMAC_APP_DIGEST_BYTES]; + for (unsigned ix = 0u; ix < OT_KMAC_APP_DIGEST_BYTES; ix++) { + key[ix] = rsp->digest_share0[ix] ^ rsp->digest_share1[ix]; + } + trace_ot_keymgr_kmac_rsp( + s->ot_id, true, + ot_keymgr_dump_bigint(s, key, OT_KMAC_APP_DIGEST_BYTES)); + } else { + trace_ot_keymgr_kmac_rsp(s->ot_id, false, ""); + } + } + + if (!rsp->done) { + /* not the last response from KMAC, send more data */ + ot_keymgr_send_kmac_req(s); + return; + } + + g_assert(s->kdf_buf.offset == s->kdf_buf.length); + + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + int op = (int)FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION); + switch (op) { + case KEYMGR_OP_ADVANCE: + case KEYMGR_OP_GENERATE_ID: + case KEYMGR_OP_GENERATE_SW_OUTPUT: + case KEYMGR_OP_GENERATE_HW_OUTPUT: + case KEYMGR_OP_DISABLE: + /* @todo: handle KMAC app responses in different keymgr ops */ + qemu_log_mask(LOG_UNIMP, + "%s: %s: KMAC response in op %s is not implemented.\n", + __func__, s->ot_id, OP_NAME(s->state)); + return; + default: + g_assert_not_reached(); + } +} + /* Tick the main FSM. Returns whether there was any state change this tick. */ static bool ot_keymgr_main_fsm_tick(OtKeyMgrState *s) { @@ -1656,6 +1734,8 @@ static Property ot_keymgr_properties[] = { DEFINE_PROP_UINT8("edn-ep", OtKeyMgrState, edn.ep, UINT8_MAX), DEFINE_PROP_LINK("flash_ctrl", OtKeyMgrState, flash_ctrl, TYPE_OT_FLASH, OtFlashState *), + DEFINE_PROP_LINK("kmac", OtKeyMgrState, kmac, TYPE_OT_KMAC, OtKMACState *), + DEFINE_PROP_UINT8("kmac-app", OtKeyMgrState, kmac_app, UINT8_MAX), DEFINE_PROP_LINK("lc_ctrl", OtKeyMgrState, lc_ctrl, TYPE_OT_LC_CTRL, OtLcCtrlState *), DEFINE_PROP_LINK("otp_ctrl", OtKeyMgrState, otp_ctrl, TYPE_OT_OTP, @@ -1711,6 +1791,8 @@ static void ot_keymgr_reset_enter(Object *obj, ResetType type) g_assert(s->edn.device); g_assert(s->edn.ep != UINT8_MAX); g_assert(s->flash_ctrl); + g_assert(s->kmac); + g_assert(s->kmac_app != UINT8_MAX); g_assert(s->lc_ctrl); g_assert(s->otp_ctrl); g_assert(s->rom_ctrl); @@ -1750,6 +1832,11 @@ static void ot_keymgr_reset_enter(Object *obj, ResetType type) /* update IRQ and alert states */ ot_keymgr_update_irq(s); ot_keymgr_update_alerts(s); + + /* connect to KMAC */ + OtKMACClass *kc = OT_KMAC_GET_CLASS(s->kmac); + kc->connect_app(s->kmac, s->kmac_app, &KMAC_APP_CFG, + ot_keymgr_handle_kmac_response, s); } static void ot_keymgr_reset_exit(Object *obj, ResetType type) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index bc1a0d40c8907..20975765d07fc 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -291,6 +291,8 @@ ot_keymgr_go_idle(const char *id) "%s" ot_keymgr_io_read_out(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" ot_keymgr_io_write(const char *id, uint32_t addr, const char *regname, uint32_t val, uint32_t pc) "%s: addr=0x%02x (%s), val=0x%08x, pc=0x%x" ot_keymgr_irq(const char *id, uint32_t active, uint32_t mask, bool level) "%s: act:0x%08x msk:0x%08x lvl:%u" +ot_keymgr_kmac_req(const char *id, uint32_t fifo_len, uint32_t offset, uint32_t msg_len, bool last) "%s: fifo_len:%u offset:%u msg_len:%u, last:%d" +ot_keymgr_kmac_rsp(const char *id, bool done, const char *hexstr) "%s: done:%d %s" ot_keymgr_lc_signal(const char *id, int level) "%s: LC signal level:%d" ot_keymgr_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" ot_keymgr_operation(const char *id, const char *stage, const char *op, int nop) "%s: stage:%s op:[%s:%d]" diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 392c7c77d3d0d..78699af91e117 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1188,12 +1188,14 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { .link = IBEXDEVICELINKDEFS( OT_EG_SOC_DEVLINK("edn", EDN0), OT_EG_SOC_DEVLINK("flash_ctrl", FLASH_CTRL), + OT_EG_SOC_DEVLINK("kmac", KMAC), OT_EG_SOC_DEVLINK("lc_ctrl", LC_CTRL), OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL), OT_EG_SOC_DEVLINK("rom_ctrl", ROM_CTRL) ), .prop = IBEXDEVICEPROPDEFS( - IBEX_DEV_UINT_PROP("edn-ep", 0u) + IBEX_DEV_UINT_PROP("edn-ep", 0u), + IBEX_DEV_UINT_PROP("kmac-app", 0u) ), }, [OT_EG_SOC_DEV_CSRNG] = { From 64df2bf066f0b05e0bc5b315575a09bf1aca137f Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sun, 31 Aug 2025 15:07:52 +0100 Subject: [PATCH 4/7] [ot] hw/opentitan: ot_keymgr: Connect sideloading key sinks Connect sideloading key sinks for AES, KMAC and OTBN to allow the keymgr to send keys to these blocks. This commit only introduces logic to push keys to the key sink interfaces, and does not yet implement any sideloading behaviour or related software register interfaces. Signed-off-by: Alex Jones --- hw/opentitan/ot_keymgr.c | 84 +++++++++++++++++++++++++++++++++++++++ hw/opentitan/trace-events | 1 + hw/riscv/ot_earlgrey.c | 2 + 3 files changed, 87 insertions(+) diff --git a/hw/opentitan/ot_keymgr.c b/hw/opentitan/ot_keymgr.c index 7dd7369d52b0e..3f829a1606284 100644 --- a/hw/opentitan/ot_keymgr.c +++ b/hw/opentitan/ot_keymgr.c @@ -31,13 +31,16 @@ #include "qemu/log.h" #include "qemu/main-loop.h" #include "qapi/error.h" +#include "hw/opentitan/ot_aes.h" #include "hw/opentitan/ot_alert.h" #include "hw/opentitan/ot_common.h" #include "hw/opentitan/ot_edn.h" #include "hw/opentitan/ot_flash.h" +#include "hw/opentitan/ot_key_sink.h" #include "hw/opentitan/ot_keymgr.h" #include "hw/opentitan/ot_kmac.h" #include "hw/opentitan/ot_lc_ctrl.h" +#include "hw/opentitan/ot_otbn.h" #include "hw/opentitan/ot_otp.h" #include "hw/opentitan/ot_prng.h" #include "hw/opentitan/ot_rom_ctrl.h" @@ -82,6 +85,8 @@ #define KEYMGR_KDF_BUFFER_WIDTH 1600u #define KEYMGR_KDF_BUFFER_BYTES ((KEYMGR_KDF_BUFFER_WIDTH) / 8u) #define KEYMGR_SEED_BYTES (KEYMGR_KEY_BYTES) +#define _MAX(_a_, _b_) ((_a_) > (_b_) ? (_a_) : (_b_)) +#define KEYMGR_KEY_SIZE_MAX _MAX(KEYMGR_KEY_BYTES, OT_OTBN_KEY_SIZE) /* * The EDN provides words of entropy at a time, so the keymgr needs @@ -103,6 +108,8 @@ static_assert(KEYMGR_KEY_BYTES == OT_OTP_KEYMGR_SECRET_SIZE, static_assert(KEYMGR_KEY_BYTES == OT_KMAC_KEY_SIZE, "KeyMgr key size does not match KMAC key size"); /* NOLINTEND(misc-redundant-expression) */ +static_assert(KEYMGR_KEY_SIZE_MAX <= OT_KMAC_APP_DIGEST_BYTES, + "KeyMgr key size does not match KMAC digest size"); #define KEYMGR_ENTROPY_WIDTH (KEYMGR_LFSR_WIDTH / 2u) #define KEYMGR_ENTROPY_ROUNDS (KEYMGR_KEY_WIDTH / KEYMGR_ENTROPY_WIDTH) @@ -431,6 +438,7 @@ typedef struct OtKeyMgrState { OtLcCtrlState *lc_ctrl; OtOTPState *otp_ctrl; OtRomCtrlState *rom_ctrl; + DeviceState *key_sinks[KEYMGR_KEY_SINK_COUNT]; char *seed_xstrs[KEYMGR_SEED_COUNT]; } OtKeyMgrState; @@ -439,6 +447,12 @@ struct OtKeyMgrClass { ResettablePhases parent_phases; }; +static const size_t OT_KEY_MGR_KEY_SINK_SIZES[KEYMGR_KEY_SINK_COUNT] = { + [KEYMGR_KEY_SINK_AES] = OT_AES_KEY_SIZE, + [KEYMGR_KEY_SINK_KMAC] = OT_KMAC_KEY_SIZE, + [KEYMGR_KEY_SINK_OTBN] = OT_OTBN_KEY_SIZE, +}; + /* see kmac_pkg::AppCfg in `kmac_pkg.sv` */ static const OtKMACAppCfg KMAC_APP_CFG = OT_KMAC_CONFIG(KMAC, 256u, "KMAC", ""); @@ -544,6 +558,18 @@ static const char *OP_NAMES[] = { #define OP_NAME(_op_) \ (((unsigned)(_op_)) < ARRAY_SIZE(OP_NAMES) ? OP_NAMES[(_op_)] : "?") +#define KEY_SINK_ENTRY(_st_) [KEYMGR_KEY_SINK_##_st_] = stringify(_st_) +static const char *KEY_SINK_NAMES[] = { + KEY_SINK_ENTRY(AES), + KEY_SINK_ENTRY(KMAC), + KEY_SINK_ENTRY(OTBN), +}; +#undef KEY_SINK_ENTRY +#define KEY_SINK_NAME(_st_) \ + (((unsigned)(_st_)) < ARRAY_SIZE(KEY_SINK_NAMES) ? \ + KEY_SINK_NAMES[(_st_)] : \ + "?") + #define WORKING_STATE_ENTRY(_st_) \ [KEYMGR_WORKING_STATE_##_st_] = stringify(_st_) static const char *WORKING_STATE_NAMES[] = { @@ -787,6 +813,42 @@ static void ot_keymgr_request_entropy(OtKeyMgrState *s) } } +static void ot_keymgr_push_key(OtKeyMgrState *s, OtKeyMgrKeySink key_sink, + const uint8_t *key_share0, + const uint8_t *key_share1, bool valid) +{ + g_assert((unsigned)key_sink < KEYMGR_KEY_SINK_COUNT); + + size_t key_size = OT_KEY_MGR_KEY_SINK_SIZES[key_sink]; + DeviceState *sink = s->key_sinks[key_sink]; + + g_assert(sink); + + if (trace_event_get_state(TRACE_OT_KEYMGR_PUSH_KEY)) { + if (!key_share0 || !key_share1) { + trace_ot_keymgr_push_key(s->ot_id, KEY_SINK_NAME(key_sink), valid, + ""); + } else { + /* compute the unmasked key for tracing */ + uint8_t key_value[KEYMGR_KEY_SIZE_MAX]; + for (unsigned ix = 0u; ix < key_size; ix++) { + key_value[ix] = key_share0[ix] ^ key_share1[ix]; + } + + trace_ot_keymgr_push_key(s->ot_id, KEY_SINK_NAME(key_sink), valid, + ot_keymgr_dump_bigint(s, key_value, + key_size)); + } + } + + OtKeySinkIfClass *kc = OT_KEY_SINK_IF_GET_CLASS(sink); + OtKeySinkIf *ki = OT_KEY_SINK_IF(sink); + + g_assert(kc->push_key); + + kc->push_key(ki, key_share0, key_share1, key_size, valid); +} + /* check that 'data' is not all zeros or all ones */ static bool ot_keymgr_valid_data_check(const uint8_t *data, size_t len) { @@ -1742,6 +1804,10 @@ static Property ot_keymgr_properties[] = { OtOTPState *), DEFINE_PROP_LINK("rom_ctrl", OtKeyMgrState, rom_ctrl, TYPE_OT_ROM_CTRL, OtRomCtrlState *), + DEFINE_PROP_LINK("aes", OtKeyMgrState, key_sinks[KEYMGR_KEY_SINK_AES], + TYPE_OT_KEY_SINK_IF, DeviceState *), + DEFINE_PROP_LINK("otbn", OtKeyMgrState, key_sinks[KEYMGR_KEY_SINK_OTBN], + TYPE_OT_KEY_SINK_IF, DeviceState *), DEFINE_PROP_STRING("lfsr_seed", OtKeyMgrState, seed_xstrs[KEYMGR_SEED_LFSR]), DEFINE_PROP_STRING("revision_seed", OtKeyMgrState, @@ -1849,6 +1915,12 @@ static void ot_keymgr_reset_exit(Object *obj, ResetType type) if (c->parent_phases.exit) { c->parent_phases.exit(obj, type); } + + /* invalidate all sideloaded keys when exiting reset */ + for (unsigned ix = 0u; ix < KEYMGR_KEY_SINK_COUNT; ix++) { + OtKeyMgrKeySink key_sink = (OtKeyMgrKeySink)ix; + ot_keymgr_push_key(s, key_sink, NULL, NULL, false); + } } static void ot_keymgr_realize(DeviceState *dev, Error **errp) @@ -1862,7 +1934,19 @@ static void ot_keymgr_realize(DeviceState *dev, Error **errp) g_strdup(object_get_canonical_path_component(OBJECT(s)->parent)); } + for (unsigned ix = 0u; ix < KEYMGR_KEY_SINK_COUNT; ix++) { + if (s->key_sinks[ix]) { + OBJECT_CHECK(OtKeySinkIf, s->key_sinks[ix], TYPE_OT_KEY_SINK_IF); + } + } + ot_keymgr_configure_constants(s); + + /* + * Define KMAC separately in `s->kmac` instead of directly as a key sink, + * as we also need to offload KDF operations to it. + */ + s->key_sinks[KEYMGR_KEY_SINK_KMAC] = DEVICE(s->kmac); } static void ot_keymgr_init(Object *obj) diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index 20975765d07fc..fb334087ca4a1 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -296,6 +296,7 @@ ot_keymgr_kmac_rsp(const char *id, bool done, const char *hexstr) "%s: done:%d % ot_keymgr_lc_signal(const char *id, int level) "%s: LC signal level:%d" ot_keymgr_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" ot_keymgr_operation(const char *id, const char *stage, const char *op, int nop) "%s: stage:%s op:[%s:%d]" +ot_keymgr_push_key(const char *id, const char *sink, bool valid, const char *hexstr) "%s: sink:%s valid:%u key:%s" ot_keymgr_reset(const char *id, const char *stage) "%s: %s" ot_keymgr_schedule_fsm(const char *id, const char *func, int line) "%s @ %s:%d" ot_keymgr_seed_missing(const char *id, unsigned ix) "%s: #%u" diff --git a/hw/riscv/ot_earlgrey.c b/hw/riscv/ot_earlgrey.c index 78699af91e117..a971dda944e2a 100644 --- a/hw/riscv/ot_earlgrey.c +++ b/hw/riscv/ot_earlgrey.c @@ -1186,10 +1186,12 @@ static const IbexDeviceDef ot_eg_soc_devices[] = { OT_EG_SOC_GPIO_ALERT(1, 50) ), .link = IBEXDEVICELINKDEFS( + OT_EG_SOC_DEVLINK("aes", AES), OT_EG_SOC_DEVLINK("edn", EDN0), OT_EG_SOC_DEVLINK("flash_ctrl", FLASH_CTRL), OT_EG_SOC_DEVLINK("kmac", KMAC), OT_EG_SOC_DEVLINK("lc_ctrl", LC_CTRL), + OT_EG_SOC_DEVLINK("otbn", OTBN), OT_EG_SOC_DEVLINK("otp_ctrl", OTP_CTRL), OT_EG_SOC_DEVLINK("rom_ctrl", ROM_CTRL) ), From a310e84524bd0c9e4ea3a65379139325b03d3263 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Sun, 31 Aug 2025 22:12:22 +0100 Subject: [PATCH 5/7] [ot] hw/opentitan: ot_keymgr: Add advance key derivation Finishes the implementation of the keymgr's `Advance` operation, with logic for all key stages (still pending FSM key states). When an `Advance` operation is issued, the current key state is sideloaded to the KMAC, and the contents of the computed KDF buffer are processed to derive the next key state. One key difference from `keymgr_dpe` is that both CDIs must be advanced in a single advance operation, and so after receiving the KMAC response and storing the key state, we send another KMAC app request to advance the sealing key also. This continues for all CDIs (currently only 2). One other key difference to note is that `keymgr` does not unlock the max key version REGWENs on advancement, whereas the `keymgr_dpe` does (see the `keymgr.sv` and `keymgr_dpe.sv` RTL). Signed-off-by: Alex Jones --- hw/opentitan/ot_keymgr.c | 86 ++++++++++++++++++++++++++++++++++----- hw/opentitan/trace-events | 1 + 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/hw/opentitan/ot_keymgr.c b/hw/opentitan/ot_keymgr.c index 3f829a1606284..2ed955c996d7a 100644 --- a/hw/opentitan/ot_keymgr.c +++ b/hw/opentitan/ot_keymgr.c @@ -1102,12 +1102,15 @@ static void ot_keymgr_operation_advance(OtKeyMgrState *s, OtKeyMgrStage stage, g_assert(s->kdf_buf.length <= KEYMGR_ADV_DATA_BYTES); s->kdf_buf.length = KEYMGR_ADV_DATA_BYTES; - ot_keymgr_dump_kdf_buf(s, "adv"); + /* send the current key state to KMAC as the KDF key */ + g_assert(cdi < NUM_CDIS); + OtKeyMgrKey *key_state = &s->key_states[cdi]; + ot_keymgr_push_key(s, KEYMGR_KEY_SINK_KMAC, key_state->share0, + key_state->share1, key_state->valid); - /* - * @todo: send the KMAC KDF request and handle the response. When we get a - * response, transition the main FSM to `KEYMGR_ST_CREATOR_ROOT_KEY`. - */ + /* transmit the contents of the KDF buffer to KMAC for computation */ + ot_keymgr_dump_kdf_buf(s, "adv"); + ot_keymgr_send_kmac_req(s); } static void ot_keymgr_operation_disable(OtKeyMgrState *s) @@ -1131,10 +1134,6 @@ static void ot_keymgr_start_operation(OtKeyMgrState *s) s->op_state.adv_cdi_cnt = (OtKeyMgrCdi)0; ot_keymgr_operation_advance(s, s->op_state.stage, s->op_state.adv_cdi_cnt); - /* - * @todo: when we get a KMAC response with the sealing key, we should - * then send a request for the attestation CDI before completing. - */ break; case KEYMGR_OP_DISABLE: ot_keymgr_operation_disable(s); @@ -1145,6 +1144,62 @@ static void ot_keymgr_start_operation(OtKeyMgrState *s) } } +static void ot_keymgr_reset_kmac_key(OtKeyMgrState *s) +{ + /* @todo: when not in use, this should be filled with dummy entropy */ + trace_ot_keymgr_reset_kmac_key(s->ot_id); + ot_keymgr_push_key(s, KEYMGR_KEY_SINK_KMAC, NULL, NULL, false); +} + +static bool +ot_keymgr_handle_kmac_resp_advance(OtKeyMgrState *s, const OtKMACAppRsp *rsp) +{ + unsigned cdi = s->op_state.adv_cdi_cnt; + + g_assert(cdi < NUM_CDIS); + + OtKeyMgrKey *key_state = &s->key_states[cdi]; + key_state->valid = true; + memcpy(key_state->share0, rsp->digest_share0, OT_KMAC_KEY_SIZE); + memcpy(key_state->share1, rsp->digest_share1, OT_KMAC_KEY_SIZE); + + /* SW can lock the `SW_BINDING` regs, and HW unlocks after an advance */ + s->regs[R_SW_BINDING_REGWEN] |= R_SW_BINDING_REGWEN_EN_MASK; + + unsigned next_cdi = cdi + 1; + if (next_cdi < NUM_CDIS) { + /* when advancing, we must advance all CDIs before completing */ + s->op_state.adv_cdi_cnt = next_cdi; + ot_keymgr_operation_advance(s, s->op_state.stage, next_cdi); + return false; + } + + /* all CDIs have been advanced, so complete the advance operation */ + ot_keymgr_reset_kmac_key(s); + s->op_state.adv_cdi_cnt = 0u; + + /* SW can lock the `SW_BINDING` regs, and HW unlocks after an advance */ + s->regs[R_SW_BINDING_REGWEN] |= R_SW_BINDING_REGWEN_EN_MASK; + + switch (s->op_state.stage) { + case KEYMGR_STAGE_CREATOR: + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_CREATOR_ROOT_KEY); + break; + case KEYMGR_STAGE_OWNER_INT: + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_OWNER_INT_KEY); + break; + case KEYMGR_STAGE_OWNER: + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_OWNER_KEY); + break; + case KEYMGR_STAGE_DISABLE: + default: + /* should not be advancing to the `disable` state */ + g_assert_not_reached(); + } + + return true; +} + static void ot_keymgr_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) { @@ -1174,21 +1229,32 @@ ot_keymgr_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) g_assert(s->kdf_buf.offset == s->kdf_buf.length); uint32_t ctrl = ot_shadow_reg_peek(&s->control); + bool op_complete; + int op = (int)FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION); switch (op) { case KEYMGR_OP_ADVANCE: + op_complete = ot_keymgr_handle_kmac_resp_advance(s, rsp); + break; case KEYMGR_OP_GENERATE_ID: case KEYMGR_OP_GENERATE_SW_OUTPUT: case KEYMGR_OP_GENERATE_HW_OUTPUT: case KEYMGR_OP_DISABLE: /* @todo: handle KMAC app responses in different keymgr ops */ + op_complete = true; qemu_log_mask(LOG_UNIMP, "%s: %s: KMAC response in op %s is not implemented.\n", __func__, s->ot_id, OP_NAME(s->state)); - return; + break; default: g_assert_not_reached(); } + + if (op_complete) { + /* complete the operation, and reschedule the FSM */ + s->op_state.op_ack = true; + ot_keymgr_schedule_fsm(s); + } } /* Tick the main FSM. Returns whether there was any state change this tick. */ diff --git a/hw/opentitan/trace-events b/hw/opentitan/trace-events index fb334087ca4a1..8e0c225f19239 100644 --- a/hw/opentitan/trace-events +++ b/hw/opentitan/trace-events @@ -298,6 +298,7 @@ ot_keymgr_main_fsm_tick(const char *id, const char *st, int nst) "%s: [%s:%d]" ot_keymgr_operation(const char *id, const char *stage, const char *op, int nop) "%s: stage:%s op:[%s:%d]" ot_keymgr_push_key(const char *id, const char *sink, bool valid, const char *hexstr) "%s: sink:%s valid:%u key:%s" ot_keymgr_reset(const char *id, const char *stage) "%s: %s" +ot_keymgr_reset_kmac_key(const char *id) "%s" ot_keymgr_schedule_fsm(const char *id, const char *func, int line) "%s @ %s:%d" ot_keymgr_seed_missing(const char *id, unsigned ix) "%s: #%u" ot_keymgr_update_alert(const char *id, int prev, int next) "%s: %d -> %d" From a30826f6fb2f1187a55aad8d68f65c9d15a939eb Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Mon, 1 Sep 2025 17:03:22 +0100 Subject: [PATCH 6/7] [ot] hw/opentitan: ot_keymgr: Add remaining FSM states This commit implements all remaining `keymgr` FSM states, almost all of which are unique to `keymgr` and not in `keymgr_dpe`. Advancing and disabling is now supported for all key stage states. To reduce duplication, the different key FSM states are abstracted into a generic `ot_keymgr_fsm_key_stage(...)` function. Signed-off-by: Alex Jones --- hw/opentitan/ot_keymgr.c | 142 ++++++++++++++++++++++++++++++--------- 1 file changed, 109 insertions(+), 33 deletions(-) diff --git a/hw/opentitan/ot_keymgr.c b/hw/opentitan/ot_keymgr.c index 2ed955c996d7a..eb9ab941d210f 100644 --- a/hw/opentitan/ot_keymgr.c +++ b/hw/opentitan/ot_keymgr.c @@ -813,6 +813,16 @@ static void ot_keymgr_request_entropy(OtKeyMgrState *s) } } +static void ot_keymgr_wipe_key_states(OtKeyMgrState *s) +{ + /* @todo: should wipe with random entropy, but just zero for now */ + for (unsigned cdi = 0u; cdi < NUM_CDIS; cdi++) { + memset(s->key_states[cdi].share0, 0u, KEYMGR_KEY_BYTES); + memset(s->key_states[cdi].share1, 0u, KEYMGR_KEY_BYTES); + s->key_states[cdi].valid = false; + } +} + static void ot_keymgr_push_key(OtKeyMgrState *s, OtKeyMgrKeySink key_sink, const uint8_t *key_share0, const uint8_t *key_share1, bool valid) @@ -1035,6 +1045,14 @@ static void ot_keymgr_send_kmac_req(OtKeyMgrState *s) kc->app_request(s->kmac, s->kmac_app, &req); } +static void ot_keymgr_operation_disable(OtKeyMgrState *s) +{ + ot_keymgr_wipe_key_states(s); + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_DISABLED); + s->op_state.op_ack = true; + ot_keymgr_schedule_fsm(s); +} + static void ot_keymgr_operation_advance(OtKeyMgrState *s, OtKeyMgrStage stage, OtKeyMgrCdi cdi) { @@ -1076,12 +1094,10 @@ static void ot_keymgr_operation_advance(OtKeyMgrState *s, OtKeyMgrStage stage, "OWNER_SEED", &dvalid); break; case KEYMGR_STAGE_DISABLE: - qemu_log_mask(LOG_GUEST_ERROR, - "%s: %s: advance called in the %s key stage\n.", __func__, - s->ot_id, STAGE_NAME(stage)); + /* you can "advance" from the OwnerRootKey to the `Disabled` state */ + ot_keymgr_operation_disable(s); return; default: - /* should only be called with a valid stage */ g_assert_not_reached(); } @@ -1113,14 +1129,6 @@ static void ot_keymgr_operation_advance(OtKeyMgrState *s, OtKeyMgrStage stage, ot_keymgr_send_kmac_req(s); } -static void ot_keymgr_operation_disable(OtKeyMgrState *s) -{ - /* @todo: wipe the current keys with some dummy entropy data */ - ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_DISABLED); - s->op_state.op_ack = true; - ot_keymgr_schedule_fsm(s); -} - static void ot_keymgr_start_operation(OtKeyMgrState *s) { uint32_t ctrl = ot_shadow_reg_peek(&s->control); @@ -1135,6 +1143,13 @@ static void ot_keymgr_start_operation(OtKeyMgrState *s) ot_keymgr_operation_advance(s, s->op_state.stage, s->op_state.adv_cdi_cnt); break; + case KEYMGR_OP_GENERATE_ID: + case KEYMGR_OP_GENERATE_SW_OUTPUT: + case KEYMGR_OP_GENERATE_HW_OUTPUT: + /* @todo: implement generate operations */ + qemu_log_mask(LOG_UNIMP, "%s: %s, Operation %s is not implemented.\n", + __func__, s->ot_id, OP_NAME(op)); + break; case KEYMGR_OP_DISABLE: ot_keymgr_operation_disable(s); break; @@ -1257,6 +1272,54 @@ ot_keymgr_handle_kmac_response(void *opaque, const OtKMACAppRsp *rsp) } } +static void ot_keymgr_fsm_key_stage(OtKeyMgrState *s, bool supports_generation, + OtKeyMgrStage advance, + OtKeyMgrStage generate) +{ + bool op_start = s->regs[R_START] & R_START_EN_MASK; + bool invalid_state = s->regs[R_FAULT_STATUS] & FAULT_STATUS_MASK; + uint32_t ctrl = ot_shadow_reg_peek(&s->control); + + /* check for op errors before enablement & state validity */ + uint32_t op = FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION); + bool invalid_op = false; + switch (op) { + case KEYMGR_OP_GENERATE_ID: + case KEYMGR_OP_GENERATE_HW_OUTPUT: + case KEYMGR_OP_GENERATE_SW_OUTPUT: + if (op_start && !supports_generation) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + invalid_op = true; + } + break; + default: + break; + } + + /* wipe if disabled or in an invalid state */ + if (!s->enabled || invalid_state) { + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_WIPE); + return; + } + + if (!op_start || s->op_state.op_req || invalid_op) { + return; /* no state change if op_start is not set */ + } + + switch (op) { + case KEYMGR_OP_DISABLE: + s->op_state.stage = KEYMGR_STAGE_DISABLE; + break; + case KEYMGR_OP_ADVANCE: + s->op_state.stage = advance; + break; + default: + s->op_state.stage = generate; + } + s->op_state.op_req = true; + ot_keymgr_start_operation(s); +} + /* Tick the main FSM. Returns whether there was any state change this tick. */ static bool ot_keymgr_main_fsm_tick(OtKeyMgrState *s) { @@ -1341,34 +1404,47 @@ static bool ot_keymgr_main_fsm_tick(OtKeyMgrState *s) break; case KEYMGR_ST_INIT: ot_keymgr_change_working_state(s, KEYMGR_WORKING_STATE_INIT); - if (!s->enabled || invalid_state) { - ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_WIPE); - break; - } - if (!op_start) { - break; /* no state change if op_start is not set */ - } - uint32_t op = FIELD_EX32(ctrl, CONTROL_SHADOWED, OPERATION); - bool valid_op = (op == KEYMGR_OP_ADVANCE) || (op == KEYMGR_OP_DISABLE); - if (!valid_op) { - s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; - } else if (!s->op_state.op_req) { - s->op_state.stage = - (op == KEYMGR_OP_ADVANCE) ? KEYMGR_STAGE_CREATOR : - KEYMGR_STAGE_DISABLE; - s->op_state.op_req = true; - ot_keymgr_start_operation(s); - } + ot_keymgr_fsm_key_stage(s, false, KEYMGR_STAGE_CREATOR, + KEYMGR_STAGE_DISABLE); break; case KEYMGR_ST_CREATOR_ROOT_KEY: + ot_keymgr_change_working_state(s, + KEYMGR_WORKING_STATE_CREATOR_ROOT_KEY); + ot_keymgr_fsm_key_stage(s, true, KEYMGR_STAGE_OWNER_INT, + KEYMGR_STAGE_CREATOR); + break; case KEYMGR_ST_OWNER_INT_KEY: + ot_keymgr_change_working_state( + s, KEYMGR_WORKING_STATE_OWNER_INTERMEDIATE_KEY); + ot_keymgr_fsm_key_stage(s, true, KEYMGR_STAGE_OWNER, + KEYMGR_STAGE_OWNER_INT); + break; case KEYMGR_ST_OWNER_KEY: + ot_keymgr_change_working_state(s, KEYMGR_WORKING_STATE_OWNER_KEY); + ot_keymgr_fsm_key_stage(s, true, KEYMGR_STAGE_DISABLE, + KEYMGR_STAGE_OWNER); + break; case KEYMGR_ST_DISABLED: + ot_keymgr_change_working_state(s, KEYMGR_WORKING_STATE_DISABLED); + if (!s->enabled || invalid_state) { + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_WIPE); + } + break; case KEYMGR_ST_WIPE: + /* whilst wiping, keymgr retains the previous working state */ + ot_keymgr_wipe_key_states(s); + /* see OT keymgr_ctrl.sv RTL: maintain & complete ongoing ops */ + if (op_start) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + } else { + ot_keymgr_change_main_fsm_state(s, KEYMGR_ST_INVALID); + } + break; case KEYMGR_ST_INVALID: - /* @todo: implement other keymgr FSM states */ - qemu_log_mask(LOG_UNIMP, "%s: %s: FSM State %s is not implemented.\n", - __func__, s->ot_id, FST_NAME(s->state)); + ot_keymgr_change_working_state(s, KEYMGR_WORKING_STATE_INVALID); + if (op_start) { + s->regs[R_ERR_CODE] |= R_ERR_CODE_INVALID_OP_MASK; + } break; default: break; From e2265b4692a13b945caf835d7c9263175c541300 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Mon, 15 Sep 2025 16:23:38 +0100 Subject: [PATCH 7/7] [ot] scripts/opentitan: tests: Mark keymgr_functest as passing Now that the `keymgr` is partially implemented, with the option to advance through all of its main FSM states (but no ability to generate output keys or sideload keys), this is sufficient to get the `keymgr_functest` passing. Signed-off-by: Alex Jones --- scripts/opentitan/tests-passing.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/opentitan/tests-passing.txt b/scripts/opentitan/tests-passing.txt index 6dccbfb1bc8e1..39a6b621df040 100644 --- a/scripts/opentitan/tests-passing.txt +++ b/scripts/opentitan/tests-passing.txt @@ -6,6 +6,7 @@ //sw/device/lib/crypto/drivers:rv_core_ibex_test_sim_qemu_rom_with_fake_keys //sw/device/silicon_creator/lib/drivers:hmac_functest_sim_qemu_rom_with_fake_keys //sw/device/silicon_creator/lib/drivers:kmac_functest_sim_qemu_rom_with_fake_keys +//sw/device/silicon_creator/lib/drivers:keymgr_functest_sim_qemu_rom_with_fake_keys //sw/device/silicon_creator/lib/drivers:retention_sram_functest_sim_qemu_rom_with_fake_keys //sw/device/silicon_creator/lib/drivers:rnd_functest_sim_qemu_rom_with_fake_keys //sw/device/silicon_creator/lib/drivers:rstmgr_functest_sim_qemu_rom_with_fake_keys