diff --git a/cmake/sysbuild/provision_hex.cmake b/cmake/sysbuild/provision_hex.cmake index e88b86d1e3dd..28f44f94c9a9 100644 --- a/cmake/sysbuild/provision_hex.cmake +++ b/cmake/sysbuild/provision_hex.cmake @@ -80,6 +80,18 @@ function(provision application prefix_name) set(mcuboot_counters_slots --mcuboot-counters-slots ${SB_CONFIG_MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_SLOTS}) endif() + if(SB_CONFIG_TFM_OTP_VERIFICATION_SERVICE_URL) + set(verification_service_url --verification-service-url ${SB_CONFIG_TFM_OTP_VERIFICATION_SERVICE_URL}) + endif() + + if(SB_CONFIG_TFM_OTP_PSA_CERTIFICATE_REFERENCE AND SB_CONFIG_TFM_PSA_CERTIFICATE_REFERENCE_VALUE) + set(psa_certificate_reference --psa-certificate-reference ${SB_CONFIG_TFM_PSA_CERTIFICATE_REFERENCE_VALUE}) + endif() + + if(SB_CONFIG_TFM_OTP_PROFILE_DEFINITION) + set(profile_definition --profile-definition ${SB_CONFIG_TFM_OTP_PROFILE_DEFINITION}) + endif() + if(CONFIG_SECURE_BOOT) add_custom_command( OUTPUT @@ -96,6 +108,9 @@ function(provision application prefix_name) ${monotonic_counter_arg} ${no_verify_hashes_arg} ${mcuboot_counters_slots} + ${verification_service_url} + ${psa_certificate_reference} + ${profile_definition} DEPENDS ${PROVISION_KEY_DEPENDS} ${PROVISION_DEPENDS} @@ -118,6 +133,9 @@ function(provision application prefix_name) --max-size ${CONFIG_PM_PARTITION_SIZE_PROVISION} ${mcuboot_counters_num} ${mcuboot_counters_slots} + ${verification_service_url} + ${psa_certificate_reference} + ${profile_definition} DEPENDS ${PROVISION_KEY_DEPENDS} WORKING_DIRECTORY diff --git a/include/bl_storage.h b/include/bl_storage.h index de136388821f..d732011a55af 100644 --- a/include/bl_storage.h +++ b/include/bl_storage.h @@ -26,6 +26,12 @@ extern "C" { /* 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 */ #define BL_MONOTONIC_COUNTERS_DESC_NSIB 0x1 @@ -35,6 +41,56 @@ extern "C" { */ #define BL_MONOTONIC_COUNTERS_DESC_MCUBOOT_ID0 0x2 +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; + uint16_t counter_slots[1]; +}; + +/** 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 via array indices. + */ +struct counter_collection { + struct collection collection; /* Type must be BL_COLLECTION_TYPE_MONOTONIC_COUNTERS */ + struct monotonic_counter counters[1]; +}; + +/* Variable data types. */ +enum variable_data_type { + BL_VARIABLE_DATA_TYPE_VERIFICATION_SERVICE_URL = 0x1, + BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE = 0x2, + BL_VARIABLE_DATA_TYPE_PROFILE_DEFINITION = 0x3, +}; +struct variable_data { + uint8_t type; + uint8_t length; + uint8_t data[1]; +}; + +/* The third data structure in the provision page. It has unknown length since + * 'variable_data' is repeated. The collection starts immediately after the + * counter collection. As the counter collection has unknown length, the start + * of the variable data collection must be calculated dynamically. Similarly, + * the entries in the variable data collection have unknown length, so they + * cannot be accessed via array indices. + */ +struct variable_data_collection { + struct collection collection; /* Type must be BL_COLLECTION_TYPE_VARIABLE_DATA */ + struct variable_data variable_data[1]; +}; + /** Storage for the PRoT Security Lifecycle state, that consists of 4 states: * - Device assembly and test * - PRoT Provisioning @@ -59,7 +115,7 @@ struct life_cycle_state_data { /** The first data structure in the bootloader storage. It has unknown length * since 'key_data' is repeated. This data structure is immediately followed by - * struct counter_collection. + * struct counter_collection, which is then followed by struct variable_data_collection. */ struct bl_storage_data { /* NB: When placed in OTP, reads must be 4 bytes and 4 byte aligned */ @@ -72,6 +128,27 @@ struct bl_storage_data { uint32_t valid; uint8_t hash[SB_PUBLIC_KEY_HASH_LEN]; } key_data[1]; + + /* Monotonic counters: + * uint16_t type; + * uint16_t count; + * struct { + * uint16_t description; + * uint16_t num_counter_slots; + * uint16_t counter_slots[1]; + * } counters[1]; + */ + + /* Variable data: + * uint16_t type; + * uint16_t count; + * struct { + * uint8_t type; + * uint8_t length; + * uint8_t data[1]; + * } variable_data[1]; + * uint8_t padding[1]; // Padding to align to 4 bytes + */ }; #define BL_STORAGE ((const volatile struct bl_storage_data *)(PM_PROVISION_ADDRESS)) @@ -97,13 +174,6 @@ uint32_t s0_address_read(void); */ uint32_t s1_address_read(void); -/** - * @brief Function for reading number of public key data slots. - * - * @return Number of public key data slots. - */ -uint32_t num_public_keys_read(void); - /** * @brief Function for reading number of public key data slots. * @@ -176,6 +246,38 @@ int get_monotonic_counter(uint16_t counter_desc, uint16_t *counter_value); */ int set_monotonic_counter(uint16_t counter_desc, uint16_t new_counter); +/** + * @brief Function for reading number of public key data slots. + * + * @return Number of public key data slots. + */ +NRFX_STATIC_INLINE uint32_t num_public_keys_read(void) +{ + return nrfx_nvmc_uicr_word_read(&BL_STORAGE->num_public_keys); +} + +/** + * @brief Get the first collection after the public keys. + * + * @return Pointer to the first collection. + */ +NRFX_STATIC_INLINE const struct collection *get_first_collection(void) +{ + return (const struct collection *)&BL_STORAGE->key_data[num_public_keys_read()]; +} + +/** + * @brief Get the collection type. + * + * @param[in] collection Pointer to the collection. + * + * @return Collection type. + */ +NRFX_STATIC_INLINE uint16_t get_collection_type(const struct collection *collection) +{ + return nrfx_nvmc_otp_halfword_read((uint32_t)&collection->type); +} + /** * @brief The PSA life cycle states a device can be in. * @@ -194,21 +296,41 @@ enum lcs { * time. Writes to @p dst are done a byte at a time. * * @param[out] dst destination buffer. - * @param[in] src source buffer in OTP. Must be 4-byte-aligned. - * @param[in] size number of *bytes* in src to copy into dst. Must be divisible by 4. + * @param[in] src source buffer in OTP. + * @param[in] size number of *bytes* in src to copy into dst. */ -NRFX_STATIC_INLINE void otp_copy32(uint8_t *restrict dst, uint32_t volatile * restrict src, - size_t size) +NRFX_STATIC_INLINE void otp_copy(uint8_t *restrict dst, uint8_t volatile * restrict src, + size_t size) { - for (int i = 0; i < size / 4; i++) { - /* OTP is in UICR */ - uint32_t val = nrfx_nvmc_uicr_word_read(src + i); + size_t copied = 0; /* Bytes copied. */ + uint8_t src_offset = (uint32_t)src % 4; /* Align the source address to 32-bits. */ + + uint32_t val; /* 32-bit value read from the OTP. */ + uint8_t copy_size; /* Number of bytes to copy. */ + + while (copied < size) { - for (int j = 0; j < 4; j++) { - dst[i * 4 + j] = (val >> 8 * j) & 0xFF; + /* Read 32-bits. */ + val = nrfx_nvmc_uicr_word_read((uint32_t *)(src + copied - src_offset)); + + /* Calculate the size to copy. */ + copy_size = sizeof(val) - src_offset; + if (size - copied < copy_size) { + copy_size = size - copied; + } + + /* Copy the data one byte at a time. */ + for (int i = 0; i < copy_size; i++) { + *dst++ = (val >> (8 * (i + src_offset))) & 0xFF; } + + /* Source address is aligned to 32-bits after the first iteration. */ + src_offset = 0; + + copied += copy_size; } } + /** * Read the implementation id from OTP and copy it into a given buffer. * @@ -220,7 +342,7 @@ NRFX_STATIC_INLINE void read_implementation_id_from_otp(uint8_t *buf) return; } - otp_copy32(buf, (uint32_t *)&BL_STORAGE->implementation_id, + otp_copy(buf, (uint8_t *)&BL_STORAGE->implementation_id, BL_STORAGE_IMPLEMENTATION_ID_SIZE); } @@ -336,6 +458,124 @@ NRFX_STATIC_INLINE int update_life_cycle_state(enum lcs next_lcs) return -EINVALIDLCS; } +#if CONFIG_BUILD_WITH_TFM + +static uint32_t get_monotonic_counter_collection_size(const struct counter_collection *collection) +{ + /* Add only the constant part of the counter_collection. */ + uint32_t size = sizeof(struct collection); + uint16_t num_counters = + nrfx_nvmc_otp_halfword_read((uint32_t)&collection->collection.count); + const struct monotonic_counter *counter = collection->counters; + + for (int i = 0; i < num_counters; i++) { + /* Add only the constant part of the monotonic_counter. */ + size += sizeof(struct monotonic_counter) - sizeof(uint16_t); + + uint16_t num_slots = + nrfx_nvmc_otp_halfword_read((uint32_t)&counter->num_counter_slots); + size += (num_slots * sizeof(uint16_t)); + + /* Move to the next monotonic counter. */ + counter = (const struct monotonic_counter *)&counter->counter_slots[num_slots]; + } + + return size; +} + +static const struct variable_data_collection *get_variable_data_collection(void) +{ + /* We expect to find variable data after the monotonic counters. */ + const struct collection *collection = get_first_collection(); + + if (get_collection_type(collection) == BL_COLLECTION_TYPE_MONOTONIC_COUNTERS) { + + /* Advance to next collection. */ + collection = (const struct collection *)((uint8_t *)collection + + get_monotonic_counter_collection_size( + (const struct counter_collection *) + collection)); + + /* Verify that we found variable collection. */ + return get_collection_type(collection) == BL_COLLECTION_TYPE_VARIABLE_DATA + ? (const struct variable_data_collection *)collection + : NULL; + + } else if (get_collection_type(collection) == BL_COLLECTION_TYPE_VARIABLE_DATA) { + /* Bit of a special scenario where monotonic counters are not present. */ + return (const struct variable_data_collection *)collection; + } + + return NULL; +} + +/** + * @brief Read variable data from OTP. + * + * Variable data starts with variable data collection ID, followed by amount of variable data + * entries and the variable data entries themselves. + * [Collection ID][Variable count][Type][Variable data length][Variable data][Type]... + * 2 bytes 2 bytes 1 byte 1 byte 0-255 bytes + * + * @note If data is not found, function does not fail. Instead, 0 length is returned. + * + * @param[in] data_type Type of the variable data to read. + * @param[out] buf Buffer to store the variable data. + * @param[in,out] buf_len On input, the size of the buffer. On output, the length of the data. + * + * @retval 0 Variable data read successfully, or not found. + * @retval -EINVAL No buffer provided. + * @retval -ENOMEM Buffer too small. + */ +int read_variable_data(enum variable_data_type data_type, uint8_t *buf, uint32_t *buf_len) +{ + if (buf == NULL) { + return -EINVAL; + } + + const struct variable_data_collection *collection = get_variable_data_collection(); + + if (collection == NULL) { + /* Variable data collection does not necessarily exist. Exit gracefully. */ + *buf_len = 0; + return 0; + } + + const struct variable_data *variable_data = collection->variable_data; + const uint16_t count = nrfx_nvmc_otp_halfword_read((uint32_t)&collection->collection.count); + uint8_t type; + uint8_t length; + + /* Loop through all variable data entries. */ + for (int i = 0; i < count; i++) { + + /* Read the type and length of the variable data. */ + otp_copy((uint8_t *)&type, (uint8_t *)&variable_data->type, sizeof(type)); + otp_copy((uint8_t *)&length, (uint8_t *)&variable_data->length, sizeof(length)); + + if (type == data_type) { + /* Found the requested variable data. */ + if (*buf_len < length) { + return -ENOMEM; + } + + /* Copy the variable data into the buffer. */ + otp_copy(buf, (uint8_t *)&variable_data->data, length); + *buf_len = length; + return 0; + } + /* Move to the next variable data entry. */ + variable_data = + (const struct variable_data *)((uint8_t *)&variable_data->data + length); + } + + /* No matching variable data. */ + *buf_len = 0; + + return 0; +} +#endif + /** @} */ #ifdef __cplusplus diff --git a/modules/trusted-firmware-m/CMakeLists.txt b/modules/trusted-firmware-m/CMakeLists.txt index 782dff48c90d..36f731dfb41c 100644 --- a/modules/trusted-firmware-m/CMakeLists.txt +++ b/modules/trusted-firmware-m/CMakeLists.txt @@ -152,6 +152,9 @@ set_property(TARGET zephyr_property_target APPEND PROPERTY TFM_CMAKE_OPTIONS -DCRYPTO_HW_ACCELERATOR=True ) +set_property(TARGET zephyr_property_target + APPEND PROPERTY TFM_CMAKE_OPTIONS -DPLATFORM_DEFAULT_NV_SEED=OFF + ) if (CONFIG_TFM_ALLOW_NON_SECURE_FAULT_HANDLING) set_property(TARGET zephyr_property_target @@ -170,7 +173,15 @@ if (CONFIG_TFM_PROFILE_TYPE_MINIMAL) APPEND PROPERTY TFM_CMAKE_OPTIONS -DPLATFORM_DEFAULT_ROTPK=OFF -DPLATFORM_DEFAULT_IAK=OFF - -DPLATFORM_DEFAULT_NV_SEED=OFF + -DPLATFORM_DEFAULT_OTP=OFF + -DPLATFORM_DEFAULT_OTP_WRITEABLE=OFF + -DPLATFORM_DEFAULT_NV_COUNTERS=OFF + ) +endif() + +if (CONFIG_TFM_PLATFORM_NV_COUNTER_MODULE_DISABLED) + set_property(TARGET zephyr_property_target + APPEND PROPERTY TFM_CMAKE_OPTIONS -DPLATFORM_DEFAULT_OTP=OFF -DPLATFORM_DEFAULT_OTP_WRITEABLE=OFF -DPLATFORM_DEFAULT_NV_COUNTERS=OFF @@ -276,7 +287,7 @@ set(CRYPTO_ASYM_ENCRYPT_MODULE_ENABLED ${CONFIG_TFM_CRYPTO_ASYM_ENCRYPT_MODU set(CRYPTO_KEY_DERIVATION_MODULE_ENABLED ${CONFIG_TFM_CRYPTO_KEY_DERIVATION_MODULE_ENABLED}) set(CRYPTO_PAKE_MODULE_ENABLED ${CONFIG_TFM_CRYPTO_PAKE_MODULE_ENABLED}) set(CRYPTO_IOVEC_BUFFER_SIZE ${CONFIG_TFM_CRYPTO_IOVEC_BUFFER_SIZE}) -set(CRYPTO_NV_SEED ${CONFIG_TFM_CRYPTO_NV_SEED}) +set(CRYPTO_NV_SEED 0) set(CRYPTO_SINGLE_PART_FUNCS_DISABLED ${CONFIG_TFM_CRYPTO_SINGLE_PART_FUNCS_DISABLED}) set(CRYPTO_STACK_SIZE ${CONFIG_TFM_CRYPTO_PARTITION_STACK_SIZE}) set(TFM_FWU_BUF_SIZE 0) diff --git a/modules/trusted-firmware-m/Kconfig b/modules/trusted-firmware-m/Kconfig index 48b5c4f073ba..4fc9185c8337 100644 --- a/modules/trusted-firmware-m/Kconfig +++ b/modules/trusted-firmware-m/Kconfig @@ -43,8 +43,9 @@ config TFM_PLATFORM_SP_STACK_SIZE config TFM_PLATFORM_NV_COUNTER_MODULE_DISABLED bool "Disable Non-volatile counter module" - default y if TFM_PROFILE_TYPE_MINIMAL - + default n if CONFIG_SOC_NRF54L15_ENGA_CPUAPP + default n if TFM_PARTITION_PROTECTED_STORAGE && TFM_PS_ROLLBACK_PROTECTION + default y endmenu # Copied from secure_fw/partitions/crypto/Kconfig, appended the TFM prefix diff --git a/modules/trusted-firmware-m/tfm_boards/common/attest_hal.c b/modules/trusted-firmware-m/tfm_boards/common/attest_hal.c index 4fe9c93fa73b..854e65367378 100644 --- a/modules/trusted-firmware-m/tfm_boards/common/attest_hal.c +++ b/modules/trusted-firmware-m/tfm_boards/common/attest_hal.c @@ -11,7 +11,6 @@ #include "tfm_attest_hal.h" #include "tfm_plat_boot_seed.h" #include "tfm_plat_device_id.h" -#include "tfm_plat_otp.h" #include #include "tfm_strnlen.h" #include "nrf_provisioning.h" @@ -74,49 +73,19 @@ int tfm_attest_update_security_lifecycle_otp(enum tfm_security_lifecycle_t slc) enum tfm_plat_err_t tfm_attest_hal_get_verification_service(uint32_t *size, uint8_t *buf) { - enum tfm_plat_err_t err; - size_t otp_size; - size_t copy_size; - - err = tfm_plat_otp_read(PLAT_OTP_ID_VERIFICATION_SERVICE_URL, *size, buf); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; - } - - err = tfm_plat_otp_get_size(PLAT_OTP_ID_VERIFICATION_SERVICE_URL, &otp_size); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; + if (read_variable_data(BL_VARIABLE_DATA_TYPE_VERIFICATION_SERVICE_URL, buf, size)) { + return TFM_PLAT_ERR_SYSTEM_ERR; } - /* Actually copied data is always the smaller */ - copy_size = *size < otp_size ? *size : otp_size; - /* String content */ - *size = tfm_strnlen((char *)buf, copy_size); - return TFM_PLAT_ERR_SUCCESS; } enum tfm_plat_err_t tfm_attest_hal_get_profile_definition(uint32_t *size, uint8_t *buf) { - enum tfm_plat_err_t err; - size_t otp_size; - size_t copy_size; - - err = tfm_plat_otp_read(PLAT_OTP_ID_PROFILE_DEFINITION, *size, buf); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; - } - - err = tfm_plat_otp_get_size(PLAT_OTP_ID_PROFILE_DEFINITION, &otp_size); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; + if (read_variable_data(BL_VARIABLE_DATA_TYPE_PROFILE_DEFINITION, buf, size)) { + return TFM_PLAT_ERR_SYSTEM_ERR; } - /* Actually copied data is always the smaller */ - copy_size = *size < otp_size ? *size : otp_size; - /* String content */ - *size = tfm_strnlen((char *)buf, copy_size); - return TFM_PLAT_ERR_SUCCESS; } @@ -147,24 +116,9 @@ enum tfm_plat_err_t tfm_plat_get_implementation_id(uint32_t *size, uint8_t *buf) enum tfm_plat_err_t tfm_plat_get_cert_ref(uint32_t *size, uint8_t *buf) { - enum tfm_plat_err_t err; - size_t otp_size; - size_t copy_size; - - err = tfm_plat_otp_read(PLAT_OTP_ID_CERT_REF, *size, buf); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; - } - - err = tfm_plat_otp_get_size(PLAT_OTP_ID_CERT_REF, &otp_size); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; + if (read_variable_data(BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE, buf, size)) { + return TFM_PLAT_ERR_SYSTEM_ERR; } - /* Actually copied data is always the smaller */ - copy_size = *size < otp_size ? *size : otp_size; - /* String content */ - *size = tfm_strnlen((char *)buf, copy_size); - return TFM_PLAT_ERR_SUCCESS; } diff --git a/modules/trusted-firmware-m/tfm_boards/partition/flash_layout.h b/modules/trusted-firmware-m/tfm_boards/partition/flash_layout.h index a0ad53009780..477cee58499e 100644 --- a/modules/trusted-firmware-m/tfm_boards/partition/flash_layout.h +++ b/modules/trusted-firmware-m/tfm_boards/partition/flash_layout.h @@ -148,11 +148,13 @@ #define TFM_HAL_ITS_PROGRAM_UNIT (0x4) /* OTP / NV counter definitions */ +#ifdef PM_TFM_OTP_NV_COUNTERS_ADDRESS #define TFM_OTP_NV_COUNTERS_AREA_SIZE (FLASH_OTP_NV_COUNTERS_AREA_SIZE / 2) #define TFM_OTP_NV_COUNTERS_AREA_ADDR FLASH_OTP_NV_COUNTERS_AREA_OFFSET #define TFM_OTP_NV_COUNTERS_SECTOR_SIZE FLASH_OTP_NV_COUNTERS_SECTOR_SIZE #define TFM_OTP_NV_COUNTERS_BACKUP_AREA_ADDR \ (TFM_OTP_NV_COUNTERS_AREA_ADDR + TFM_OTP_NV_COUNTERS_AREA_SIZE) +#endif /* Use Flash memory to store Code data */ #define FLASH_BASE_ADDRESS (0x00000000) diff --git a/samples/tfm/tfm_psa_template/sysbuild.conf b/samples/tfm/tfm_psa_template/sysbuild.conf index bc1ff1a8b380..5e70d40a5bb1 100644 --- a/samples/tfm/tfm_psa_template/sysbuild.conf +++ b/samples/tfm/tfm_psa_template/sysbuild.conf @@ -10,3 +10,6 @@ SB_CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y SB_CONFIG_MCUBOOT_MODE_OVERWRITE_ONLY=y SB_CONFIG_MCUBOOT_UPDATEABLE_IMAGES=2 SB_CONFIG_MCUBOOT_APP_SYNC_UPDATEABLE_IMAGES=n +SB_CONFIG_TFM_OTP_VERIFICATION_SERVICE_URL="verification_url.com" +SB_CONFIG_TFM_OTP_PSA_CERTIFICATE_REFERENCE=y +SB_CONFIG_TFM_OTP_PROFILE_DEFINITION="Profile Definition v1.0" diff --git a/scripts/bootloader/provision.py b/scripts/bootloader/provision.py index 191a5171a407..98b16a69ff6c 100644 --- a/scripts/bootloader/provision.py +++ b/scripts/bootloader/provision.py @@ -20,12 +20,25 @@ # These variable names and values are copied from bl_storage.h and # should be kept in sync +BL_COLLECTION_TYPE_MONOTONIC_COUNTERS = 0x1 +BL_COLLECTION_TYPE_VARIABLE_DATA = 0x9312 + BL_MONOTONIC_COUNTERS_DESC_NSIB = 1 BL_MONOTONIC_COUNTERS_DESC_MCUBOOT_ID0 = 2 +BL_VARIABLE_DATA_TYPE_VERIFICATION_SERVICE_URL = 0x1 +BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE = 0x2 +BL_VARIABLE_DATA_TYPE_PROFILE_DEFINITION = 0x3 + +# Maximum lengths for the TF-M attestation variable data fields. +# These are defined in tfm_attest_hal.h and in tfm_plat_device_id.h. +VERIFICATION_URL_MAX_SIZE = 32 +CERTIFICATION_REF_MAX_SIZE = 19 +PROFILE_DEFINITION_MAX_SIZE = 32 + def generate_provision_intel_hex_file(provision_data, provision_address, output, max_size): assert len(provision_data) <= max_size, """Provisioning data doesn't fit. -Reduce the number of public keys or counter slots and try again.""" +Reduce the number of public keys, counter slots or variable data and try again.""" ih = IntelHex() ih.frombytes(provision_data, offset=provision_address) @@ -41,7 +54,7 @@ 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', 1) # Type "counter collection" + 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: @@ -57,7 +70,7 @@ def add_hw_counters(provision_data, num_counter_slots_version, mcuboot_counters_ return provision_data -def generate_mcuboot_only_provision_hex_file(provision_address, output, max_size, mcuboot_counters_slots): +def generate_mcuboot_only_provision_hex_file(provision_address, output, max_size, mcuboot_counters_slots, variable_data): # This function generates a .hex file with the provisioned data # for the uncommon use-case where MCUBoot is present, but NSIB is # not. @@ -89,15 +102,16 @@ def generate_mcuboot_only_provision_hex_file(provision_address, output, max_size provision_data = add_hw_counters(provision_data, 0, mcuboot_counters_slots) + 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, - num_counter_slots_version, mcuboot_counters_slots): + num_counter_slots_version, mcuboot_counters_slots, variable_data): provision_data = struct.pack('III', s0_address, s1_address, len(hashes)) - idx = 0 for mhash in hashes: provision_data += struct.pack('I', 0x50FAFFFF | (idx << 24)) # Invalidation token @@ -106,6 +120,8 @@ def generate_provision_hex_file(s0_address, s1_address, hashes, provision_addres provision_data = add_hw_counters(provision_data, num_counter_slots_version, mcuboot_counters_slots) + provision_data += variable_data + generate_provision_intel_hex_file(provision_data, provision_address, output, max_size) @@ -132,6 +148,12 @@ def parse_args(): help="The MCUBOOT bootloader is used without the NSIB bootloader. Only the provision address, the MCUBOOT counters and the MCUBOOT counters slots arguments will be used.") parser.add_argument('--mcuboot-counters-slots', required=False, type=int, default=0, help='Number of monotonic counter slots for every MCUBOOT counter.') + parser.add_argument('--verification-service-url', required=False, type=str, default=None, + help='(Optional) Address of the verification service for the TF-M attestation token.') + parser.add_argument('--psa-certificate-reference', required=False, type=str, default=None, + help='(Optional) PSA certificate reference for the TF-M attestation token.') + parser.add_argument('--profile-definition', required=False, type=str, default=None, + help='(Optional) Profile definition for the TF-M attestation token.') return parser.parse_args() @@ -151,6 +173,53 @@ def get_hashes(public_key_files, verify_hashes): return hashes +def get_variable_data(verification_service_url, profile_definition, psa_certification_reference): + # Get variable data to be written to the OTP-region. + # + # Variable data, if present, starts with variable data collection ID, followed + # by amount of variable data entries and the variable data entries themselves. + # [Collection ID][Variable count][Type][Variable data length][Variable data][Type]... + # 2 bytes 2 bytes 1 byte 1 byte 0-255 bytes + # + variable_data = b'' + variable_data_count = 0 + + def add_variable_data(variable_data_type, data): + nonlocal variable_data + nonlocal variable_data_count + + variable_data += struct.pack('B', variable_data_type) + variable_data += struct.pack('B', len(data)) + variable_data += data.encode('ascii') + variable_data_count += 1 + + if verification_service_url: + if len(verification_service_url) > VERIFICATION_URL_MAX_SIZE: + raise RuntimeError(f"Verification service URL is too long. Maximum length is {VERIFICATION_URL_MAX_SIZE} characters.") + add_variable_data(BL_VARIABLE_DATA_TYPE_VERIFICATION_SERVICE_URL, verification_service_url) + + if psa_certification_reference: + if len(psa_certification_reference) > CERTIFICATION_REF_MAX_SIZE: + raise RuntimeError(f"PSA certification reference is too long. Maximum length is {CERTIFICATION_REF_MAX_SIZE} characters.") + add_variable_data(BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE, psa_certification_reference) + + if profile_definition: + if len(profile_definition) > PROFILE_DEFINITION_MAX_SIZE: + raise RuntimeError(f"Profile definition is too long. Maximum length is {PROFILE_DEFINITION_MAX_SIZE} characters.") + add_variable_data(BL_VARIABLE_DATA_TYPE_PROFILE_DEFINITION, profile_definition) + + + if variable_data_count: + # Add the variable data header at the beginning of the variable data. + variable_data = struct.pack('H', BL_COLLECTION_TYPE_VARIABLE_DATA) + \ + struct.pack('H', variable_data_count) + \ + variable_data + + # Padding to align to 4 bytes. + padding_length = 4 - (len(variable_data) % 4) + variable_data += b'\x00' * padding_length + + return variable_data def main(): args = parse_args() @@ -158,12 +227,19 @@ def main(): 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( + verification_service_url=args.verification_service_url, + profile_definition=args.profile_definition, + psa_certification_reference=args.psa_certificate_reference + ) + if args.mcuboot_only: generate_mcuboot_only_provision_hex_file( provision_address=args.provision_addr, output=args.output, max_size=args.max_size, - mcuboot_counters_slots=args.mcuboot_counters_slots + mcuboot_counters_slots=args.mcuboot_counters_slots, + variable_data=variable_data ) return @@ -193,7 +269,8 @@ def main(): output=args.output, max_size=max_size, num_counter_slots_version=args.num_counter_slots_version, - mcuboot_counters_slots=args.mcuboot_counters_slots + mcuboot_counters_slots=args.mcuboot_counters_slots, + variable_data=variable_data ) diff --git a/subsys/bootloader/bl_storage/bl_storage.c b/subsys/bootloader/bl_storage/bl_storage.c index cbbed0adb5fa..6f483240e416 100644 --- a/subsys/bootloader/bl_storage/bl_storage.c +++ b/subsys/bootloader/bl_storage/bl_storage.c @@ -12,7 +12,6 @@ #include #include -#define TYPE_COUNTERS 1 /* Type referring to counter collection. */ #define COUNTER_DESC_VERSION 1 /* Counter description value for firmware version. */ #ifdef CONFIG_SB_NUM_VER_COUNTER_SLOTS @@ -31,26 +30,6 @@ BUILD_ASSERT(CONFIG_MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_SLOTS % 2 == 0, * 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; - uint16_t counter_slots[1]; -}; - -/** 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 via array indices. - */ -struct counter_collection { - uint16_t type; /* Must be "monotonic counter". */ - uint16_t num_counters; /* Number of entries in 'counters' list. */ - struct monotonic_counter counters[1]; -}; /* * BL_STORAGE is usually, but not always, in UICR. For code simplicity @@ -67,11 +46,6 @@ uint32_t s1_address_read(void) return nrfx_nvmc_uicr_word_read(&BL_STORAGE->s1_address); } -uint32_t num_public_keys_read(void) -{ - return nrfx_nvmc_uicr_word_read(&BL_STORAGE->num_public_keys); -} - /* Value written to the invalidation token when invalidating an entry. */ #define INVALID_VAL 0x50FA0000 #define INVALID_WRITE_VAL 0xFFFF0000 @@ -136,14 +110,7 @@ int public_key_data_read(uint32_t key_idx, uint8_t *p_buf) p_key = BL_STORAGE->key_data[key_idx].hash; - /* Ensure word alignment, since the data is stored in memory region - * with word sized read limitation. Perform both build time and run - * time asserts to catch the issue as soon as possible. - */ - BUILD_ASSERT(offsetof(struct bl_storage_data, key_data) % 4 == 0); - __ASSERT(((uint32_t)p_key % 4 == 0), "Key address is not word aligned"); - - otp_copy32(p_buf, (volatile uint32_t *restrict)p_key, SB_PUBLIC_KEY_HASH_LEN); + otp_copy(p_buf, (volatile uint8_t *restrict)p_key, SB_PUBLIC_KEY_HASH_LEN); return SB_PUBLIC_KEY_HASH_LEN; } @@ -162,10 +129,11 @@ void invalidate_public_key(uint32_t key_idx) /** Get the counter_collection data structure in the provision data. */ static const struct counter_collection *get_counter_collection(void) { - const struct counter_collection *collection = (struct counter_collection *) - &BL_STORAGE->key_data[num_public_keys_read()]; - return nrfx_nvmc_otp_halfword_read((uint32_t)&collection->type) == TYPE_COUNTERS - ? collection : NULL; + const struct collection *collection = get_first_collection(); + + return get_collection_type(collection) == BL_COLLECTION_TYPE_MONOTONIC_COUNTERS + ? (const struct counter_collection *)collection + : NULL; } /** Get one of the (possibly multiple) counters in the provision data. @@ -183,7 +151,7 @@ static const struct monotonic_counter *get_counter_struct(uint16_t description) const struct monotonic_counter *current = counters->counters; for (size_t i = 0; i < nrfx_nvmc_otp_halfword_read( - (uint32_t)&counters->num_counters); i++) { + (uint32_t)&counters->collection.count); i++) { uint16_t num_slots = nrfx_nvmc_otp_halfword_read( (uint32_t)¤t->num_counter_slots); diff --git a/subsys/partition_manager/pm.yml.tfm b/subsys/partition_manager/pm.yml.tfm index b0c60768d0a1..f1d5e911a1b3 100644 --- a/subsys/partition_manager/pm.yml.tfm +++ b/subsys/partition_manager/pm.yml.tfm @@ -74,12 +74,14 @@ tfm_its: inside: tfm_storage size: CONFIG_PM_PARTITION_SIZE_TFM_INTERNAL_TRUSTED_STORAGE +#ifndef CONFIG_TFM_PLATFORM_NV_COUNTER_MODULE_DISABLED tfm_otp_nv_counters: placement: before: end align: {start: CONFIG_NRF_TRUSTZONE_FLASH_REGION_SIZE} inside: tfm_storage size: CONFIG_PM_PARTITION_SIZE_TFM_OTP_NV_COUNTERS +#endif #endif /* (CONFIG_SOC_NRF54L15_ENGA_CPUAPP || CONFIG_SOC_NRF54L15_CPUAPP)*/ diff --git a/sysbuild/Kconfig.sysbuild b/sysbuild/Kconfig.sysbuild index d724d17a3ab7..996b107e7f5c 100644 --- a/sysbuild/Kconfig.sysbuild +++ b/sysbuild/Kconfig.sysbuild @@ -79,3 +79,4 @@ rsource "Kconfig.zip" rsource "Kconfig.matter" rsource "Kconfig.wifi" rsource "Kconfig.suit" +rsource "Kconfig.tfm" diff --git a/sysbuild/Kconfig.tfm b/sysbuild/Kconfig.tfm new file mode 100644 index 000000000000..1f3f1f7b2ff4 --- /dev/null +++ b/sysbuild/Kconfig.tfm @@ -0,0 +1,38 @@ +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +config TFM_OTP_VERIFICATION_SERVICE_URL + string "TF-M attestation verification service URL" + depends on BOARD_IS_NON_SECURE + default "" + help + Include the optional URL of the verification service in the TF-M attestation token. + Stored in one-time programmable (OTP) memory during provisioning. + +config TFM_OTP_PSA_CERTIFICATE_REFERENCE + bool "Include TF-M attestation PSA certificate reference" + depends on BOARD_IS_NON_SECURE + default n + help + Include the optional PSA certificate reference in the TF-M attestation token. + Stored in one-time programmable (OTP) memory during provisioning. + +config TFM_PSA_CERTIFICATE_REFERENCE_VALUE + string "TF-M PSA Certificate Reference" + depends on TFM_OTP_PSA_CERTIFICATE_REFERENCE + depends on BOARD_IS_NON_SECURE + default "0632793519546-10200" if SOC_NRF9160 + default "0632793519539-10100" if SOC_NRF5340_CPUAPP + default "" + help + The reference of the PSA certificate. + Certificate details available at https://www.psacertified.org. + +config TFM_OTP_PROFILE_DEFINITION + string "TF-M attestation profile definition" + depends on BOARD_IS_NON_SECURE + default "" + help + Include the optional profile definition in the TF-M attestation token. + Stored in one-time programmable (OTP) memory during provisioning.