From 2cf35a2b74b001687cf043da50d1bb2cb243245b 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 + .../releases/release-notes-changelog.rst | 7 +- include/bl_storage.h | 92 +++++++++- modules/trusted-firmware-m/CMakeLists.txt | 16 +- modules/trusted-firmware-m/Kconfig | 11 +- modules/trusted-firmware-m/Kconfig.tfm.pm | 3 +- .../tfm_boards/common/attest_hal.c | 86 ++++----- .../tfm_boards/partition/flash_layout.h | 2 + samples/tfm/tfm_psa_template/README.rst | 80 +++++---- 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 | 168 +++++++++++++++--- sysbuild/Kconfig.sysbuild | 1 + sysbuild/Kconfig.tfm | 21 +++ 15 files changed, 437 insertions(+), 129 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/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index e8417f9579be..df31755a8537 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -369,8 +369,13 @@ SUIT samples Trusted Firmware-M (TF-M) samples --------------------------------- -|no_changes_yet_note| +* :ref:`tfm_psa_template` sample: + + * Added support for the following attestation token fields: + * Profile definition + * PSA certificate reference (optional), configured using the :kconfig:option:`SB_CONFIG_TFM_OTP_PSA_CERTIFICATE_REFERENCE` sysbuild Kconfig option + * Verification service URL (optional), configured using the :kconfig:option:`CONFIG_TFM_ATTEST_VERIFICATION_SERVICE_URL` Kconfig option Thread samples -------------- diff --git a/include/bl_storage.h b/include/bl_storage.h index 6413b8c018e8..25d1eaa1fc4b 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 @@ -88,23 +94,50 @@ struct monotonic_counter { uint16_t description; /* Number of entries in 'counter_slots' list. */ uint16_t num_counter_slots; - counter_t counter_slots[1]; + 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 via array indices. + * entries beyond the first cannot be accessed through 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]; + 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; + uint8_t data[]; +}; + +/* 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 through array indices. + */ +struct variable_data_collection { + struct collection collection; /* Type must be BL_COLLECTION_TYPE_VARIABLE_DATA */ + struct variable_data variable_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 */ @@ -116,7 +149,28 @@ struct bl_storage_data { struct { uint32_t valid; uint8_t hash[SB_PUBLIC_KEY_HASH_LEN]; - } key_data[1]; + } key_data[]; + + /* Monotonic counter collection: + * uint16_t type; + * uint16_t count; + * struct { + * uint16_t description; + * uint16_t num_counter_slots; + * uint16_t counter_slots[]; + * } counters[]; + */ + + /* 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 + */ }; #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. @@ -259,12 +313,32 @@ int update_life_cycle_state(enum lcs next_lcs); #if CONFIG_BUILD_WITH_TFM /** - * Read the implementation id from OTP and copy it into a given buffer. + * Read the implementation ID from OTP and copy it into a given buffer. * * @param[out] buf Buffer that has at least BL_STORAGE_IMPLEMENTATION_ID_SIZE bytes */ 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 d3a3190c2cdd..07509823ee5d 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. @@ -314,7 +326,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 6c4d9007d518..be1833636a8f 100644 --- a/modules/trusted-firmware-m/Kconfig +++ b/modules/trusted-firmware-m/Kconfig @@ -43,8 +43,8 @@ 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 TFM_PARTITION_PROTECTED_STORAGE && TFM_PS_ROLLBACK_PROTECTION + default y endmenu # Copied from secure_fw/partitions/crypto/Kconfig, appended the TFM prefix @@ -130,6 +130,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/Kconfig.tfm.pm b/modules/trusted-firmware-m/Kconfig.tfm.pm index 3a4e35b771a2..eaa48f14478e 100644 --- a/modules/trusted-firmware-m/Kconfig.tfm.pm +++ b/modules/trusted-firmware-m/Kconfig.tfm.pm @@ -59,7 +59,8 @@ config PM_PARTITION_SIZE_TFM_INTERNAL_TRUSTED_STORAGE config PM_PARTITION_SIZE_TFM_OTP_NV_COUNTERS hex "Memory reserved for TFM OTP / Non-Volatile Counters" - default 0x2000 if !TFM_PROFILE_TYPE_MINIMAL + default 0x2000 if !TFM_PROFILE_TYPE_MINIMAL && \ + !TFM_PLATFORM_NV_COUNTER_MODULE_DISABLED default 0 help Memory set aside for the OTP / Non-Volatile (NV) Counters partition 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/README.rst b/samples/tfm/tfm_psa_template/README.rst index e89d58e9f03f..8d175c9945b6 100644 --- a/samples/tfm/tfm_psa_template/README.rst +++ b/samples/tfm/tfm_psa_template/README.rst @@ -57,25 +57,31 @@ After programming the sample, the following output is displayed in the console: .. code-block:: console - *** Booting nRF Connect SDK v2.6.99-27b5931e8742 *** - *** Using Zephyr OS v3.6.99-b5cf30402984 *** + *** Booting nRF Connect SDK v2.9.99-ad7a4f0e68fa *** + *** Using Zephyr OS v3.7.99-f5efb381b8af *** Attempting to boot slot 0. Attempting to boot from address 0x8200. - Verifying signature against key 0. - Hash: 0x43...7f - Firmware signature verified. + I: Trying to get Firmware version + I: Verifying signature against key 0. + I: Hash: 0xf0...99 + I: Firmware signature verified. Firmware version 1 - *** Booting My Application v2.1.0-dev-0b5810de95eb *** - *** Using nRF Connect SDK v2.6.99-27b5931e8742 *** - *** Using Zephyr OS v3.6.99-b5cf30402984 *** - *** Booting nRF Connect SDK v2.6.99-27b5931e8742 *** - *** Using Zephyr OS v3.6.99-b5cf30402984 *** - build time: May 13 2024 17:13:28 + *** Booting My Application v2.1.0-dev-b82206c15fff *** + *** Using nRF Connect SDK v2.9.99-ad7a4f0e68fa *** + *** Using Zephyr OS v3.7.99-f5efb381b8af *** + I: Starting bootloader + I: Image index: 0, Swap type: none + I: Image index: 1, Swap type: none + I: Image index: 2, Swap type: none + I: Bootloader chainload address offset: 0x20000 + *** Booting nRF Connect SDK v2.9.99-ad7a4f0e68fa *** + *** Using Zephyr OS v3.7.99-f5efb381b8af *** + build time: Dec 19 2024 08:31:12 FW info S0: Magic: 0x281ee6de8fcebb4c00003502 - Total Size: 60 - Size: 0x000081c8 + Total Size: 140 + Size: 0x0000805c Version: 1 Address: 0x00008200 Boot address: 0x00008200 @@ -83,8 +89,8 @@ After programming the sample, the following output is displayed in the console: FW info S1: Magic: 0x281ee6de8fcebb4c00003502 - Total Size: 60 - Size: 0x000081c8 + Total Size: 140 + Size: 0x0000805c Version: 1 Address: 0x00014200 Boot address: 0x00014200 @@ -93,28 +99,32 @@ After programming the sample, the following output is displayed in the console: Active slot: S0 Requesting initial attestation token with 64 byte challenge. - Received initial attestation token of 303 bytes. + Received initial attestation token of 360 bytes. 0 1 2 3 4 5 6 7 8 9 A B C D E F - D2 84 43 A1 01 26 A0 58 E4 AA 3A 00 01 24 FF 58 | ..C..&.X..:..$.X - 40 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE | @.."3DUfw....... - FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE | ..."3DUfw....... - FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE | ..."3DUfw....... - FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE | ..."3DUfw....... - FF 3A 00 01 24 FB 58 20 3B 32 D3 C6 4F E0 9E 44 | .:..$.X ;2..O..D - 85 33 05 C0 7E 1B 14 39 C0 73 22 2E AE 63 68 8C | .3..~..9.s"..ch. - 26 66 52 D2 84 50 EF 56 3A 00 01 25 00 58 21 01 | &fR..P.V:..%.X!. - 70 7D AE 14 A9 68 1F 77 99 6F F8 02 CF 69 C2 47 | p}...h.w.o...i.G - BF 3A 10 DD EA C3 74 6E 0B F7 45 CF 2A 25 A9 DA | .:....tn..E.*%.. - 3A 00 01 24 FA 58 20 AA AA AA AA AA AA AA AA BB | :..$.X ......... - BB BB BB BB BB BB BB CC CC CC CC CC CC CC CC DD | ................ - DD DD DD DD DD DD DD 3A 00 01 24 F8 20 3A 00 01 | .......:..$. :.. - 24 F9 19 30 00 3A 00 01 24 FE 01 3A 00 01 24 F7 | $..0.:..$..:..$. - 60 3A 00 01 25 01 60 3A 00 01 24 FC 60 58 40 00 | `:..%.`:..$.`X@. - E0 AB 97 6B C1 24 8D AF C9 E1 1C 77 E4 5E 1D 8E | ...k.$.....w.^.. - 0D 44 61 76 28 A5 0D A1 BE A3 2D B2 A0 35 77 0E | .Dav(.....-..5w. - 78 72 7D E6 BE A1 10 A2 DC 7C ED 87 76 C7 33 E4 | xr}......|..v.3. - 4E 8C 39 3D AA EC 40 EB 31 4B D5 68 80 53 77 | N.9=..@.1K.h.Sw + D2 84 43 A1 01 26 A0 59 01 1C AA 3A 00 01 24 FF | ..C..&.Y...:..$. + 58 40 00 11 22 33 44 55 66 77 88 99 AA BB CC DD | X@.."3DUfw...... + EE FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD | ...."3DUfw...... + EE FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD | ...."3DUfw...... + EE FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD | ...."3DUfw...... + EE FF 3A 00 01 24 FB 58 20 20 E7 18 5E 6F F1 0A | ..:..$.X ..^o.. + F7 C0 04 63 A9 6C 73 78 65 92 11 02 EC DF 09 EF | ...c.lsxe....... + 4C BD 13 AD 69 A3 33 AD AE 3A 00 01 25 00 58 21 | L...i.3..:..%.X! + 01 69 48 1A 3C 87 E2 6E 48 D3 15 8A 45 F1 D8 E4 | .iH.<..nH...E... + 6A 00 A0 14 0C 87 E5 5E 3F B3 B6 98 45 8F 36 5E | j......^?...E.6^ + BB 3A 00 01 24 FA 58 20 AA AA AA AA AA AA AA AA | .:..$.X ........ + BB BB BB BB BB BB BB BB CC CC CC CC CC CC CC CC | ................ + DD DD DD DD DD DD DD DD 3A 00 01 24 F8 3A 3B FF | ........:..$.:;. + FF FF 3A 00 01 24 F9 19 30 00 3A 00 01 24 FE 01 | ..:..$..0.:..$.. + 3A 00 01 24 F7 71 50 53 41 5F 49 4F 54 5F 50 52 | :..$.qPSA_IOT_PR + 4F 46 49 4C 45 5F 31 3A 00 01 25 01 70 76 65 72 | OFILE_1:..%.pver + 69 66 69 63 61 74 69 6F 6E 5F 75 72 6C 3A 00 01 | ification_url:.. + 24 FC 73 30 36 33 32 37 39 33 35 31 39 35 33 39 | $.s0632793519539 + 2D 31 30 31 30 30 58 40 F9 04 F8 56 4F 89 A0 67 | -10100X@...VO..g + 2C 21 31 6C 88 EF D6 34 D1 02 4F 6D EA 17 54 9F | ,!1l...4..Om..T. + B6 90 E4 6E D6 55 4E D3 62 8C 9D 6A 0F 67 8D 9E | ...n.UN.b..j.g.. + D7 05 45 3C 89 BE C2 9B 2B D0 ED 05 F1 AC 42 21 | ..E<....+.....B! + F0 05 00 CE B7 B9 47 E9 | ......G. Firmware update *************** 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..eb6fbeac20cb 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)) % 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..c505d9baf777 100644 --- a/subsys/bootloader/bl_storage/bl_storage.c +++ b/subsys/bootloader/bl_storage/bl_storage.c @@ -10,13 +10,11 @@ #include #include #include -#if defined(CONFIG_BUILD_WITH_TFM) #include -#else +#if !defined(CONFIG_BUILD_WITH_TFM) #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 +168,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. */ + + 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; + } - for (int j = 0; j < 4; j++) { - dst[i * 4 + j] = (val >> 8 * j) & 0xFF; + /* 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; } } @@ -207,7 +224,7 @@ int public_key_data_read(uint32_t key_idx, uint8_t *p_buf) 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 +240,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 +275,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 +496,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); + + 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/sysbuild/Kconfig.sysbuild b/sysbuild/Kconfig.sysbuild index 958e7080ffcd..97513ef3d17f 100644 --- a/sysbuild/Kconfig.sysbuild +++ b/sysbuild/Kconfig.sysbuild @@ -83,3 +83,4 @@ rsource "Kconfig.suit_provisioning" 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..2dfe99fb771b --- /dev/null +++ b/sysbuild/Kconfig.tfm @@ -0,0 +1,21 @@ +# 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 + 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.