From db6ad7e4aa614546498b9a8a613d7a06e98d4974 Mon Sep 17 00:00:00 2001 From: Markus Lassila Date: Mon, 16 Sep 2024 08:11:02 +0300 Subject: [PATCH] tfm: Move TF-M attestation data to provisioned OTP region Optional fields to TF-M attestation were previously stored in tfm_otp_nv_counters region, which we were not able to provision. This moves the psa_certification_reference to the provisioned OTP-region and adds support for accessing the variable data in bl_storage.h. Verification service URL and profile may change with device upgrades, for this reason they are added as Kconfigs. Note that we still need to keep the tfm_otp_nv_counters region when TFM_PARTITION_PROTECTED_STORAGE and TFM_PS_ROLLBACK_PROTECTION are enabled. TF-M will increase monotonic counters every time new data is written and given the limited size of our OTP-region it would not support many updates. NCSDK-17932 Signed-off-by: Markus Lassila --- cmake/sysbuild/provision_hex.cmake | 6 + include/bl_storage.h | 82 ++++++++- modules/trusted-firmware-m/CMakeLists.txt | 16 +- modules/trusted-firmware-m/Kconfig | 12 +- .../tfm_boards/common/attest_hal.c | 86 ++++----- .../tfm_boards/partition/flash_layout.h | 2 + samples/tfm/tfm_psa_template/prj.conf | 3 + samples/tfm/tfm_psa_template/sysbuild.conf | 1 + scripts/bootloader/provision.py | 69 ++++++- subsys/bootloader/bl_storage/bl_storage.c | 172 +++++++++++++++--- subsys/partition_manager/pm.yml.tfm | 2 + sysbuild/Kconfig.sysbuild | 1 + sysbuild/Kconfig.tfm | 22 +++ 13 files changed, 382 insertions(+), 92 deletions(-) create mode 100644 sysbuild/Kconfig.tfm diff --git a/cmake/sysbuild/provision_hex.cmake b/cmake/sysbuild/provision_hex.cmake index 81d727afc74d..e7c56ef1ba55 100644 --- a/cmake/sysbuild/provision_hex.cmake +++ b/cmake/sysbuild/provision_hex.cmake @@ -87,6 +87,10 @@ 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_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(CONFIG_SECURE_BOOT) add_custom_command( OUTPUT @@ -103,6 +107,7 @@ function(provision application prefix_name) ${monotonic_counter_arg} ${no_verify_hashes_arg} ${mcuboot_counters_slots} + ${psa_certificate_reference} --lcs-state-size ${lcs_state_struct_size} DEPENDS ${PROVISION_KEY_DEPENDS} @@ -126,6 +131,7 @@ function(provision application prefix_name) --max-size ${CONFIG_PM_PARTITION_SIZE_PROVISION} ${mcuboot_counters_num} ${mcuboot_counters_slots} + ${psa_certificate_reference} --lcs-state-size ${lcs_state_struct_size} DEPENDS ${PROVISION_KEY_DEPENDS} diff --git a/include/bl_storage.h b/include/bl_storage.h index 6413b8c018e8..70049fd7bdea 100644 --- a/include/bl_storage.h +++ b/include/bl_storage.h @@ -44,6 +44,12 @@ typedef uint32_t lcs_reserved_t; /* 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 @@ -91,20 +97,47 @@ struct monotonic_counter { counter_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 { - uint16_t type; /* Must be "monotonic counter". */ - uint16_t num_counters; /* Number of entries in 'counters' list. */ + 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_PSA_CERTIFICATION_REFERENCE = 0x1 +}; +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]; +}; + /** 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 */ @@ -117,6 +150,27 @@ struct bl_storage_data { uint32_t valid; uint8_t hash[SB_PUBLIC_KEY_HASH_LEN]; } key_data[1]; + + /* Monotonic counter collection: + * uint16_t type; + * uint16_t count; + * struct { + * uint16_t description; + * uint16_t num_counter_slots; + * uint16_t counter_slots[1]; + * } counters[1]; + */ + + /* Variable data collection: + * 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)) @@ -150,7 +204,7 @@ uint32_t s1_address_read(void); uint32_t num_public_keys_read(void); /** - * @brief Function for reading number of public key data slots. + * @brief Function for verifying public keys. * * @retval 0 if all keys are ok. * @retval -EHASHFF if one or more keys contains an aligned 0xFFFF. @@ -265,6 +319,26 @@ int update_life_cycle_state(enum lcs next_lcs); */ void read_implementation_id_from_otp(uint8_t *buf); +/** + * @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); + #endif /** @} */ diff --git a/modules/trusted-firmware-m/CMakeLists.txt b/modules/trusted-firmware-m/CMakeLists.txt index 62ec748b929d..4f83626001fb 100644 --- a/modules/trusted-firmware-m/CMakeLists.txt +++ b/modules/trusted-firmware-m/CMakeLists.txt @@ -168,6 +168,10 @@ set_property(TARGET zephyr_property_target -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 APPEND PROPERTY TFM_CMAKE_OPTIONS @@ -187,13 +191,21 @@ 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 + ) +endif() + if(NOT CONFIG_MBEDTLS_PSA_CRYPTO_STORAGE_C) # Workaround: NCSDK-13530 # Allow TF-M crypto to not depend on ITS when PSA crypto storage is disabled. @@ -307,7 +319,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(CRYPTO_LIBRARY_ABI_COMPAT ON) diff --git a/modules/trusted-firmware-m/Kconfig b/modules/trusted-firmware-m/Kconfig index 7021d6771d49..ff2372209f85 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 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 @@ -130,6 +131,13 @@ config TFM_ATTEST_INCLUDE_OPTIONAL_CLAIMS bool "Include optional claims in initial attestation token" default y +config TFM_ATTEST_VERIFICATION_SERVICE_URL + string "TF-M attestation verification service URL" + default "" + depends on TFM_ATTEST_INCLUDE_OPTIONAL_CLAIMS + help + Optional claim of URL of the verification service in the TF-M attestation token. + config TFM_ATTEST_INCLUDE_COSE_KEY_ID bool "Include COSE key-id in initial attestation token" default n 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..37558514bde0 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" @@ -72,50 +71,30 @@ int tfm_attest_update_security_lifecycle_otp(enum tfm_security_lifecycle_t slc) return update_life_cycle_state(next_lcs); } -enum tfm_plat_err_t tfm_attest_hal_get_verification_service(uint32_t *size, uint8_t *buf) +static const char *get_attestation_profile(void) { - 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; - } - - /* 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; +#if defined(CONFIG_TFM_ATTEST_TOKEN_PROFILE_PSA_IOT_1) + return "PSA_IOT_PROFILE_1"; +#elif defined(CONFIG_TFM_ATTEST_TOKEN_PROFILE_PSA_2_0_0) + return "http://arm.com/psa/2.0.0"; +#elif defined(CONFIG_TFM_ATTEST_TOKEN_PROFILE_ARM_CCA) + return "http://arm.com/CCA-SSD/1.0.0"; +#else +#error "Attestation token profile not defined" +#endif } 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; - } + const char *profile = get_attestation_profile(); + uint32_t profile_len = strlen(profile); - err = tfm_plat_otp_get_size(PLAT_OTP_ID_PROFILE_DEFINITION, &otp_size); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; + if (*size < profile_len) { + 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); + memcpy(buf, profile, profile_len); + *size = profile_len; return TFM_PLAT_ERR_SUCCESS; } @@ -144,27 +123,30 @@ enum tfm_plat_err_t tfm_plat_get_implementation_id(uint32_t *size, uint8_t *buf) return TFM_PLAT_ERR_SUCCESS; } -enum tfm_plat_err_t tfm_plat_get_cert_ref(uint32_t *size, uint8_t *buf) +#if CONFIG_TFM_ATTEST_INCLUDE_OPTIONAL_CLAIMS +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; + if (read_variable_data(BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE, buf, size)) { + return TFM_PLAT_ERR_SYSTEM_ERR; } - err = tfm_plat_otp_get_size(PLAT_OTP_ID_CERT_REF, &otp_size); - if (err != TFM_PLAT_ERR_SUCCESS) { - return err; + return TFM_PLAT_ERR_SUCCESS; +} + +enum tfm_plat_err_t tfm_attest_hal_get_verification_service(uint32_t *size, uint8_t *buf) +{ + const char *url = CONFIG_TFM_ATTEST_VERIFICATION_SERVICE_URL; + uint32_t url_len = strlen(url); + + if (*size < url_len) { + 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); + memcpy(buf, url, url_len); + *size = url_len; return TFM_PLAT_ERR_SUCCESS; } + +#endif /* CONFIG_TFM_ATTEST_INCLUDE_OPTIONAL_CLAIMS */ 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 4c4cef76c664..08823eb2f36d 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/prj.conf b/samples/tfm/tfm_psa_template/prj.conf index 1483bb0d4d4b..dd0df4ca54a1 100644 --- a/samples/tfm/tfm_psa_template/prj.conf +++ b/samples/tfm/tfm_psa_template/prj.conf @@ -67,3 +67,6 @@ CONFIG_TFM_ISOLATION_LEVEL=2 # Initial Attestation requires RNG CONFIG_PSA_WANT_GENERATE_RANDOM=y + +# Optional attestation token fields +CONFIG_TFM_ATTEST_VERIFICATION_SERVICE_URL="verification_url" diff --git a/samples/tfm/tfm_psa_template/sysbuild.conf b/samples/tfm/tfm_psa_template/sysbuild.conf index b41085afb318..6cf40685f7f5 100644 --- a/samples/tfm/tfm_psa_template/sysbuild.conf +++ b/samples/tfm/tfm_psa_template/sysbuild.conf @@ -10,3 +10,4 @@ SB_CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y SB_CONFIG_MCUBOOT_MODE_OVERWRITE_ONLY=y SB_CONFIG_APPROTECT_LOCK=y SB_CONFIG_SECURE_APPROTECT_LOCK=y +SB_CONFIG_TFM_OTP_PSA_CERTIFICATE_REFERENCE=y diff --git a/scripts/bootloader/provision.py b/scripts/bootloader/provision.py index 80128d0527e9..da4877c57988 100644 --- a/scripts/bootloader/provision.py +++ b/scripts/bootloader/provision.py @@ -18,12 +18,21 @@ # 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_PSA_CERTIFICATION_REFERENCE = 0x1 + +# 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. +CERTIFICATION_REF_MAX_SIZE = 19 + 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) @@ -39,7 +48,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: @@ -55,7 +64,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, lcs_state_sz): +def generate_mcuboot_only_provision_hex_file(provision_address, output, max_size, mcuboot_counters_slots, lcs_state_sz, 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. @@ -87,11 +96,13 @@ 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)) @@ -104,6 +115,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 +145,8 @@ def parse_args(): help='Number of monotonic counter slots for every MCUBOOT counter.') parser.add_argument('--lcs-state-size', required=False, type=lambda x: int(x, 0), default=0x8, help='Size of life cycle state data structure in bytes.') + parser.add_argument('--psa-certificate-reference', required=False, type=str, default=None, + help='(Optional) PSA certificate reference for the TF-M attestation token.') return parser.parse_args() @@ -151,6 +166,42 @@ def get_hashes(public_key_files, verify_hashes): return hashes +def get_variable_data(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 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 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() @@ -161,13 +212,18 @@ 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( + 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, - lcs_state_sz=lcs_state_size + lcs_state_sz=lcs_state_size, + variable_data=variable_data ) return @@ -197,7 +253,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 6584703feca1..f7c5b9d1f0e9 100644 --- a/subsys/bootloader/bl_storage/bl_storage.c +++ b/subsys/bootloader/bl_storage/bl_storage.c @@ -16,7 +16,6 @@ #include #endif -#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 @@ -170,19 +169,38 @@ int verify_public_keys(void) * 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. */ -static void otp_copy32(uint8_t *restrict dst, uint32_t volatile * restrict src, - size_t size) +static void otp_copy(uint8_t *restrict dst, const uint8_t volatile * restrict src, + size_t size) { - for (int i = 0; i < size / 4; i++) { - /* OTP is in UICR */ - uint32_t val = bl_storage_word_read((uint32_t)(src + i)); + size_t copied = 0; /* Bytes copied. */ + uint8_t src_offset = (uint32_t)src % 4; /* Align the source address to 32-bits. */ - for (int j = 0; j < 4; j++) { - dst[i * 4 + j] = (val >> 8 * j) & 0xFF; + uint32_t val; /* 32-bit value read from the OTP. */ + uint8_t copy_size; /* Number of bytes to copy. */ + + while (copied < size) { + + /* Read 32-bits. */ + val = bl_storage_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; } } @@ -200,14 +218,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, p_key, SB_PUBLIC_KEY_HASH_LEN); return SB_PUBLIC_KEY_HASH_LEN; } @@ -223,13 +234,24 @@ void invalidate_public_key(uint32_t key_idx) } } +static const struct collection *get_first_collection(void) +{ + return (const struct collection *)&BL_STORAGE->key_data[num_public_keys_read()]; +} + +static uint16_t get_collection_type(const struct collection *collection) +{ + return bl_storage_otp_halfword_read((uint32_t)&collection->type); +} + /** Get the counter_collection data structure in the provision data. */ -const struct counter_collection *get_counter_collection(void) +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 bl_storage_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. @@ -247,7 +269,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 < bl_storage_otp_halfword_read( - (uint32_t)&counters->num_counters); i++) { + (uint32_t)&counters->collection.count); i++) { uint16_t num_slots = bl_storage_otp_halfword_read( (uint32_t)¤t->num_counter_slots); @@ -468,8 +490,106 @@ 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); } -#endif +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 = + bl_storage_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(counter_t); + + uint16_t num_slots = + bl_storage_otp_halfword_read((uint32_t)&counter->num_counter_slots); + size += (num_slots * sizeof(counter_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; +} + +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 = + bl_storage_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 /* CONFIG_BUILD_WITH_TFM */ diff --git a/subsys/partition_manager/pm.yml.tfm b/subsys/partition_manager/pm.yml.tfm index babbdc542c00..4204ed35d236 100644 --- a/subsys/partition_manager/pm.yml.tfm +++ b/subsys/partition_manager/pm.yml.tfm @@ -62,12 +62,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_CPUAPP */ diff --git a/sysbuild/Kconfig.sysbuild b/sysbuild/Kconfig.sysbuild index 37a8907deaf2..174159d2c16a 100644 --- a/sysbuild/Kconfig.sysbuild +++ b/sysbuild/Kconfig.sysbuild @@ -82,3 +82,4 @@ rsource "Kconfig.suit" rsource "Kconfig.sdp" rsource "Kconfig.approtect" rsource "Kconfig.lwm2m_carrier" +rsource "Kconfig.tfm" diff --git a/sysbuild/Kconfig.tfm b/sysbuild/Kconfig.tfm new file mode 100644 index 000000000000..29a64403e3a5 --- /dev/null +++ b/sysbuild/Kconfig.tfm @@ -0,0 +1,22 @@ +# Copyright (c) 2024 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +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.