[STM32U5] STM32U5使用TFM的PSA接口技巧和方法

[复制链接]
46|10
renzheshengui 发表于 2026-8-8 18:16 | 显示全部楼层 |阅读模式
PSA 是 Arm 定义的安全接口标准;
TF-M 是 PSA 的官方参考实现;
TrustZone 是运行 TF-M 的硬件基础

在TrustZone中,如果不用TF-M,那NS和S交互基本上就是自定义API实现交互,而TF-M将这些API标准化,统一的一套PSA接口实现,更安全和规范。
当然,如果小项目就几个API,为了代码轻量化直接使用自定义API会更方便,TF-M相对是更复杂一点的。

PSA 标准接口说明
TFM 向非安全侧(NS)暴露以下标准 PSA 接口,均可在 NonSecure 代码中直接调用。

U575 注意: 由于禁用了硬件加速(无 CRYP/PKA/SAES),Crypto 接口中 AES/GCM/CCM/RSA/ECDSA 均回退到 mbedtls 软件实现;SHA256/SHA1/MD5 仍使用 HASH 硬件加速。

1. PSA Crypto
头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/crypto.h

使用前必须先初始化:

#include "psa/crypto.h"

psa_crypto_init();  // 必须在所有 Crypto API 调用前执行


1.1 密钥管理
// 导入持久化 AES 密钥
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_id(&attr, 1);                                  // 持久化 key ID
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attr, 128);
psa_set_key_algorithm(&attr, PSA_ALG_CBC_NO_PADDING);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);

psa_key_id_t key;
const uint8_t key_data[16] = { /* 16字节密钥 */ };
psa_import_key(&attr, key_data, sizeof(key_data), &key);

// 打开/关闭/销毁持久化密钥
psa_open_key(1, &key);
psa_close_key(key);
psa_destroy_key(key);

// 生成随机密钥
psa_generate_key(&attr, &key);

// 导出密钥
uint8_t out[16];
size_t out_len;
psa_export_key(key, out, sizeof(out), &out_len);
psa_export_public_key(key, out, sizeof(out), &out_len);


1.2 哈希(Hash)
// 一次性计算
uint8_t hash[32];
size_t hash_len;
psa_hash_compute(PSA_ALG_SHA_256, input, input_len, hash, sizeof(hash), &hash_len);

// 比较
psa_hash_compare(PSA_ALG_SHA_256, input, input_len, expected_hash, expected_len);

// 多步计算
psa_hash_operation_t op = PSA_HASH_OPERATION_INIT;
psa_hash_setup(&op, PSA_ALG_SHA_256);
psa_hash_update(&op, data, data_len);
psa_hash_finish(&op, hash, sizeof(hash), &hash_len);
// 或 psa_hash_verify(&op, expected_hash, expected_len);
psa_hash_abort(&op);


1.3 对称加密(Cipher)
// 一次性加密(自动生成 IV,IV 附在输出头部)
uint8_t output[32];
size_t output_len;
psa_cipher_encrypt(key, PSA_ALG_CBC_NO_PADDING,
                   plaintext, plaintext_len,
                   output, sizeof(output), &output_len);

// 一次性解密(输入 = IV + 密文)
psa_cipher_decrypt(key, PSA_ALG_CBC_NO_PADDING,
                   input, input_len,
                   output, sizeof(output), &output_len);

// 多步加密
psa_cipher_operation_t op = PSA_CIPHER_OPERATION_INIT;
psa_cipher_encrypt_setup(&op, key, PSA_ALG_CBC_NO_PADDING);
uint8_t iv[16];
size_t iv_len;
psa_cipher_generate_iv(&op, iv, sizeof(iv), &iv_len);
psa_cipher_update(&op, plaintext, plaintext_len, output, sizeof(output), &output_len);
psa_cipher_finish(&op, output, sizeof(output), &output_len);
psa_cipher_abort(&op);



1.4 AEAD(认证加密)
// 一次性 AES-GCM 加密
uint8_t ciphertext[64];
size_t ciphertext_len;
psa_aead_encrypt(key, PSA_ALG_GCM,
                 nonce, nonce_len,
                 aad, aad_len,
                 plaintext, plaintext_len,
                 ciphertext, sizeof(ciphertext), &ciphertext_len);

