Skip to content
Draft
Show file tree
Hide file tree
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
21 changes: 8 additions & 13 deletions cmake/sysbuild/provision_hex.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
include(${CMAKE_CURRENT_LIST_DIR}/sign.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/debug_keys.cmake)

function(provision application prefix_name)
function(provision application prefix_name mcuboot_downgrade_protection)
ExternalProject_Get_Property(${application} BINARY_DIR)
import_kconfig(CONFIG_ ${BINARY_DIR}/zephyr/.config)

Expand Down Expand Up @@ -64,13 +64,9 @@ function(provision application prefix_name)
# Adjustment to be able to load into sysbuild
if(CONFIG_SOC_NRF5340_CPUNET OR "${domain}" STREQUAL "CPUNET")
set(partition_manager_target partition_manager_CPUNET)
set(s0_arg --s0-addr $<TARGET_PROPERTY:${partition_manager_target},PM_APP_ADDRESS>)
set(s1_arg)
set(cpunet_target y)
else()
set(partition_manager_target partition_manager)
set(s0_arg --s0-addr $<TARGET_PROPERTY:${partition_manager_target},PM_S0_ADDRESS>)
set(s1_arg --s1-addr $<TARGET_PROPERTY:${partition_manager_target},PM_S1_ADDRESS>)
set(cpunet_target n)
endif()

Expand All @@ -84,7 +80,7 @@ function(provision application prefix_name)
endif()
endif()

if(SB_CONFIG_MCUBOOT_HARDWARE_DOWNGRADE_PREVENTION)
if(mcuboot_downgrade_protection AND SB_CONFIG_MCUBOOT_HARDWARE_DOWNGRADE_PREVENTION)
set(mcuboot_counters_slots --mcuboot-counters-slots ${SB_CONFIG_MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_SLOTS})
endif()

Expand All @@ -99,8 +95,6 @@ function(provision application prefix_name)
COMMAND
${PYTHON_EXECUTABLE}
${ZEPHYR_NRF_MODULE_DIR}/scripts/bootloader/provision.py
${s0_arg}
${s1_arg}
--provision-addr $<TARGET_PROPERTY:${partition_manager_target},PM_PROVISION_ADDRESS>
${public_keys_file_arg}
--output ${PROVISION_HEX}
Expand All @@ -119,7 +113,8 @@ function(provision application prefix_name)
"Creating data to be provisioned to the Bootloader, storing to ${PROVISION_HEX_NAME}"
USES_TERMINAL
)
elseif(SB_CONFIG_MCUBOOT_HARDWARE_DOWNGRADE_PREVENTION)
elseif(mcuboot_downgrade_protection)
# elseif(SB_CONFIG_MCUBOOT_HARDWARE_DOWNGRADE_PREVENTION)
add_custom_command(
OUTPUT
${PROVISION_HEX}
Expand Down Expand Up @@ -179,12 +174,12 @@ if(NCS_SYSBUILD_PARTITION_MANAGER)
# Get the main app of the domain that secure boot should handle.
if(SB_CONFIG_SECURE_BOOT AND SB_CONFIG_SECURE_BOOT_APPCORE)
if(SB_CONFIG_BOOTLOADER_MCUBOOT)
provision("mcuboot" "app_")
provision("mcuboot" "app_" y)
else()
provision("${DEFAULT_IMAGE}" "app_")
provision("${DEFAULT_IMAGE}" "app_" n)
endif()
elseif(SB_CONFIG_MCUBOOT_HARDWARE_DOWNGRADE_PREVENTION)
provision("${DEFAULT_IMAGE}" "app_")
provision("${DEFAULT_IMAGE}" "app_" y)
endif()

if(SB_CONFIG_SECURE_BOOT_NETCORE)
Expand All @@ -195,6 +190,6 @@ if(NCS_SYSBUILD_PARTITION_MANAGER)
" but no image is selected for this domain.")
endif()

provision("${main_app}" "net_")
provision("${main_app}" "net_" n)
endif()
endif()
72 changes: 17 additions & 55 deletions include/bl_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,27 @@ typedef uint32_t lcs_reserved_t;

