From 8836d1e6912114cda9824f43c1ae23ff49db39f4 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 22 Oct 2025 20:39:18 +0100 Subject: [PATCH 1/6] [ot] hw/opentitan: ot_otp_eg: Load OTP scrambling keys Load the OTP scrambling keys from hex-string configured properties. These keys are used for decoding the scrambled data stored in secret partitions (`SECRET0`, `SECRET1`, etc.) Signed-off-by: Alex Jones --- hw/opentitan/ot_otp_eg.c | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/hw/opentitan/ot_otp_eg.c b/hw/opentitan/ot_otp_eg.c index 558883f53a7f6..12f16501bb5bd 100644 --- a/hw/opentitan/ot_otp_eg.c +++ b/hw/opentitan/ot_otp_eg.c @@ -418,6 +418,11 @@ REG32(LC_STATE, 2008u) #define DAI_DIGEST_DELAY_NS 50000u /* 50us */ #define LCI_PROG_SCHED_NS 1000u /* 1us*/ +/* The size of keys used for OTP scrambling */ +#define OTP_SCRAMBLING_KEY_WIDTH 128u +#define OTP_SCRAMBLING_KEY_BYTES ((OTP_SCRAMBLING_KEY_WIDTH) / 8u) + +/* Sizes of constants used for deriving scrambling keys (e.g. flash, SRAM) */ #define FLASH_KEY_SEED_WIDTH 256u #define SRAM_KEY_SEED_WIDTH 128u #define KEY_MGR_KEY_WIDTH 256u @@ -682,6 +687,8 @@ struct OtOTPEgState { uint8_t flash_data_const[16u]; uint64_t flash_addr_iv; uint8_t flash_addr_const[16u]; + /* OTP scrambling key constants, not constants for deriving other keys */ + uint8_t *otp_scramble_keys[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ uint8_t *inv_default_parts[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ OtOTPStorage *otp; @@ -703,6 +710,7 @@ struct OtOTPEgState { char *flash_data_const_xstr; char *flash_addr_iv_xstr; char *flash_addr_const_xstr; + char *otp_scramble_key_xstrs[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ char *inv_default_part_xstrs[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ uint8_t edn_ep; bool fatal_escalate; @@ -3724,6 +3732,65 @@ static void ot_otp_eg_configure_sram(OtOTPEgState *s) s->sram_iv = ldq_le_p(sram_iv); } +static void ot_otp_eg_configure_part_scramble_keys(OtOTPEgState *s) +{ + for (unsigned ix = 0u; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { + if (!s->otp_scramble_key_xstrs[ix]) { + continue; + } + + size_t len = strlen(s->otp_scramble_key_xstrs[ix]); + if (len != OTP_SCRAMBLING_KEY_BYTES * 2u) { + error_setg( + &error_fatal, + "%s: %s Invalid OTP scrambling key length %zu for partition %u", + __func__, s->ot_id, len, ix); + return; + } + + g_assert(!s->otp_scramble_keys[ix]); + + s->otp_scramble_keys[ix] = g_new0(uint8_t, OTP_SCRAMBLING_KEY_BYTES); + if (ot_common_parse_hexa_str(s->otp_scramble_keys[ix], + s->otp_scramble_key_xstrs[ix], + OTP_SCRAMBLING_KEY_BYTES, true, true)) { + error_setg(&error_fatal, + "%s: %s unable to parse otp_scramble_keys[%u]", __func__, + s->ot_id, ix); + return; + } + + TRACE_OTP("otp_scramble_keys[%s] %s", PART_NAME(ix), + ot_otp_hexdump(s, s->otp_scramble_keys[ix], + OTP_SCRAMBLING_KEY_BYTES)); + } +} + +static void ot_otp_eg_class_add_scramble_key_props(OtOTPClass *odc) +{ + unsigned secret_ix = 0u; + for (unsigned ix = 0u; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { + if (!OtOTPPartDescs[ix].secret) { + continue; + } + + Property *prop = g_new0(Property, 1u); + + /* + * Assumes secret partitions are sequentially ordered and named + * SECRET0, SECRET1, SECRET2, etc. + */ + prop->name = g_strdup_printf("secret%u_scramble_key", secret_ix++); + prop->info = &qdev_prop_string; + prop->offset = offsetof(OtOTPEgState, otp_scramble_key_xstrs) + + sizeof(char *) * ix; + + object_class_property_add(OBJECT_CLASS(odc), prop->name, + prop->info->name, prop->info->get, + prop->info->set, prop->info->release, prop); + } +} + static void ot_otp_eg_configure_inv_default_parts(OtOTPEgState *s) { for (unsigned ix = 0; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { @@ -3943,6 +4010,7 @@ static void ot_otp_eg_realize(DeviceState *dev, Error **errp) ot_otp_eg_configure_digest(s); ot_otp_eg_configure_sram(s); ot_otp_eg_configure_flash(s); + ot_otp_eg_configure_part_scramble_keys(s); ot_otp_eg_configure_inv_default_parts(s); } @@ -4050,6 +4118,7 @@ static void ot_otp_eg_class_init(ObjectClass *klass, void *data) oc->get_keymgr_secret = &ot_otp_eg_get_keymgr_secret; oc->program_req = &ot_otp_eg_program_req; + ot_otp_eg_class_add_scramble_key_props(oc); ot_otp_eg_class_add_inv_def_props(oc); } From d63f68847bbbe5268c42ab7e3e0f8cca8e78eff1 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 22 Oct 2025 20:40:53 +0100 Subject: [PATCH 2/6] [ot] hw/opentitan: ot_otp_dj: Load OTP scrambling keys Load the OTP scrambling keys from hex-string configured properties. These keys are used for decoding the scrambled data stored in secret partitions (`SECRET0`, `SECRET1`, etc.) Signed-off-by: Alex Jones --- hw/opentitan/ot_otp_dj.c | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 968d21d03cb89..368e18eb2a201 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -490,6 +490,11 @@ REG32(LC_STATE, 16344u) #define DAI_DIGEST_DELAY_NS 50000u /* 50us */ #define LCI_PROG_SCHED_NS 1000u /* 1us*/ +/* The size of keys used for OTP scrambling */ +#define OTP_SCRAMBLING_KEY_WIDTH 128u +#define OTP_SCRAMBLING_KEY_BYTES ((OTP_SCRAMBLING_KEY_WIDTH) / 8u) + +/* Sizes of constants used for deriving scrambling keys (e.g. SRAM, OTBN) */ #define SRAM_KEY_SEED_WIDTH (SRAM_DATA_KEY_SEED_SIZE * 8u) #define KEY_MGR_KEY_WIDTH 256u #define SRAM_KEY_WIDTH 128u @@ -749,6 +754,8 @@ struct OtOTPDjState { uint8_t digest_const[16u]; uint64_t sram_iv; uint8_t sram_const[16u]; + /* OTP scrambling key constants, not constants for deriving other keys */ + uint8_t *otp_scramble_keys[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ uint8_t *inv_default_parts[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ OtOTPStorage *otp; @@ -765,6 +772,7 @@ struct OtOTPDjState { char *digest_iv_xstr; char *sram_const_xstr; char *sram_iv_xstr; + char *otp_scramble_key_xstrs[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ char *inv_default_part_xstrs[ARRAY_SIZE(OtOTPPartDescs)]; /* may be NULL */ uint8_t edn_ep; bool fatal_escalate; @@ -3667,6 +3675,65 @@ static void ot_otp_dj_configure_sram(OtOTPDjState *s) s->sram_iv = ldq_le_p(sram_iv); } +static void ot_otp_dj_configure_part_scramble_keys(OtOTPDjState *s) +{ + for (unsigned ix = 0u; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { + if (!s->otp_scramble_key_xstrs[ix]) { + continue; + } + + size_t len = strlen(s->otp_scramble_key_xstrs[ix]); + if (len != OTP_SCRAMBLING_KEY_BYTES * 2u) { + error_setg( + &error_fatal, + "%s: %s Invalid OTP scrambling key length %zu for partition %u", + __func__, s->ot_id, len, ix); + return; + } + + g_assert(!s->otp_scramble_keys[ix]); + + s->otp_scramble_keys[ix] = g_new0(uint8_t, OTP_SCRAMBLING_KEY_BYTES); + if (ot_common_parse_hexa_str(s->otp_scramble_keys[ix], + s->otp_scramble_key_xstrs[ix], + OTP_SCRAMBLING_KEY_BYTES, true, true)) { + error_setg(&error_fatal, + "%s: %s unable to parse otp_scramble_keys[%u]", __func__, + s->ot_id, ix); + return; + } + + TRACE_OTP("otp_scramble_keys[%s] %s", PART_NAME(ix), + ot_otp_hexdump(s, s->otp_scramble_keys[ix], + OTP_SCRAMBLING_KEY_BYTES)); + } +} + +static void ot_otp_dj_class_add_scramble_key_props(OtOTPClass *odc) +{ + unsigned secret_ix = 0u; + for (unsigned ix = 0u; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { + if (!OtOTPPartDescs[ix].secret) { + continue; + } + + Property *prop = g_new0(Property, 1u); + + /* + * Assumes secret partitions are sequentially ordered and named + * SECRET0, SECRET1, SECRET2, SECRET3 etc. + */ + prop->name = g_strdup_printf("secret%u_scramble_key", secret_ix++); + prop->info = &qdev_prop_string; + prop->offset = offsetof(OtOTPDjState, otp_scramble_key_xstrs) + + sizeof(char *) * ix; + + object_class_property_add(OBJECT_CLASS(odc), prop->name, + prop->info->name, prop->info->get, + prop->info->set, prop->info->release, prop); + } +} + static void ot_otp_dj_configure_inv_default_parts(OtOTPDjState *s) { for (unsigned ix = 0; ix < ARRAY_SIZE(OtOTPPartDescs); ix++) { @@ -3891,6 +3958,7 @@ static void ot_otp_dj_realize(DeviceState *dev, Error **errp) ot_otp_dj_configure_scrmbl_key(s); ot_otp_dj_configure_digest(s); ot_otp_dj_configure_sram(s); + ot_otp_dj_configure_part_scramble_keys(s); ot_otp_dj_configure_inv_default_parts(s); } @@ -3998,6 +4066,7 @@ static void ot_otp_dj_class_init(ObjectClass *klass, void *data) oc->get_keymgr_secret = &ot_otp_dj_get_keymgr_secret; oc->program_req = &ot_otp_dj_program_req; + ot_otp_dj_class_add_scramble_key_props(oc); ot_otp_dj_class_add_inv_def_props(oc); } From bdbc72c980addc0ec61c4434ec4335ac68af2355 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 22 Oct 2025 20:44:56 +0100 Subject: [PATCH 3/6] [ot] scripts/opentitan: cfggen.py: Load OTP scrambling keys from SV Parse the hard-coded OTP scrambling key constants out of the relevant SV partition SV files into the generated OT configuration files, so that they can be used in the OTP. Signed-off-by: Alex Jones --- python/qemu/ot/otp/const.py | 25 ++++++++++++++++++++++++- scripts/opentitan/cfggen.py | 4 ++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/python/qemu/ot/otp/const.py b/python/qemu/ot/otp/const.py index 1cb52163d2e6a..9117171c557a8 100644 --- a/python/qemu/ot/otp/const.py +++ b/python/qemu/ot/otp/const.py @@ -203,9 +203,16 @@ def get_digests(self) -> list[str]: except KeyError as exc: raise ValueError("No 'digest' enum found") from exc + def get_scrambling_keys(self) -> list[str]: + """Return a list of parsed scrambling keys for secret partitions.""" + try: + return list(self._enums['key']) + except KeyError as exc: + raise ValueError("No 'key' enum found") from exc + def get_digest_pair(self, name: str, prefix: str) -> dict[str, str]: """Return a dict of digest pair. - :param name: one of the enumerated values, see #get_enums + :param name: one of the enumerated values, see #get_digests :param prefix: the prefix to add to each dict key. """ try: @@ -221,6 +228,22 @@ def get_digest_pair(self, name: str, prefix: str) -> dict[str, str]: odict[oname] = values[idx] return odict + def get_scrambling_key(self, name: str) -> str: + """Get the value of a scrambling key. + :param name: One of the enumerated values, see #get_scrambling_keys + """ + try: + idx = self._enums['key'][name] + except KeyError as exc: + raise ValueError(f'Unknown scrambling key {name}') from exc + try: + key_values = self._consts['key'] + except KeyError as exc: + raise ValueError('No scrambling key constants found') from exc + if len(key_values) <= idx: + raise ValueError(f'No such key {name} in the key array') from exc + return key_values[idx] + def get_partition_inv_defaults(self, partition: int) -> Optional[str]: """Return the invalid default values for a partition, if any. Partitions with only digest defaults are considered without default diff --git a/scripts/opentitan/cfggen.py b/scripts/opentitan/cfggen.py index c5a3f6350e0aa..bea7ca85d3e9b 100755 --- a/scripts/opentitan/cfggen.py +++ b/scripts/opentitan/cfggen.py @@ -313,6 +313,10 @@ def prepare(self) -> None: continue pair = self._otpconst.get_digest_pair(digest, prefix) otp_ctrl.update(pair) + for key in self._otpconst.get_scrambling_keys(): + key_value = self._otpconst.get_scrambling_key(key) + key = key.removesuffix("key") + "_scramble_key" + otp_ctrl[key] = key_value idx = 0 while True: try: From fef9f6ae38d81d3502b97bd50d6560950254bfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Marques?= Date: Wed, 22 Oct 2025 14:08:02 +0000 Subject: [PATCH 4/6] [ot] hw/opentitan: ot_otp_eg: perform partition descrambling in DAI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unscramble DAI reads from secret partitions, which are scrambled with a scrambling key (configured per-partition as a netlist constant) via a simple Present block cipher. Co-authored-by: Alex Jones Signed-off-by: Luís Marques Signed-off-by: Alex Jones --- hw/opentitan/ot_otp_eg.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hw/opentitan/ot_otp_eg.c b/hw/opentitan/ot_otp_eg.c index 12f16501bb5bd..31245ad3aaaa9 100644 --- a/hw/opentitan/ot_otp_eg.c +++ b/hw/opentitan/ot_otp_eg.c @@ -1717,6 +1717,7 @@ static void ot_otp_eg_dai_read(OtOTPEgState *s) bool is_digest = ot_otp_eg_is_part_digest_offset(partition, address); bool is_readable = ot_otp_eg_is_readable(s, partition); bool is_wide = ot_otp_eg_is_wide_granule(partition, address); + bool is_secret = OtOTPPartDescs[partition].secret; /* "in all partitions, the digest itself is ALWAYS readable." */ if (!is_digest && !is_readable) { @@ -1770,6 +1771,18 @@ static void ot_otp_eg_dai_read(OtOTPEgState *s) } } + if (is_secret) { + const uint8_t *scrambling_key = s->otp_scramble_keys[partition]; + g_assert(scrambling_key); + uint64_t data = ((uint64_t)data_hi << 32u) | data_lo; + OtPresentState *ps = ot_present_new(); + ot_present_init(ps, scrambling_key); + ot_present_decrypt(ps, data, &data); + ot_present_free(ps); + data_lo = data; + data_hi = (data >> 32u); + } + s->regs[R_DIRECT_ACCESS_RDATA_0] = data_lo; s->regs[R_DIRECT_ACCESS_RDATA_1] = data_hi; From 6accf86f2cf780cd8b3ebb8e00005f9ad202aee0 Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 22 Oct 2025 20:41:29 +0100 Subject: [PATCH 5/6] [ot] hw/opentitan: ot_otp_dj: perform partition descrambling in DAI Unscramble DAI reads from secret partitions, which are scrambled with a scrambling key (configured per-partition as a netlist constant) via a simple Present block cipher. Signed-off-by: Alex Jones --- hw/opentitan/ot_otp_dj.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hw/opentitan/ot_otp_dj.c b/hw/opentitan/ot_otp_dj.c index 368e18eb2a201..24945f71f893c 100644 --- a/hw/opentitan/ot_otp_dj.c +++ b/hw/opentitan/ot_otp_dj.c @@ -1889,6 +1889,7 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) bool is_zer = ot_otp_dj_is_part_zer_offset(partition, address); bool is_readable = ot_otp_dj_is_readable(s, partition); bool is_wide = ot_otp_dj_is_wide_granule(partition, address); + bool is_secret = OtOTPPartDescs[partition].secret; /* "in all partitions, the digest itself is ALWAYS readable." */ if (!is_digest && !is_zer && !is_readable) { @@ -1945,6 +1946,18 @@ static void ot_otp_dj_dai_read(OtOTPDjState *s) } } + if (is_secret) { + const uint8_t *scrambling_key = s->otp_scramble_keys[partition]; + g_assert(scrambling_key); + uint64_t data = ((uint64_t)data_hi << 32u) | data_lo; + OtPresentState *ps = ot_present_new(); + ot_present_init(ps, scrambling_key); + ot_present_decrypt(ps, data, &data); + ot_present_free(ps); + data_lo = data; + data_hi = (data >> 32u); + } + s->regs[R_DIRECT_ACCESS_RDATA_0] = data_lo; s->regs[R_DIRECT_ACCESS_RDATA_1] = data_hi; From c70a4cd61e101ac404dda8a3e46f60d771626f5d Mon Sep 17 00:00:00 2001 From: Alex Jones Date: Wed, 22 Oct 2025 20:55:07 +0100 Subject: [PATCH 6/6] [ot] docs/config: opentitan: Regenerate default config files These files need to include the new OTP scrambling keys, so regenerate them from the latest commits on the `master` branch (for Darjeeling) and the `earlgrey_1.0.0` branch (for Earlgrey). Signed-off-by: Alex Jones --- docs/config/opentitan/darjeeling.cfg | 6 +++++- docs/config/opentitan/earlgrey.cfg | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/config/opentitan/darjeeling.cfg b/docs/config/opentitan/darjeeling.cfg index 57b71305dc923..0c27f85672b8b 100644 --- a/docs/config/opentitan/darjeeling.cfg +++ b/docs/config/opentitan/darjeeling.cfg @@ -1,5 +1,5 @@ # Generated from OpenTitan 'master' branch -# commit: f5703e1660 +# commit: fc2d73b432 [ot_device "ot-rom_ctrl.rom0"] key = "30ae84156d37cc68063276f9e85faee1" @@ -20,6 +20,10 @@ inv_default_part_20 = "9c47222c695c123916e90f1bdde834c31d3eef689e998822eddeb20732f666faba37deb973d827e00000000000000000" inv_default_part_21 = "6149b9ff4f5979607aead63a44f896431df745a52c5af5fdf86d2ce9fa1041c43f145a8bf5be7640d7aaf2481067180fd08f694a5f790581d728bd369d03f8087a60a7f8ed956442c9cff0f99e594c7307f376e7b2b2ff8c" scrmbl_key = "d6780626a12b5904a9b37439f8177d19d04d3fad040ed02909a9ea4b5429b9e6" + secret0_scramble_key = "688a9a20b68e0d35660e593f560f6866" + secret1_scramble_key = "a1ad90a50942397740ab78c5737a2379" + secret2_scramble_key = "c2ede5b25ec5514b680ccaab361c3f85" + secret3_scramble_key = "8e1c5cccc121a2c95d21294d190ab75c" sram_const = "4dcba329ff2f7d4b8a3acdb325087fab" sram_iv = "9bd623e40aec9b61" diff --git a/docs/config/opentitan/earlgrey.cfg b/docs/config/opentitan/earlgrey.cfg index 1680449499423..2890b9edeb74e 100644 --- a/docs/config/opentitan/earlgrey.cfg +++ b/docs/config/opentitan/earlgrey.cfg @@ -1,5 +1,5 @@ # Generated from OpenTitan 'earlgrey_1.0.0' branch -# commit: 1528bbce20 +# commit: 4e0ccfc0bd [ot_device "ot-rom_ctrl"] key = "663c291739ff0e7d644758fee1c58564" @@ -19,6 +19,9 @@ inv_default_part_9 = "8a1cd4f5518b4d2a1978b594ed3dcd94671685f41086d5d3f6f8a097aad0585e4d857adfdebd0d2c0ece8ed011c5bad01988d871b8d25f316e627ccc51fc79029f45333ca4988068edfed1b3f0968cd628a94cbb02adbb8c" inv_default_part_10 = "be6f2b4a67801b852a4529345af1cdbf37e97ab260e717e8e3c3363a666c130154d936fd1163e401fadd9f8c0ee9d1a056a2719fc2c345a4873c594f465e723e64ba4e17c79665cccb7943e751f0059633fbb917e41db693" scrmbl_key = "55d70063277642b5309a163990b966cd494444c3bcdf8087b7facd65e9654cd8" + secret0_scramble_key = "3ba121c5e097ddeb7768b4c666e9c3da" + secret1_scramble_key = "effa6d736c5eff49ae7b70f9c46e5a62" + secret2_scramble_key = "85a9e830bc059ba9286d6e2856a05cc3" sram_const = "4a22d4b78fe0266fbee3958332f2939b" sram_iv = "f98c48b1f9377284"