// 一次性 AES-GCM 解密(含认证)
uint8_t plaintext_out[64];
size_t plaintext_len_out;
psa_aead_decrypt(key, PSA_ALG_GCM,
                 nonce, nonce_len,
                 aad, aad_len,
                 ciphertext, ciphertext_len,
                 plaintext_out, sizeof(plaintext_out), &plaintext_len_out);


1.5 非对称签名
// ECDSA 签名(需先计算 hash)
uint8_t sig[64];
size_t sig_len;
psa_sign_hash(key, PSA_ALG_ECDSA(PSA_ALG_SHA_256),
              hash, hash_len,
              sig, sizeof(sig), &sig_len);

// 验签
psa_verify_hash(key, PSA_ALG_ECDSA(PSA_ALG_SHA_256),
                hash, hash_len,
                sig, sig_len);


1.6 随机数
uint8_t random[32];
psa_generate_random(random, sizeof(random));


2. PSA ITS(Internal Trusted Storage,内部可信存储)
头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/internal_trusted_storage.h

存储在安全侧 Flash,无加密,有完整性保护,掉电不丢失。

#include "psa/internal_trusted_storage.h"

#define MY_UID  1U

// 写入
const uint8_t data[] = "hello ITS";
psa_its_set(MY_UID, sizeof(data), data, PSA_STORAGE_FLAG_NONE);

// 读取
uint8_t buf[32];
size_t read_len;
psa_its_get(MY_UID, 0, sizeof(buf), buf, &read_len);

// 查询元数据(大小、flags)
struct psa_storage_info_t info;
psa_its_get_info(MY_UID, &info);
// info.size: 数据大小
// info.flags: 创建时的 flags

// 删除
psa_its_remove(MY_UID);




可用 flags:

637396a72a83065891.png

3. PSA PS(Protected Storage,受保护存储)
头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/protected_storage.h

相比 ITS 额外提供加密保护和防回滚,适合存储敏感数据。接口与 ITS 基本一致。

#include "psa/protected_storage.h"

#define MY_UID  2U

// 写入
const uint8_t data[] = "TRUSTEDFIRMWARE_FOR_STM32";
psa_ps_set(MY_UID, sizeof(data) - 1, data, PSA_STORAGE_FLAG_NONE);

// 读取
uint8_t buf[32];
size_t read_len;
psa_ps_get(MY_UID, 0, sizeof(buf), buf, &read_len);

// 查询元数据
struct psa_storage_info_t info;
psa_ps_get_info(MY_UID, &info);

// 删除
psa_ps_remove(MY_UID);

// 部分写入(需先 create 预留空间)
psa_ps_create(MY_UID, 128, PSA_STORAGE_FLAG_NONE);
psa_ps_set_extended(MY_UID, 0, 16, partial_data);   // 写偏移 0 处 16 字节
psa_ps_set_extended(MY_UID, 16, 8, more_data);       // 追加写偏移 16 处 8 字节



4. PSA Initial Attestation(初始证明)
头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/initial_attestation.h

用于向远端证明设备身份,返回包含设备信息的 CBOR 格式 EAT token,由设备私钥签名。

#include "psa/initial_attestation.h"

// challenge:32/48/64 字节的 nonce(由服务端提供,防重放)
uint8_t challenge[32] = { /* server nonce */ };
uint8_t token_buf[PSA_INITIAL_ATTEST_MAX_TOKEN_SIZE];
size_t token_size;

psa_status_t status = psa_initial_attest_get_token(
    challenge, sizeof(challenge),
    token_buf, sizeof(token_buf),
    &token_size);

if (status == PSA_SUCCESS) {
    // token_buf[0..token_size-1] 即为 CBOR 编码的 EAT token
    // 发送给服务端验证
}

// 若需动态分配 buffer,先查询 token 大小
size_t needed_size;
psa_initial_attest_get_token_size(sizeof(challenge), &needed_size);



token 中包含的主要 claims:

506566a72a634d5ec7.png

5. PSA Firmware Update(固件升级)
头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/update.h

通过 PSA FWU 接口触发固件升级流程(配合 MCUBoot)。

#include "psa/update.h"

// image_id 构造:slot=STAGE(0x02), type 和 specific_id 按需填写
// 例如 NS App 镜像:
psa_image_id_t image_id = FWU_CALCULATE_IMAGE_ID(
    FWU_IMAGE_ID_SLOT_STAGE, IMAGE_TYPE_NS_APP, 0);

// 1. 分块写入固件到 staging 区(每块 <= PSA_FWU_MAX_BLOCK_SIZE = 1024 字节)
size_t offset = 0;
psa_fwu_write(image_id, offset, block, block_size);
offset += block_size;
// ... 循环写完所有块