#define EHASHFF 113 /* A hash contains too many 0xFs. */
#define EREADLCS 114 /* LCS field of OTP is in an invalid state */
#define EINVALIDLCS 115 /* Invalid LCS*/
#define EINVALIDLCS 115 /* Invalid LCS */

/* We truncate the 32 byte sha256 down to 16 bytes before storing it */
#define SB_PUBLIC_KEY_HASH_LEN 16

/* Supported collection types. */
enum collection_type {
BL_COLLECTION_TYPE_MONOTONIC_COUNTERS = 1,
BL_COLLECTION_TYPE_VARIABLE_DATA = 0x9312,
};
/** Counter used by NSIB to check the firmware version. */
BL_MONOTONIC_COUNTERS_DESC_NSIB = 0,

/* Counter used by NSIB to check the firmware version */
#define BL_MONOTONIC_COUNTERS_DESC_NSIB 0x1
/**
* Counter used by MCUBOOT to check the firmware version. Suffixed with ID0 as we might
* support checking the version of multiple images in the future.
*/
BL_MONOTONIC_COUNTERS_DESC_MCUBOOT_ID0,

/* Counter used by MCUBOOT to check the firmware version. Suffixed
* with ID0 as we might support checking the version of multiple
* images in the future.
*/
#define BL_MONOTONIC_COUNTERS_DESC_MCUBOOT_ID0 0x2
/** Variable data #variable_data_type. */
BL_VARIABLE_DATA,
};

#define BL_COLLECTION_TYPE_VARIABLE_DATA 0x9312

/** Storage for the PRoT Security Lifecycle state, that consists of 4 states:
* - Device assembly and test
Expand All @@ -81,42 +83,11 @@ struct life_cycle_state_data {
lcs_data_t decommissioned;
};

/** This library implements monotonic counters where each time the counter
* is increased, a new slot is written.
* This way, the counter can be updated without erase. This is, among other things,
* necessary so the counter can be used in the OTP section of the UICR
* (available on e.g. nRF91 and nRF53).
*/
struct monotonic_counter {
/* Counter description. What the counter is used for. See
* BL_MONOTONIC_COUNTERS_DESC_x.
*/
uint16_t description;
/* Number of entries in 'counter_slots' list. */
uint16_t num_counter_slots;
counter_t counter_slots[];
};

/** Common part for all collections. */
struct collection {
uint16_t type;
uint16_t count;
};

/** The second data structure in the provision page. It has unknown length since
* 'counters' is repeated. Note that each entry in counters also has unknown
* length, and each entry can have different length from the others, so the
* entries beyond the first cannot be accessed through array indices.
*/
struct counter_collection {
struct collection collection; /* Type must be BL_COLLECTION_TYPE_MONOTONIC_COUNTERS */
struct monotonic_counter counters[];
};

/* Variable data types. */
enum variable_data_type {
BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE = 0x1
};

