跳到主要内容

OP-TEE SPI FS

本文档面向使用 MTT E300 BSP/SDK 的用户,介绍 OP-TEE SPI FS(SPI Flash 安全存储)的功能定位、架构关系、使用方式和注意事项。读者应具备 OP-TEE TA 开发和 secure storage 基础。

1. 背景

OP-TEE secure storage 用于为 TA(Trusted Application)保存 persistent object,常见后端包括 REE FS 和 RPMB FS。REE FS 数据保存在 Normal World 文件系统中,RPMB FS 依赖具备 RPMB 特性的存储设备。

SPI FS 基于平台已有 SPI Flash 区域提供 OP-TEE secure storage 能力,使 TA 可以继续使用标准 OP-TEE Internal API 访问 persistent object,同时由 OP-TEE 负责数据加密和文件系统管理。它适用于需要将少量 TA 私有数据保存到 SPI Flash,并希望避免直接暴露在 REE 文件系统中的场景。

2. 功能概述

SPI FS 是 OP-TEE OS 中的 SPI Flash secure storage 后端。它与 REE FS、RPMB FS 一样,实现 OP-TEE 内部的 tee_file_operations 文件操作接口,对上层 TA 暴露统一的 persistent object 访问能力。

TA 使用 SPI FS 时,不需要直接操作 SPI Flash,也不需要关心底层 FAT entry、文件块或加密细节。TA 仍通过 OP-TEE Internal API 创建、打开、读取、写入、截断、删除、重命名和遍历 persistent object。OP-TEE 在内部完成对象到 SPI FS 文件的映射,并将文件数据加密后写入 SPI Flash。

SPI FS 支持的主要能力包括:

  • 通过 OP-TEE secure storage API 保存 TA 私有数据;
  • 将 persistent object 数据以密文形式写入 SPI Flash;
  • 使用文件级 FEK(File Encryption Key)保护不同对象的数据;
  • 通过专用 storage ID(TEE_STORAGE_PRIVATE_SPI)选择 SPI FS 后端;
  • 通过平台固件接口访问 SPI Flash 区域。

3. 方案架构

SPI FS 位于 OP-TEE secure storage 层下方,向上承接 TA 的 persistent object 请求,向下通过平台固件接口访问 SPI Flash。

+--------------------------------------------------+
| TA |
| TEE_CreatePersistentObject() |
| TEE_OpenPersistentObject() |
| TEE_ReadObjectData() / TEE_WriteObjectData() |
+---------------------------+----------------------+
|
+---------------------------v----------------------+
| OP-TEE secure storage layer |
| storage ID -> tee_file_operations |
+---------------------------+----------------------+
|
+---------------------------v----------------------+
| SPI FS |
| FAT management / file operations / encryption |
+---------------------------+----------------------+
|
+---------------------------v----------------------+
| Platform firmware (SMC) interface |
| SPI FS info / SPI Flash read / SPI Flash write |
+---------------------------+----------------------+
|
+---------------------------v----------------------+
| SPI Flash |
| SPI FS reserved region |
+--------------------------------------------------+

OP-TEE 访问外设通常可以通过 RPC 交给 Normal World 的 tee-supplicant 和驱动完成。SPI FS 当前实现选择通过平台固件接口访问 SPI Flash,适合由 OP-TEE 通过受控接口读写有限容量的安全存储数据。

4. 存储格式与加密机制

SPI FS 在 SPI Flash 的预留区域中维护文件系统元数据和加密后的文件数据。整体布局可以概括为:

SPI FS reserved region

+--------------------------------------------------+
| Partition metadata |
| - magic |
| - fs_version |
| - FAT start address |
+--------------------------------------------------+
| FAT area |
| - FAT entries |
+--------------------------------------------------+
| File data area |
| - encrypted file blocks |
+--------------------------------------------------+

每个 persistent object 在 SPI FS 中对应一个文件。FAT entry 用于描述文件的数据位置、数据大小、状态、文件名以及加密相关信息。文件数据区域保存的是加密后的数据块,而不是 TA 写入的明文内容。

SPI FS 复用 OP-TEE secure storage 的密钥管理机制。创建文件时,OP-TEE 为文件数据准备 FEK,并将 FEK 加密后保存在对应文件的 FAT entry 中。后续读写文件时,OP-TEE 使用对应 FEK 对文件数据进行加解密。

5. 使用方式

SPI FS 对 TA 的使用方式与其他 OP-TEE secure storage 后端保持一致。TA 通过 OP-TEE Internal API 操作 persistent object,只需在选择存储后端时使用 TEE_STORAGE_PRIVATE_SPI,不要依赖 TEE_STORAGE_PRIVATE 的默认后端选择顺序。

下面示例展示 TA 侧如何创建对象并写入数据。object ID 和数据由 CA 通过 TEE_Param 传入,TA 使用 TEE_STORAGE_PRIVATE_SPI 将对象保存到 SPI FS。