// 2. 触发安装(验签、完整性检查)
psa_image_id_t dep_id;
psa_image_version_t dep_ver;
psa_status_t status = psa_fwu_install(image_id, &dep_id, &dep_ver);
// 返回 PSA_SUCCESS_REBOOT 表示需要重启完成安装

// 3. 请求重启
psa_fwu_request_reboot();

// 重启后新固件运行,调用 accept 确认升级成功(否则自动回滚)
psa_fwu_accept();

// 查询镜像状态
psa_image_info_t info;
psa_fwu_query(image_id, &info);
// info.state: PSA_IMAGE_INSTALLED / PSA_IMAGE_PENDING_INSTALL 等
// info.version: 版本号
// info.digest: 镜像摘要

// 中止升级并清除 staging 区
psa_fwu_abort(image_id);



镜像状态值:

6726a72a61d1abf7.png

接口头文件汇总

817576a72a61441ced.png

NS 与 S 侧数据交互方式

262026a72a608ed257.png

1. 通过 ITS 交互
NS 写入数据,S 侧读取(或反向)。数据存储在安全侧 Flash,有完整性保护。

头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/internal_trusted_storage.h

#include "psa/internal_trusted_storage.h"

#define SHARE_UID  10U

/* NS 侧写入 */
uint8_t tx_data[32] = { /* 要共享的数据 */ };
psa_status_t st = psa_its_set(SHARE_UID, sizeof(tx_data), tx_data,
                               PSA_STORAGE_FLAG_NONE);

/* S 侧读取(在 Secure Partition 中) */
uint8_t rx_data[32];
size_t  rx_len;
psa_its_get(SHARE_UID, 0, sizeof(rx_data), rx_data, &rx_len);

/* 用完后删除 */
psa_its_remove(SHARE_UID);



注意: ITS 无加密,任何持有 UID 的分区均可读取,适合非敏感数据。

2. 通过 PS 交互
与 ITS 接口一致,但额外提供加密保护和防回滚,适合存储敏感数据。

头文件: Middlewares/Third_Party/trustedfirmware/interface/include/psa/protected_storage.h

#include "psa/protected_storage.h"

#define SHARE_UID  20U

/* NS 侧写入 */
uint8_t tx_data[32] = { /* 敏感数据 */ };
psa_status_t st = psa_ps_set(SHARE_UID, sizeof(tx_data), tx_data,
                              PSA_STORAGE_FLAG_NONE);

/* S 侧读取 */
uint8_t rx_data[32];
size_t  rx_len;
psa_ps_get(SHARE_UID, 0, sizeof(rx_data), rx_data, &rx_len);

/* 查询是否存在及大小 */
struct psa_storage_info_t info;
psa_ps_get_info(SHARE_UID, &info);  // info.size 为数据长度

/* 删除 */
psa_ps_remove(SHARE_UID);



3. 通过 IPC(APP ROT)交互
同步调用,NS 把数据作为参数直接传入 S 侧处理,S 侧处理完后同步返回结果。
项目中已有完整实现,NS 调用 veneer 函数,S 侧在 app_rot_hal_ioctl 中处理。

3.1 添加自定义命令号
文件: TFM_Appli/Secure_nsclib/platform_ioctl.h

typedef enum {
    PLATFORM_IOTCL_FWSEC_CONFIRM = 0,
    PLATFORM_IOTCL_DATASEC_CONFIRM,
    MY_CMD_PROCESS_DATA,    /* 新增自定义命令 */
} plaform_ioctl_val;



3.2 NS 侧调用
头文件: TFM_Appli/Secure_nsclib/tfm_app_rot_api.h

#include "tfm_app_rot_api.h"
#include "platform_ioctl.h"

/* 准备输入数据 */
uint8_t tx[32] = { /* 要传给 S 侧处理的数据 */ };
psa_invec input = { .base = tx, .len = sizeof(tx) };

/* 准备接收 S 侧返回结果 */
uint8_t rx[32] = {0};
psa_outvec output = { .base = rx, .len = sizeof(rx) };

/* 同步调用,阻塞直到 S 侧处理完毕 */
enum tfm_app_rot_err_t ret = tfm_app_rot_ioctl(
    (tfm_app_rot_ioctl_req_t)MY_CMD_PROCESS_DATA, &input, &output);

