Last edited 2 weeks ago

How to manage keys and secrets in TF-M

Applicable for STM32MP21x lines, STM32MP23x lines, STM32MP25x lines

Trusted domain applicability
This article is only applicable to M33-TD flavor of STM32MP2 series

1. Article purpose[edit | edit source]

This article describes how keys and secrets are managed in STMicroelectronics implementation of TF-M. It only applies to STM32MP2 series with the M33-TD flavor . The secrets presented in this page are the ones stored in OTP fuses.

2. Implementation[edit | edit source]

2.1. Required keys[edit | edit source]

TF-M requests several keys to be provisioned, see Platform Provisioning page. On OpenSTLinux, the addresses are defined in the OTP mapping. For the provisioning of those keys, refer to How_to_update_OTP page.

2.2. Builtin keys framework[edit | edit source]

In TF-M code, the keys are managed with the TF-M builtin keys framework. TF-M manages the OTP list through an enum tfm_otp_element_id_t. This list has been updated for STM32MP2 series in the file platform_otp_ids.h .

2.3. Device tree mapping[edit | edit source]

The OTP definition (size and address) that corresponds to the defined mapping is done in the device tree files, in SoC or board files. For example, in stm32mp251.dtsi for STM32MP25x lines , in the bsec node, this is the description of the RPN OTP:

		bsec: efuse@54000000 {
           ...

			rpn_otp: otp9@24 {
				#nvmem-cell-cells = <0>;
				reg = <0x24 0x4>;
			};

Then all the OTP fuses described in the bsec node are listed in an otp node, so that they can be used by the NVMEM framework:

	otp {
		compatible = "st,stm32mp2-otp";
		nvmem-cells = <&implementation_id>,
			      <&rpn_otp>,
			      <&bootrom_config_9>,
			      <&id_otp>,
			      <&package_otp>,
			      <&hconf1_otp>,
			      <&stm32certif>,
			      <&tfm_fw_pkh>,
			      <&ddr_fw_pkh>,
			      <&ca35_fw_pkh>,
			      <&fip_edmk>,
			      <&iak>;
		nvmem-cell-names = "implementation_id",
				   "rpn_otp",
				   "bootrom_config_9",
				   "id_otp",
				   "package_otp",
				   "hconf1_otp",
				   "stm32certif",
				   "tfm_fw_pkh",
				   "ddr_fw_pkh",
				   "ca35_fw_pkh",
				   "fip_edmk",
				   "iak";
		status = "okay";
	};

This follows the NVMEM bindings nvmem-consumer.yaml , similar to what is done in Linux.

2.4. NVMEM framework[edit | edit source]

To make the link with the awaited definitions of OTP by TF-M through enum tfm_otp_element_id_t and the description of OTP from device tree, an OTP/NVMEM framework is needed. Its entry point is the file otp.c . The function nvmem_dev_from_otp_id() makes the correspondance between the enum and the OTP DT node. This file also calls the BSEC functions that will read and write the OTP fuses.

2.5. Usage[edit | edit source]

There is a dedicated management of the IAK (requested for Initial Attestation service) and HUK. This is done in crypto_keys.c . The HUK is not directly used, it is derived thanks to SAES peripheral.

2.6. Tests[edit | edit source]

By default, the flag TFM_DUMMY_PROVISIONING is set to ON in platform/ext/target/stm/common/stm32mp2xx/config.cmake file, to allow testing the feature without burning the OTP fuse. You should then update tfm_psa_rot_provisioning_data_t structure and stm32_set_default_value() function to manage the dummy version of your secret. Another flag named OVERRIDE_OTP can be used in addition for development purposes. When the OVERRIDE_OTP flag is also set to ON, the dummy values are used whatever is stored in the OTPs.

Warning
For production software those flags TFM_DUMMY_PROVISIONING and OVERRIDE_OTP must be set to OFF

3. How to manage new keys[edit | edit source]

This chapter describes how to add a new key or secret in OTP, and how to manage it in TF-M.
First, select in the OTP mapping where to place this secret. If this is a key or a secret, it should be in upper OTP region (after OTP word 256). If this is not a secret (an informative OTP, a public key hash...), this could be in the middle OTP region.

As an example, we create a secret key (my_secret) of 256 bit, starting at OTP word 268 and ending at word 275.

3.1. Device tree modification[edit | edit source]

In the board device tree file for TF-M secure, check the &bsec node exists. If not, create it and add the my_secret OTP description:

&bsec {
	...

	my_secret: otp268@430 {
		#nvmem-cell-cells = <0>;
		reg = <0x430 0x20>;
	};
};

Then, if it does not exist, create the otp node in the board device tree file. This should be a copy of the node from SoC device tree file (except compatible and status properties). And add my_secret to the list of OTP:

/ {
	...

	otp {
		nvmem-cells = <&implementation_id>,
			      <&rpn_otp>,
			      <&bootrom_config_9>,
			      <&id_otp>,
			      <&package_otp>,
			      <&hconf1_otp>,
			      <&stm32certif>,
			      <&tfm_fw_pkh>,
			      <&ddr_fw_pkh>,
			      <&ca35_fw_pkh>,
			      <&fip_edmk>,
			      <&my_secret>,
			      <&iak>;
		nvmem-cell-names = "implementation_id",
				   "rpn_otp",
				   "bootrom_config_9",
				   "id_otp",
				   "package_otp",
				   "hconf1_otp",
				   "stm32certif",
				   "tfm_fw_pkh",
				   "ddr_fw_pkh",
				   "ca35_fw_pkh",
				   "fip_edmk",
				   "my_secret",
				   "iak";
	};
};

3.2. Code modification[edit | edit source]

Several usage examples can be found in crypto_keys.c , attest_hal.c or bl2/banner.c

3.2.1. OTP access[edit | edit source]

First, a new entry is needed in enum tfm_otp_element_id_t in the file platform_otp_ids.h , for example PLAT_OTP_MY_SECRET. This can be placed anywhere in the enum, but must be placed before PLAT_OTP_ID_MAX.

Then update the function nvmem_dev_from_otp_id() in otp.c file adding this in the switch/case:

	case PLAT_OTP_MY_SECRET:
		dev = DT_INST_DEV_NVMEM(0, my_secret);
		break;

After that, it is possible to access the OTP content with the following functions:

	err =  tfm_plat_otp_get_size(PLAT_OTP_MY_SECRET, &otp_size);
	if (err != TFM_PLAT_ERR_SUCCESS)
		return err;

	err = tfm_plat_otp_read(PLAT_OTP_MY_SECRET, otp_size, buf);
	if(err != TFM_PLAT_ERR_SUCCESS)
		return err;

3.2.2. Key usage with PSA crypto API[edit | edit source]

It is possible to work with your keys through PSA crypto API[1] with TF-M Crypto Service. The handling of keys is described in TF-M builtin keys article.

For that, add a new key ID in the enum tfm_builtin_key_id_t in tfm_builtin_key_ids.h , for example TFM_BUILTIN_KEY_ID_MY_SECRET. Place it between TFM_BUILTIN_KEY_ID_PLAT_SPECIFIC_MIN and TFM_BUILTIN_KEY_ID_MAX:

	TFM_BUILTIN_KEY_ID_PLAT_SPECIFIC_MIN = 0x7FFF816Bu,
	TFM_BUILTIN_KEY_ID_MY_SECRET,
	TFM_BUILTIN_KEY_ID_MAX = 0x7FFF817Bu,

A new key slot is also needed in the file platform_builtin_key_loader_ids.h , in the enum psa_drv_slot_number_t, before TFM_BUILTIN_KEY_SLOT_MAX

	TFM_BUILTIN_KEY_SLOT_MY_SECRET,
	TFM_BUILTIN_KEY_SLOT_MAX,

Then, update crypto_keys.c file. Add the description of the key in static const tfm_plat_builtin_key_descriptor_t g_builtin_keys_desc[], and create a dedicated loader_key_func, for example tfm_plat_get_my_secret. Add the key policy in static const tfm_plat_builtin_key_policy_t g_builtin_keys_policy[]. Depending on key policy, creating a new static const tfm_plat_builtin_key_per_user_policy_t g_my_secret_per_user_policy[] may be needed.

The tfm_builtin_key_ids.h file should be shared with the PSA client application.

4. References[edit | edit source]