static TEE_Result create_spi_fs_object(uint32_t param_types, TEE_Param params[4])
{
const uint32_t exp_param_types =
TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
TEE_ObjectHandle object;
TEE_Result res;
char *obj_id;
size_t obj_id_sz;
char *data;
size_t data_sz;
uint32_t flags;

if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;

obj_id_sz = params[0].memref.size;
obj_id = TEE_Malloc(obj_id_sz, 0);
if (!obj_id)
return TEE_ERROR_OUT_OF_MEMORY;
TEE_MemMove(obj_id, params[0].memref.buffer, obj_id_sz);

data_sz = params[1].memref.size;
data = TEE_Malloc(data_sz, 0);
if (!data) {
TEE_Free(obj_id);
return TEE_ERROR_OUT_OF_MEMORY;
}
TEE_MemMove(data, params[1].memref.buffer, data_sz);

flags = TEE_DATA_FLAG_ACCESS_READ |
TEE_DATA_FLAG_ACCESS_WRITE |
TEE_DATA_FLAG_ACCESS_WRITE_META |
TEE_DATA_FLAG_OVERWRITE;

res = TEE_CreatePersistentObject(TEE_STORAGE_PRIVATE_SPI,
obj_id, obj_id_sz,
flags,
TEE_HANDLE_NULL,
NULL, 0,
&object);
if (res != TEE_SUCCESS)
goto out;

res = TEE_WriteObjectData(object, data, data_sz);
if (res != TEE_SUCCESS)
TEE_CloseAndDeletePersistentObject1(object);
else
TEE_CloseObject(object);

out:
TEE_Free(obj_id);
TEE_Free(data);
return res;
}

读取对象时,可使用 TEE_OpenPersistentObject() 打开同一 object ID,并先通过 TEE_GetObjectInfo1() 获取对象大小。下面示例中,params[1].memref.size 作为输入表示输出缓冲区容量,作为输出表示实际读取字节数;如果缓冲区容量不足,函数会返回 TEE_ERROR_SHORT_BUFFER,并通过 params[1].memref.size 返回所需大小。

static TEE_Result read_spi_fs_object(uint32_t param_types, TEE_Param params[4])
{
const uint32_t exp_param_types =
TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INPUT,
TEE_PARAM_TYPE_MEMREF_OUTPUT,
TEE_PARAM_TYPE_NONE,
TEE_PARAM_TYPE_NONE);
TEE_ObjectHandle object;
TEE_ObjectInfo object_info;
TEE_Result res;
uint32_t read_bytes;
char *obj_id;
size_t obj_id_sz;
char *data;
size_t data_sz;

if (param_types != exp_param_types)
return TEE_ERROR_BAD_PARAMETERS;

obj_id_sz = params[0].memref.size;
obj_id = TEE_Malloc(obj_id_sz, 0);
if (!obj_id)
return TEE_ERROR_OUT_OF_MEMORY;
TEE_MemMove(obj_id, params[0].memref.buffer, obj_id_sz);

data_sz = params[1].memref.size;
data = TEE_Malloc(data_sz, 0);
if (!data) {
TEE_Free(obj_id);
return TEE_ERROR_OUT_OF_MEMORY;
}

res = TEE_OpenPersistentObject(TEE_STORAGE_PRIVATE_SPI,
obj_id, obj_id_sz,
TEE_DATA_FLAG_ACCESS_READ |
TEE_DATA_FLAG_SHARE_READ,
&object);
if (res != TEE_SUCCESS)
goto out_free;

res = TEE_GetObjectInfo1(object, &object_info);
if (res != TEE_SUCCESS)
goto out_close;

if (object_info.dataSize > data_sz) {
params[1].memref.size = object_info.dataSize;
res = TEE_ERROR_SHORT_BUFFER;
goto out_close;
}

res = TEE_ReadObjectData(object, data, object_info.dataSize,
&read_bytes);
if (res != TEE_SUCCESS)
goto out_close;
if (read_bytes != object_info.dataSize) {
res = TEE_ERROR_CORRUPT_OBJECT;
goto out_close;
}

TEE_MemMove(params[1].memref.buffer, data, read_bytes);
params[1].memref.size = read_bytes;

out_close:
TEE_CloseObject(object);
out_free:
TEE_Free(obj_id);
TEE_Free(data);
return res;
}

CA 侧仍按普通 TA 调用流程打开 session 并调用 TA command,不需要直接访问 SPI Flash。

6. 限制与注意事项

注意

SPI Flash 通常不具备 RPMB 的硬件认证、防回滚和写保护语义。SPI FS 可以保护落盘数据不以明文形式暴露,但不能把普通 Flash 提升为 RPMB 等价安全级别。

  • SPI FS 预留区域不能与 bootloader、固件镜像、环境变量、升级分区或其他文件系统重叠。区域规划错误可能导致安全存储数据被覆盖,或覆盖其他固件数据。
  • SPI FS 数据对 TA 和普通 REE 文件系统是隔离的,但如果平台存在可以直接擦写 SPI Flash 对应区域的固件命令或驱动接口,仍可能破坏 SPI FS 数据。因此需要在系统集成时限制相关访问路径。