The Secure Storage is one of the main services of the Trusted Firmware and is quite useful to store data in a secure way. Let’s focus on the Secure Storage service of Trusted Firmware (TFM) and modify the current software to write more data into the Secure Storage and read it back.
The third article of the series on Trusted Firmware focuses of the Secure Storage service and extends the regular TFM demo application by writing more pieces of data into the Secure Storage and reading them.
The other articles of the series are:
The TFM demo application uses the internal and external flash memories for data and code areas as shown in Figure 8 below (taken from [UM2671]).
The secure application, containing the PSA updatable Root of Trust and the application updatable Root of Trust, is in the area 0 which is located in the internal flash memory. The non-secure application is in the area 1 which is located in the external flash memory. If the non-secure application binary is encrypted, the OTFDEC module decrypts it on-the-fly while the processor fetches the instructions.
The Secure Storage area is also located in the internal flash memory in the secure area and has a size of 8 kbytes.
Figure 4 (taken from [UM2671]) presents a clear view of which entity can access which assets.
The Secure Storage Service accesses the crypto operations, encryption key and NVM data storage. The implementation of the current TFM application is based on the TF-M v1.0-RC2 release [UM2671, p.1] which includes the Secure Storage API v1.0.0 available in PDF format. This document presents the Internal Trusted Storage API and the Secure Storage API (see chapter 5.2 Protected Storage API).
The use of TFM makes it difficult to follow the function calls in the source code. The TFM Middleware, which is available on the non-secure side, implements the functions of the API but they end up making PSA Calls (using the IPC – or interprocess communication – mode) as we have seen in the second article to access the secure side.
Functions implemented in TFM are (descriptions partly taken from API doc):
psa_ps_set
: Create a new or modify an existing key/value pair by associating an UID and a data of known sizepsa_ps_get
: Read part or all of the data (depends on read offset and read size) associated with the provided UIDpsa_ps_get_info
: Read the metadata associated with an UID (storage size and flags)psa_ps_remove
: Remove the UID and its associated data from the storagepsa_ps_get_support
: Return the flags set for all of the optional features supported by the implementationIn the API, function psa_ps_get_info
uses struct psa_storage_info_t
as container for metadata. This structure contains three fields: the capacity of the storage, the size of the actual stored data and the flags of the storage. However, the implementation uses struct psa_ps_info_t
instead which contains only the size of the actual stored data and the flags.
Function psa_ps_set
is able to modify the data already stored. As the metadata does not contain a capacity field, it is not possible to reserve more storage space than what the initial call to psa_ps_set
stored. Therefore, a modified data must have a size equal to or lower than the previous data stored within the same UID.
The modification of the TFM demo application required only a few changes. The biggest part was of course the code developed to run the new tests.
diff --git a/TFM_Appli/NonSecure/Src/main.c b/TFM_Appli/NonSecure/Src/main.c index eee3deb..df0c98c 100644 --- a/TFM_Appli/NonSecure/Src/main.c +++ b/TFM_Appli/NonSecure/Src/main.c @@ -34,6 +34,7 @@ __asm(" .global __ARM_use_no_argv\n"); #include "test_protections.h" #include "fw_update_app.h" #include "tfm_app.h" +#include "tfm_blog.h" /** @defgroup USER_APP exported variable * @{ */ @@ -168,6 +169,7 @@ void FW_APP_PrintMainMenu(void) #if !defined(TFM_EXTERNAL_FLASH_ENABLE) printf(" Download a new Fw Image ------------------------------- 3\r\n\n"); #endif /* !TFM_EXTERNAL_FLASH_ENABLE */ + printf(" Test TFM BLOG ----------------------------------------- 4\r\n\n"); printf(" Selection :\r\n\n"); } @@ -204,6 +206,9 @@ void FW_APP_Run(void) FW_UPDATE_Run(); break; #endif /* !TFM_EXTERNAL_FLASH_ENABLE */ + case '4': + tfm_blog_menu(); + break; default: printf("Invalid Number !\r"); break;
diff --git a/TFM_Appli/STM32CubeIDE/NonSecure/.project b/TFM_Appli/STM32CubeIDE/NonSecure/.project index 7ab20fa..a7769a4 100644 --- a/TFM_Appli/STM32CubeIDE/NonSecure/.project +++ b/TFM_Appli/STM32CubeIDE/NonSecure/.project @@ -81,6 +81,11 @@ <type>1</type> <locationURI>PARENT-2-PROJECT_LOC/NonSecure/Src/tfm_app.c</locationURI> </link> + <link> + <name>Application/User/tfm_blog.c</name> + <type>1</type> + <locationURI>PARENT-2-PROJECT_LOC/NonSecure/Src/tfm_blog.c</locationURI> + </link> <link> <name>Application/User/tfm_ns_lock.c</name> <type>1</type>
diff --git a/TFM_SBSFU_Boot/STM32CubeIDE/regression.sh b/TFM_SBSFU_Boot/STM32CubeIDE/regression.sh index ce975e7..1286f43 100644 --- a/TFM_SBSFU_Boot/STM32CubeIDE/regression.sh +++ b/TFM_SBSFU_Boot/STM32CubeIDE/regression.sh @@ -1,6 +1,6 @@ #!/bin/bash - echo "regression script started" -PATH="/C/Program Files/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/":$PATH +PATH="/C/ST/STM32CubeProgrammer/bin/":$PATH stm32programmercli="STM32_Programmer_CLI" secbootadd0=0x180052 connect="-c port=SWD mode=UR --hardRst"
/** ******************************************************************** * @file tfm_blog.h * @author Yorick Brunet * @brief TFM application examples module for bloging purposes. ******************************************************************** */ /* Define to prevent recursive inclusion ---------------------------*/ #ifndef TFM_BLOG_H #define TFM_BLOG_H #ifdef __cplusplus extern "C" { #endif #define INVOKE_SCHEDULE_NEEDS() \ do { \ ; \ } while(0); /* Exported prototypes -------------------------------------------- */ void tfm_blog_menu(void); #ifdef __cplusplus } #endif #endif /* TFM_BLOG_H */
/** ******************************************************************** * @file tfm_blog.c * @author Yorick Brunet * @brief TFM application examples module for bloging purposes. ******************************************************************** */ /* Includes --------------------------------------------------------*/ #include <string.h> #include "tfm_blog.h" #include "psa/error.h" #include "psa/protected_storage.h" #include "com.h" /** @defgroup TFM_Blog_Private_Defines Private Defines * @{ */ /* Private define -------------------------------------------------*/ const uint8_t DATA1[] = "HELLO BLOG !"; const uint32_t DATA1_SIZE = sizeof(DATA1) - 1; const uint8_t DATA2[] = "HOWTO WRITE AND READ SST !"; const uint32_t DATA2_SIZE = sizeof(DATA2) - 1; typedef enum { UID1 = 3U, UID2, } UID_t; /** * @} */ /** @defgroup TFM_Blog_Private_Functions Private Functions * @{ */ static void print_menu(void); static void sst_set_uid(const UID_t ex_uid); static void sst_remove_uid(const UID_t ex_uid); static void sst_read_uid(const UID_t ex_uid); static void sst_readinfo_uid(const UID_t ex_uid); /** * @} */ /** @defgroup TFM_Blog_Exported_Functions Exported Functions * @{ */ /** * @brief Display the TFM Blog TEST Main Menu choices on HyperTerminal * @param None. * @retval None. */ void tfm_blog_menu(void) { uint8_t key = 0; uint8_t exit = 0; print_menu(); while (exit == 0U) { key = 0U; INVOKE_SCHEDULE_NEEDS(); /* Clean the user input path */ COM_Flush(); /* Receive key */ if (COM_Receive(&key, 1U, COM_UART_TIMEOUT_MAX) == HAL_OK) { switch (key) { case '1' : sst_set_uid(UID1); print_menu(); break; case '2' : sst_read_uid(UID1); print_menu(); break; case '3' : sst_remove_uid(UID1); print_menu(); break; case '4': sst_readinfo_uid(UID1); print_menu(); break; case '5' : sst_set_uid(UID2); print_menu(); break; case '6' : sst_read_uid(UID2); print_menu(); break; case '7' : sst_remove_uid(UID2); print_menu(); break; case '8': sst_readinfo_uid(UID2); print_menu(); break; case 'x': exit = 1; break; default: printf("Invalid Number !\r"); print_menu(); break; } } } } /** * @} */ /** @addtogroup TFM_Blog_Private_Functions * @{ */ /** * @brief Display the TEST TFM Blog Menu choices on HyperTerminal * @param None. * @retval None. */ static void print_menu(void) { printf("\r\n======================= TFM Examples Menu ===========================\r\n\n"); printf(" TFM - Test SST set UID 1 --------------------- 1\r\n\n"); printf(" TFM - Test SST read / check UID 1 --------------------- 2\r\n\n"); printf(" TFM - Test SST remove UID 1 --------------------- 3\r\n\n"); printf(" TFM - Test SST info UID 1 --------------------- 4\r\n\n"); printf(" TFM - Test SST set UID 2 --------------------- 5\r\n\n"); printf(" TFM - Test SST read / check UID 2 --------------------- 6\r\n\n"); printf(" TFM - Test SST remove UID 2 --------------------- 7\r\n\n"); printf(" TFM - Test SST info UID 2 --------------------- 8\r\n\n"); printf(" Exit TFM Examples Menu --------------------- x\r\n\n"); } /** * @brief Write in SST a TEST UID */ static void sst_set_uid(const UID_t ex_uid) { psa_ps_status_t status; const psa_ps_uid_t uid = (psa_ps_uid_t)ex_uid; const psa_ps_create_flags_t flags = PSA_PS_FLAG_NONE; uint32_t write_len = 0; uint8_t* write_data = NULL; if( ex_uid == UID1 ) { write_data = (uint8_t*)DATA1; write_len = DATA1_SIZE; } else if( ex_uid == UID2 ) { write_data = (uint8_t*)DATA2; write_len = DATA2_SIZE; } else { printf("UNKNOWN UID\r\n"); return; } status = psa_ps_set(uid, write_len, write_data, flags); printf("SST set UID: \"%s\": %s\r\n", write_data, (status == PSA_SUCCESS) ? "SUCCESSFUL" : "FAILED"); } /** * @brief Remove from SST a TEST UID */ static void sst_remove_uid(const UID_t ex_uid) { psa_ps_status_t status; const psa_ps_uid_t uid = (psa_ps_uid_t)ex_uid; status = psa_ps_remove(uid); printf("SST remove UID %s\r\n", (status == PSA_SUCCESS) ? "SUCCESSFUL" : "FAILED"); } /** * @brief Read SST TEST UID and compare its value */ static void sst_read_uid(const UID_t ex_uid) { psa_ps_status_t status; const psa_ps_uid_t uid = (psa_ps_uid_t)ex_uid; uint8_t* expected_data = NULL; uint8_t read_data[100] = { '\0' }; uint32_t read_len = 0; if( ex_uid == UID1 ) { expected_data = (uint8_t*)DATA1; read_len = DATA1_SIZE; } else if( ex_uid == UID2 ) { expected_data = (uint8_t*)DATA2; read_len = DATA2_SIZE; } else { printf("UNKNOWN UID\r\n"); return; } // Read status = psa_ps_get(uid, 0, read_len, read_data); if ((status == PSA_SUCCESS) && (!memcmp(read_data, expected_data, read_len))) { printf("SST read UID: \"%s\" : SUCCESSFUL\r\n", read_data); } else { printf("SST read UID: \"%s\" : FAILED\r\n", read_data); } } /** * @brief Read storage info of a TEST UID */ static void sst_readinfo_uid(const UID_t ex_uid) { psa_ps_status_t status; const psa_ps_uid_t uid = (psa_ps_uid_t)ex_uid; struct psa_ps_info_t info = { 0 }; // Read status = psa_ps_get_info(uid, &info); printf("SST info UID %s\r\n", (status == PSA_SUCCESS) ? "SUCCESSFUL" : "FAILED"); if (status == PSA_SUCCESS) { printf("SST info UID: size = %u, flags = %x\r\n", info.size, info.flags); } } /** * @} */
To run the modified application, and assuming that you already followed all steps presented in the second article, you can execute only the following steps. Otherwise follow all steps presented in the second article.
The non-secure application should display:
====================================================================== = (C) COPYRIGHT 2019 STMicroelectronics = = = = User App #A = ====================================================================== =================== Main Menu ============================ Test Protections -------------------------------------- 1 Test TFM ---------------------------------------------- 2 Test TFM BLOG ----------------------------------------- 4 Selection :
Type 4 to display the different options added for this article:
======================= TFM Examples Menu =========================== TFM - Test SST set UID 1 --------------------- 1 TFM - Test SST read / check UID 1 --------------------- 2 TFM - Test SST remove UID 1 --------------------- 3 TFM - Test SST info UID 1 --------------------- 4 TFM - Test SST set UID 2 --------------------- 5 TFM - Test SST read / check UID 2 --------------------- 6 TFM - Test SST remove UID 2 --------------------- 7 TFM - Test SST info UID 2 --------------------- 8 Exit TFM Examples Menu --------------------- x
Tests 1 to 4 respectively set, read, remove data and get the metadata associated to UID 1 while tests 5 to 8 do the same operations for UID 2.
Lines 23 and 25 of tfm_blog.c define the data that is stored in both storages. DATA1 is stored in the first storage and associated with UID 1
const uint8_t DATA1[] = "HELLO BLOG !";
while DATA2 is stored in the second storage and associated with UID 2
const uint8_t DATA2[] = "HOWTO WRITE AND READ SST !"
The listing below presents a flow of operations and the results:
Theses tests show that several pieces of data can be stored in the Secure Storage, up to the maximum size of the Secure Storage itself. However, as metadata is also stored for each data, the total size of data must be lower than the size of the Secure Storage memory area. It may not be very efficient to store only a few bytes, though.
This article added new features to the TFM demo application by extending the tests on the Secure Storage. The implementation of TFM and the API of the Secure Storage do not completely match but using the existing code as example allows anyway the user to reach its goal.
A (better) documentation of the interprocess communication between the non-secure and secure worlds would definitely be helpful to understand in detail how TFM works.
[…] Focus on the Secure Storage service of Trusted Firmware (TFM) focuses on the Secure Storage service […]