From 861df21ab94b548c968ace59fed1ee1f92c37cab Mon Sep 17 00:00:00 2001 From: Georgios Vasilakis Date: Mon, 6 Oct 2025 17:00:04 +0200 Subject: [PATCH 1/2] sysbuild: nrf54l: Add protected ram invalidation logic This adds the logic that is needed for nRF54L devices in order to provision the protected ram invalidation slots using nrfutil. This automatically generates a json file with the appropriate PSA attributes which is provisioned every time that the device is flashed. Until now this provisioning happens in the application which uses non-volatile and volatile memory. With this solution the application doesn't need to do that manually anymore. Signed-off-by: Georgios Vasilakis --- .../sysbuild/nrf54l_prot_ram_inv_slots.cmake | 29 ++++++++ scripts/west_commands/ncs_provision.py | 74 ++++++++++++++----- sysbuild/CMakeLists.txt | 4 + sysbuild/Kconfig.nrf54l_kmu | 8 ++ sysbuild/Kconfig.sysbuild | 1 + 5 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 cmake/sysbuild/nrf54l_prot_ram_inv_slots.cmake create mode 100644 sysbuild/Kconfig.nrf54l_kmu diff --git a/cmake/sysbuild/nrf54l_prot_ram_inv_slots.cmake b/cmake/sysbuild/nrf54l_prot_ram_inv_slots.cmake new file mode 100644 index 000000000000..3a979b106dae --- /dev/null +++ b/cmake/sysbuild/nrf54l_prot_ram_inv_slots.cmake @@ -0,0 +1,29 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +# This script defines a CMake target 'generate_prot_ram_inv_slots_json' to create prot_ram_inv_slots.json +# using 'west ncs-provision upload --dry-run'. + +# --- Construct the list of commands and dependencies --- +set(kmu_json_commands "") +set(json_filename "prot_ram_inv_slots.json") + +list(APPEND kmu_json_commands + COMMAND ${Python3_EXECUTABLE} -m west ncs-provision upload + --keyname PROT_RAM_INV_SLOTS + --build-dir ${CMAKE_BINARY_DIR} + --dry-run +) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/${json_filename} + ${kmu_json_commands} # Expands to one or more COMMAND clauses + COMMENT "Generating/Updating KMU protected ram invalidation slots JSON (${CMAKE_BINARY_DIR}/${json_filename})" + VERBATIM +) + +# --- Add custom target to trigger the generation --- +add_custom_target( + generate_prot_ram_inv_slot_json ALL + DEPENDS ${CMAKE_BINARY_DIR}/${json_filename} +) diff --git a/scripts/west_commands/ncs_provision.py b/scripts/west_commands/ncs_provision.py index a9a19562e09e..101300c08297 100644 --- a/scripts/west_commands/ncs_provision.py +++ b/scripts/west_commands/ncs_provision.py @@ -22,11 +22,13 @@ "UROT_PUBKEY": [226, 228, 230], "BL_PUBKEY": [242, 244, 246], "APP_PUBKEY": [202, 204, 206], + "PROT_RAM_INV_SLOTS": [248], } POLICIES = ["revokable", "lock", "lock-last"] NRF54L15_KEY_POLICIES: dict[str, str] = { "revokable": psa_attr_generator.PsaKeyPersistence.PERSISTENCE_REVOKABLE, "lock": psa_attr_generator.PsaKeyPersistence.PERSISTENCE_READ_ONLY, + "rotatable": psa_attr_generator.PsaKeyPersistence.PERSISTENCE_DEFAULT, } @@ -35,6 +37,7 @@ class SlotParams: id: int keyfile: str lifetime: psa_attr_generator.PsaKeyPersistence + keytype: psa_attr_generator.PsaKeyType class NrfutilWrapper: @@ -65,26 +68,50 @@ def run_command(self): def _make_json_file(self) -> str: """Generate PSA key attribute file, see generate_psa_key_attributes.py for details""" - output_file = ( - Path(self.output_dir).joinpath("keyfile.json").resolve().expanduser() - ) for slot in self.slots: - attr = psa_attr_generator.PlatformKeyAttributes( - key_type=psa_attr_generator.PsaKeyType.ECC_PUBLIC_KEY_TWISTED_EDWARDS, - identifier=slot.id, - location=psa_attr_generator.PsaKeyLocation.LOCATION_CRACEN_KMU, - persistence=slot.lifetime, - key_usage=psa_attr_generator.PsaKeyUsage.VERIFY, - algorithm=psa_attr_generator.PsaAlgorithm.EDDSA_PURE, - key_bits=255, - cracen_usage=psa_attr_generator.PsaCracenUsageScheme.RAW, - ) + if slot.keytype == psa_attr_generator.PsaKeyType.AES: + attr = psa_attr_generator.PlatformKeyAttributes( + key_type=psa_attr_generator.PsaKeyType.AES, + identifier=slot.id, + location=psa_attr_generator.PsaKeyLocation.LOCATION_CRACEN_KMU, + persistence=slot.lifetime, + key_usage=psa_attr_generator.PsaKeyUsage.ENCRYPT, + algorithm=psa_attr_generator.PsaAlgorithm.CTR, + key_bits=256, + cracen_usage=psa_attr_generator.PsaCracenUsageScheme.PROTECTED, + ) + + output_file = ( + Path(self.output_dir).joinpath("prot_ram_inv_slots.json").resolve().expanduser() + ) - with open(slot.keyfile, "rb") as key_file: psa_attr_generator.generate_attr_file( - attributes=attr, key_file=output_file, key_from_file=key_file + attributes=attr, key_file=output_file, trng_key=True + ) + elif slot.keytype == psa_attr_generator.PsaKeyType.ECC_PUBLIC_KEY_TWISTED_EDWARDS: + attr = psa_attr_generator.PlatformKeyAttributes( + key_type=psa_attr_generator.PsaKeyType.ECC_PUBLIC_KEY_TWISTED_EDWARDS, + identifier=slot.id, + location=psa_attr_generator.PsaKeyLocation.LOCATION_CRACEN_KMU, + persistence=slot.lifetime, + key_usage=psa_attr_generator.PsaKeyUsage.VERIFY, + algorithm=psa_attr_generator.PsaAlgorithm.EDDSA_PURE, + key_bits=255, + cracen_usage=psa_attr_generator.PsaCracenUsageScheme.RAW, + ) + + output_file = ( + Path(self.output_dir).joinpath("keyfile.json").resolve().expanduser() ) + + with open(slot.keyfile, "rb") as key_file: + psa_attr_generator.generate_attr_file( + attributes=attr, key_file=output_file, key_from_file=key_file + ) + else: + sys.exit(f"Unsupported key type: {slot.keytype}") + return str(output_file) def _build_command(self) -> list[str]: @@ -129,7 +156,7 @@ def do_add_parser(self, parser_adder): """), formatter_class=argparse.RawDescriptionHelpFormatter, ) - group = upload_parser.add_mutually_exclusive_group(required=True) + group = upload_parser.add_mutually_exclusive_group() group.add_argument( "-i", "--input", metavar="PATH", help="Upload keys from YAML file" ) @@ -187,8 +214,16 @@ def do_run(self, args, unknown_args): self._upload_keys(args) def _upload_keys(self, args: argparse.Namespace) -> None: + if args.keys is None and args.input is None and args.keyname != "PROT_RAM_INV_SLOTS": + sys.exit("error: one of the arguments -i/--input -k/--key is required") + slots: list[SlotParams] = [] - if args.input: + data: list[dict[str, Any]] = [] + + if args.keyname == "PROT_RAM_INV_SLOTS": + for id in KEY_SLOTS["PROT_RAM_INV_SLOTS"]: + slots.append(SlotParams(id=id, keyfile=None, lifetime=NRF54L15_KEY_POLICIES["rotatable"], keytype=psa_attr_generator.PsaKeyType.AES)) + elif args.input: data = self._read_keys_params_from_file(args.input) else: data = self._read_keys_params_from_args(args) @@ -221,6 +256,7 @@ def _read_keys_params_from_file(self, filename: str) -> list[dict[str, Any]]: def _generate_slots(self, keyname: str, keys: str, policy: str) -> list[SlotParams]: """Return list of SlotParams for given keys.""" + if len(keys) > len(KEY_SLOTS[keyname]): sys.exit( "Error: requested upload of more keys than there are designated slots." @@ -234,8 +270,10 @@ def _generate_slots(self, keyname: str, keys: str, policy: str) -> list[SlotPara key_policy = NRF54L15_KEY_POLICIES["revokable"] else: key_policy = NRF54L15_KEY_POLICIES[policy] + + key_type = psa_attr_generator.PsaKeyType.ECC_PUBLIC_KEY_TWISTED_EDWARDS slot_id = KEY_SLOTS[keyname][slot_idx] - slot = SlotParams(id=slot_id, keyfile=keyfile, lifetime=key_policy) + slot = SlotParams(id=slot_id, keyfile=keyfile, lifetime=key_policy, keytype=key_type) slots.append(slot) return slots diff --git a/sysbuild/CMakeLists.txt b/sysbuild/CMakeLists.txt index e7a04d18dbbb..3f9f98e0a930 100644 --- a/sysbuild/CMakeLists.txt +++ b/sysbuild/CMakeLists.txt @@ -752,6 +752,10 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_post_cmake) include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/generate_default_keyfile.cmake) endif() + if(SB_CONFIG_NRF54L_INVALIDATE_PROTECTED_RAM_SLOTS) + include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/nrf54l_prot_ram_inv_slots.cmake) + endif() + if(SB_CONFIG_MATTER_OTA) include(${ZEPHYR_CONNECTEDHOMEIP_MODULE_DIR}/config/zephyr/ota-image_sysbuild.cmake) if(SB_CONFIG_DFU_MULTI_IMAGE_PACKAGE_BUILD) diff --git a/sysbuild/Kconfig.nrf54l_kmu b/sysbuild/Kconfig.nrf54l_kmu new file mode 100644 index 000000000000..afbd20d76c6a --- /dev/null +++ b/sysbuild/Kconfig.nrf54l_kmu @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +config NRF54L_INVALIDATE_PROTECTED_RAM_SLOTS + bool + default y + depends on SOC_SERIES_NRF54LX diff --git a/sysbuild/Kconfig.sysbuild b/sysbuild/Kconfig.sysbuild index 2722cae69e05..57b80771ea3f 100644 --- a/sysbuild/Kconfig.sysbuild +++ b/sysbuild/Kconfig.sysbuild @@ -85,3 +85,4 @@ rsource "Kconfig.lwm2m_carrier" rsource "Kconfig.cracen" rsource "Kconfig.tfm" rsource "Kconfig.firmware_loader" +rsource "Kconfig.nrf54l_kmu" From 47c16f38ca094523f94870b05421db72b0504875 Mon Sep 17 00:00:00 2001 From: Georgios Vasilakis Date: Mon, 13 Oct 2025 17:28:59 +0200 Subject: [PATCH 2/2] manifest: Update Zephyr with protected ram invalidation logic Brings Zephyr west command which checks for the protected ram invalidation logic and flashes it if it exists. Signed-off-by: Georgios Vasilakis --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index a82485063980..145dffe0e2fb 100644 --- a/west.yml +++ b/west.yml @@ -65,7 +65,7 @@ manifest: # https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/modules.html - name: zephyr repo-path: sdk-zephyr - revision: 29aa21b8ac8e738fa6854e2127833c0e1613e101 + revision: pull/3394/head import: # In addition to the zephyr repository itself, NCS also # imports the contents of zephyr/west.yml at the above