struct variable_data {
uint8_t type;
uint8_t length;
Expand All @@ -131,7 +102,8 @@ struct variable_data {
* cannot be accessed through array indices.
*/
struct variable_data_collection {
struct collection collection; /* Type must be BL_COLLECTION_TYPE_VARIABLE_DATA */
uint16_t magic;
uint16_t count;
struct variable_data variable_data[];
};

Expand All @@ -143,33 +115,23 @@ struct bl_storage_data {
/* NB: When placed in OTP, reads must be 4 bytes and 4 byte aligned */
struct life_cycle_state_data lcs;
uint8_t implementation_id[32];
uint32_t s0_address;
uint32_t s1_address;
uint32_t num_public_keys; /* Number of entries in 'key_data' list. */
struct {
uint32_t valid;
uint8_t hash[SB_PUBLIC_KEY_HASH_LEN];
} key_data[];

/* Monotonic counter collection:
* uint16_t type;
* uint16_t count;
* struct {
* uint16_t description;
* uint16_t num_counter_slots;
* counter_t counter_slots[];
* } counters[];
* counter_t counter_slots[counters][slots];
*/

/* Variable data collection:
* uint16_t type;
* uint16_t count;
* struct {
* uint8_t type;
* uint8_t length;
* uint8_t data[];
* } variable_data[];
* uint8_t padding[]; // Padding to align to 4 bytes
*/
};

Expand Down
9 changes: 9 additions & 0 deletions modules/mcuboot/Kconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
menu "MCUboot"

if MCUBOOT_HW_DOWNGRADE_PREVENTION

config MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_SLOTS
int "Number of available hardware counter slots"
default 240
range 2 300

endif # MCUBOOT_HW_DOWNGRADE_PREVENTION

if BOOTLOADER_MCUBOOT

menuconfig MCUBOOT_HARDWARE_DOWNGRADE_PREVENTION
Expand Down
19 changes: 19 additions & 0 deletions samples/bootloader/twister/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

set(APPLICATION_CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../")

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bootloader)

zephyr_library_sources(../src/main.c)

# Dummy config values due to building a proper project
zephyr_compile_definitions(CONFIG_PARTITION_MANAGER_ENABLED=y)
zephyr_compile_definitions(PM_S0_ADDRESS=0x10000)
zephyr_compile_definitions(PM_S1_ADDRESS=0x80000)
8 changes: 4 additions & 4 deletions samples/nrf5340/netboot/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ config B0N_SIZE
default 0x8580 if PM_PARTITION_SIZE_PROVISION=0x280
default 0x8800
help
Since the locking granularity is 2kB, ensure that B0N_SIZE is
set such that the size of the `b0n_container` is aligned to this
size. If the minimal config is enabled the binary will be around
5kB. Note: The provision partition is 0x280 bytes.
Since the locking granularity is 2kB, ensure that B0N_SIZE is
set such that the size of the `b0n_container` is aligned to this
size. If the minimal config is enabled the binary will be around
5kB. Note: The provision partition is 0x280 bytes.

endmenu

Expand Down
15 changes: 15 additions & 0 deletions samples/nrf5340/netboot/twister/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

set(APPLICATION_CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../")

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(network_core_bootloader)

zephyr_library_sources(../src/main.c)
zephyr_compile_definitions(CONFIG_PARTITION_MANAGER_ENABLED=y)
7 changes: 7 additions & 0 deletions samples/nrf5340/netboot/twister/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

rsource "../Kconfig"
33 changes: 9 additions & 24 deletions scripts/bootloader/provision.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ def add_hw_counters(provision_data, num_counter_slots_version, mcuboot_counters_
assert num_counter_slots_version % 2 == 0, "--num-counters-slots-version must be an even number"
assert mcuboot_counters_slots % 2 == 0, "--mcuboot-counters-slots must be an even number"

provision_data += struct.pack('<H', BL_COLLECTION_TYPE_MONOTONIC_COUNTERS)
provision_data += struct.pack('<H', num_counters) # Could be 0, 1, or 2
# provision_data += struct.pack('<H', BL_COLLECTION_TYPE_MONOTONIC_COUNTERS)
# provision_data += struct.pack('<H', num_counters) # Could be 0, 1, or 2

if num_counter_slots_version > 0:
provision_data += struct.pack('<H', BL_MONOTONIC_COUNTERS_DESC_NSIB)
provision_data += struct.pack('<H', num_counter_slots_version)
# provision_data += struct.pack('<H', BL_MONOTONIC_COUNTERS_DESC_NSIB)
# provision_data += struct.pack('<H', num_counter_slots_version)
provision_data += bytes(otp_write_width * num_counter_slots_version * [0xFF])

if mcuboot_counters_slots > 0:
provision_data += struct.pack('<H', BL_MONOTONIC_COUNTERS_DESC_MCUBOOT_ID0)
provision_data += struct.pack('<H', mcuboot_counters_slots)
# provision_data += struct.pack('<H', BL_MONOTONIC_COUNTERS_DESC_MCUBOOT_ID0)
# provision_data += struct.pack('<H', mcuboot_counters_slots)
provision_data += bytes(otp_write_width * mcuboot_counters_slots * [0xFF])

return provision_data
Expand All @@ -82,32 +82,27 @@ def generate_mcuboot_only_provision_hex_file(provision_address, output, max_size

num_bytes_in_lcs = lcs_state_sz
num_bytes_in_implementation_id = IMPLEMENTATION_ID_SIZE
num_bytes_in_s0_address = 4
num_bytes_in_s1_address = 4
num_bytes_in_num_public_keys = 4

num_bytes_in_bl_storage_data_struct = \
num_bytes_in_lcs + \
num_bytes_in_implementation_id + \
num_bytes_in_s0_address + \
num_bytes_in_s1_address + \
num_bytes_in_num_public_keys

provision_data = bytes(num_bytes_in_bl_storage_data_struct * [0])

provision_data = add_hw_counters(provision_data, 0, mcuboot_counters_slots, otp_write_width)
# provision_data = add_hw_counters(provision_data, 0, mcuboot_counters_slots, otp_write_width)

provision_data += variable_data

generate_provision_intel_hex_file(provision_data, provision_address, output, max_size)


def generate_provision_hex_file(s0_address, s1_address, hashes, provision_address, output, max_size,
def generate_provision_hex_file(hashes, provision_address, output, max_size,
num_counter_slots_version, mcuboot_counters_slots, otp_write_width,
variable_data):

provision_data = struct.pack('<III', s0_address, s1_address,
len(hashes))
provision_data = struct.pack('<I', len(hashes))

idx = 0
for mhash in hashes:
Expand All @@ -131,8 +126,6 @@ def parse_args():
description='Generate provisioning hex file.',
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False)
parser.add_argument('--s0-addr', type=lambda x: int(x, 0), required=False, help='Address of image slot s0')
parser.add_argument('--s1-addr', type=lambda x: int(x, 0), required=False, help='Address of image slot s1')
parser.add_argument('--provision-addr', type=lambda x: int(x, 0),
required=True, help='Address at which to place the provisioned data')
parser.add_argument('--public-key-files', required=False,
Expand Down Expand Up @@ -241,9 +234,6 @@ def main():
lcs_size = get_lcs_size(args.otp_write_width)
num_bytes_provisioned_elsewhere = lcs_size + IMPLEMENTATION_ID_SIZE

if not args.mcuboot_only and args.s0_addr is None:
raise RuntimeError("Either --mcuboot-only or --s0-addr must be specified")

variable_data = get_variable_data(
psa_certification_reference=args.psa_certificate_reference
)
Expand All @@ -260,9 +250,6 @@ def main():
)
return

s0_address = args.s0_addr
s1_address = args.s1_addr if args.s1_addr is not None else s0_address

# The LCS and implementation ID is stored in the OTP before the
# rest of the provisioning data so add it to the given base
# address
Expand All @@ -278,8 +265,6 @@ def main():
)

generate_provision_hex_file(
s0_address=s0_address,
s1_address=s1_address,
hashes=hashes,
provision_address=provision_address,
output=args.output,
Expand Down
7 changes: 3 additions & 4 deletions subsys/bootloader/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
menu "Bootloader"

config SECURE_BOOT
bool "Use Secure Bootloader"
depends on !IS_SECURE_BOOTLOADER
bool "Use Secure Bootloader" if !IS_SECURE_BOOTLOADER
# depends on !IS_SECURE_BOOTLOADER
default y if IS_SECURE_BOOTLOADER
select FW_INFO
select SW_VECTOR_RELAY if SOC_SERIES_NRF51X
help
Expand Down Expand Up @@ -50,8 +51,6 @@ config SB_NUM_VER_COUNTER_SLOTS
Regarding ranges: The actual maximum depends on the number of
provisioned public keys, since they share the space. The same is true if
other data is stored in the "OTP" region (on for example nRF91 and nRF53).
This configuration should not be used in code. Instead, the header before the
slots should be read at run-time.

endif # SECURE_BOOT

Expand Down
Loading