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:
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:
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);
镜像状态值:
接口头文件汇总
NS 与 S 侧数据交互方式
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
|
|