if (ret == TFM_APP_ROT_ERR_SUCCESS) {
    /* rx 中已有 S 侧写回的结果 */
}



3.3 S 侧处理
文件: TFM_Appli/Secure/Src/app_rot_test.c,在 app_rot_hal_ioctl 函数中添加分支:

enum tfm_app_rot_err_t app_rot_hal_ioctl(tfm_app_rot_ioctl_req_t request,
                                         psa_invec *in_vec,
                                         psa_outvec *out_vec)
{
    if (request == MY_CMD_PROCESS_DATA) {
        if (!in_vec || in_vec->len == 0)
            return TFM_APP_ROT_ERR_INVALID_PARAM;

        uint8_t *data = (uint8_t *)in_vec->base;
        size_t   len  = in_vec->len;

        /* 在 S 侧处理数据,例如调用 PSA Crypto 加密 */
        /* psa_cipher_encrypt(...) */

        /* 将结果写回 NS */
        if (out_vec && out_vec->base) {
            memcpy(out_vec->base, result, result_len);
            out_vec->len = result_len;
        }
        return TFM_APP_ROT_ERR_SUCCESS;
    }

    /* 其他已有命令处理 ... */
}



3.4 缓冲区大小限制
单次 IPC 调用的数据大小受 tfm_app_rot.c 中宏限制,默认各 64 字节:

文件: TFM_Appli/Secure/Src/tfm_app_rot.c

#define INPUT_BUFFER_SIZE  64   /* NS → S 最大字节数,按需调大 */
#define OUTPUT_BUFFER_SIZE 64   /* S → NS 最大字节数,按需调大 */



IPC 测试用例
在主菜单新增 case 5 用于测试 NS → S → NS 的 IPC 数据交互,验证双向数据传输是否正常。

修改文件
1. TFM_Appli/Secure_nsclib/platform_ioctl.h
新增命令号 PLATFORM_IOCTL_IPC_TEST:

typedef enum {
    PLATFORM_IOTCL_FWSEC_CONFIRM = 0,
    PLATFORM_IOTCL_DATASEC_CONFIRM,
    PLATFORM_IOCTL_IPC_TEST,       /* 新增:IPC 测试命令 */
    ...
} plaform_ioctl_val;



2. TFM_Appli/Secure/Src/app_rot_test.c
在 app_rot_hal_ioctl 中新增处理分支,将 NS 传入的每个字节 +1 后返回:

if (request == PLATFORM_IOCTL_IPC_TEST) {
    uint8_t *in  = (uint8_t *)in_vec->base;
    uint8_t *out = (uint8_t *)out_vec->base;
    for (size_t i = 0; i < in_vec->len; i++)
        out = in + 1;
    out_vec->len = in_vec->len;
    return TFM_APP_ROT_ERR_SUCCESS;
}



3. TFM_Appli/NonSecure/Src/main.c
新增头文件引用、菜单项和 case '5' 处理:

// 新增头文件
#include "tfm_app_rot_api.h"
#include "platform_ioctl.h"

// FW_APP_PrintMainMenu 新增
printf("  Test IPC ---------------------------------------------- 5\r\n\n");

// FW_APP_Run 新增 case '5'
case '5':
{
    uint8_t tx[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
    uint8_t rx[8] = {0};
    psa_invec  input  = { .base = tx, .len = sizeof(tx) };
    psa_outvec output = { .base = rx, .len = sizeof(rx) };
    enum tfm_app_rot_err_t ret = tfm_app_rot_ioctl(
        (tfm_app_rot_ioctl_req_t)PLATFORM_IOCTL_IPC_TEST, &input, &output);
    if (ret == TFM_APP_ROT_ERR_SUCCESS) {
        printf("IPC test PASSED\r\n");
        printf("  TX: ");
        for (int i = 0; i < 8; i++) printf("%02x ", tx);
        printf("\r\n  RX: ");
        for (int i = 0; i < 8; i++) printf("%02x ", rx);
        printf("\r\n");
    } else {
        printf("IPC test FAILED (err=%d)\r\n", (int)ret);
    }
    break;
}



串口输出
======================================================================
=              (C) COPYRIGHT 2021 STMicroelectronics                 =
=                                                                    =
=                          User App #B                               =
======================================================================


=================== Main Menu ============================

  Test Protections -------------------------------------- 1

  Test TFM ---------------------------------------------- 2

  New Fw Image ------------------------------------------ 3

  Non-Secure Data --------------------------------------- 4

  Test IPC ---------------------------------------------- 5

  Selection :

IPC test PASSED
  TX: 01 02 03 04 05 06 07 08
  RX: 02 03 04 05 06 07 08 09



另外在例程中有大量的TFM接口用法参考

=================== Main Menu ============================

  Test Protections -------------------------------------- 1

  Test TFM ---------------------------------------------- 2

  New Fw Image ------------------------------------------ 3

  Non-Secure Data --------------------------------------- 4

  Test IPC ---------------------------------------------- 5

  Selection :


======================= TFM Examples Menu ===========================

  TFM - Test All                                   --------------------- 0

  TFM - Test AES-GCM                               --------------------- 1

  TFM - Test AES-CBC                               --------------------- 2

  TFM - Test AES-CCM                               --------------------- 3

  TFM - Test PS set UID                            --------------------- 4

  TFM - Test PS read / check UID                   --------------------- 5

  TFM - Test PS remove UID                         --------------------- 6

  TFM - Test EAT                                   --------------------- 7

  TFM - Test ITS set UID                           --------------------- 8

  TFM - Test ITS read / check UID                  --------------------- 9

  TFM - Test ITS remove UID                        --------------------- a

  TFM - Test SHA224                                --------------------- b

  TFM - Test SHA256                                --------------------- c

  TFM - Test Persistent key import                 --------------------- d

  TFM - Test Persistent key export                 --------------------- e

  TFM - Test Persistent key destroy                --------------------- f

  Exit TFM Examples Menu                           --------------------- x




————————————————
版权声明:本文为CSDN博主「IIIIllllIII」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012988644/article/details/163049092

公羊子丹 发表于 2026-8-10 21:06 | 显示全部楼层
前段时间调U575的TFM踩到大坑,它砍掉CRYP硬件加速,RSA、AES-GCM全靠mbedtls软件跑,加密耗时直接翻倍,大批量证书验签建议拆分任务,别阻塞主业务流程。
周半梅 发表于 2026-8-10 21:07 | 显示全部楼层
想问下楼主,自定义IPC传输大块数据时,除了改输入输出buffer宏,还要调整TFM分区栈大小吗?我传超过64字节数据直接返回参数错误,一直没找到完整配置步骤。
帛灿灿 发表于 2026-8-10 21:07 | 显示全部楼层
TFM标准化接口看着规范,小体量项目属实累赘!单纯存几组设备密钥,自己写极简NS/S交互都比移植整套TFM省事,编译后固件体积直接少十几KB。
童雨竹 发表于 2026-8-10 21:08 | 显示全部楼层
新人上手先跑串口自带的TFM测试菜单,挨个测ITS、PS、AES例程验证底层正常。先别着急写自定义IPC,底层接口跑通再拓展双向数据交互逻辑。
万图 发表于 2026-8-10 21:09 | 显示全部楼层
非敏感共享数据用ITS交互足够,密钥、证书这类核心数据一定要用PS,自带防回滚和加密。ITS仅做完整性校验,固件回退后存在密钥泄露风险。
Wordsworth 发表于 2026-8-10 21:10 | 显示全部楼层
之前调用psa_crypto_init放在中断里执行,频繁触发安全fault。记住加密初始化必须放在NS侧主循环开头,所有PSA Crypto接口都不能在中断上下文调用。
Bblythe 发表于 2026-8-10 21:12 | 显示全部楼层
TFM依靠TrustZone硬件隔离安全域,所有密钥、加解密运算都在S侧运行,NS侧拿不到原始密钥。相比自研隔离方案,这套PSA接口能快速过物联网安全认证。
Pulitzer 发表于 2026-8-10 21:13 | 显示全部楼层
楼主这套TFM+PSA方案适配低功耗电池设备吗?我做便携智能锁,频繁调用PS读写密钥,担心安全分区Flash擦写损耗过快,有没有均衡读写的优化手段?
Uriah 发表于 2026-8-10 21:14 | 显示全部楼层
编译前记得更新STM配套TFM源码包,旧版本存在U5系列内存分区适配bug。调试fault问题开启安全侧日志打印,能快速定位IPC传参、密钥操作报错点。
Clyde011 发表于 2026-8-10 21:15 | 显示全部楼层
频繁读写PS可增加一层NS侧RAM缓存,减少Flash擦写次数;大文件固件升级拆分1024字节分块写入,升级完成调用psa_fwu_accept,避免意外断电触发镜像回滚。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

145

主题

4630

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部
0