1. 课程导入

在移动支付、数字版权、生物特征认证与车联网等场景中,安全敏感数据必须在“可信执行环境”中处理。OP-TEE 作为开源可信执行环境(TEE)的典型实现,运行在 ARM TrustZone 的安全世界(Secure World),与普通世界(Normal World)的 Linux 用户态应用形成隔离。Trusted Application(TA)就是运行在 OP-TEE 安全世界中的可信应用,它通过 Client Application(CA)与普通世界交互,完成密钥管理、加解密、安全存储与身份认证等任务。本课程的目标,是让学习者从零建立“CA—TEE 通信—TA—安全服务”的完整开发能力。

一、学习目标:从会用到会写

本课程不是单纯讲解 OP-TEE 的安装命令,而是围绕“可交付的 TA 工程”组织内容。完成学习后,应达到以下目标:

  • 理解 TrustZone 双世界模型、OP-TEE 的启动链路与运行时组成,能说清 CA、TA、TEE Supplicant、OP-TEE OS 之间的职责边界。
  • 掌握 TA 的两种形态:早期动态 TA(Dynamic TA)与伪 TA(Pseudo TA),理解它们与 CA 的匹配方式及 UUID 的作用。
  • 能独立编写 TA 的入口函数、命令分发逻辑与安全存储调用,并完成 CA 侧的会话建立、参数传递与调用。
  • 能使用 OP-TEE 提供的构建系统(如 xtest、optee_examples)编译、签名、部署 TA,并在 QEMU 或开发板上验证。
  • 具备基本的安全编码意识:参数校验、共享内存边界检查、密钥不落普通世界、错误码规范返回。

二、核心概念:先建立一张“世界地图”

学习 OP-TEE TA 开发,最容易混淆的是“谁在哪个世界、谁调用谁”。可以用一句话概括:CA 在普通世界发起调用,OP-TEE 驱动把请求带入安全世界,TA 在安全世界执行并返回结果。其中几个关键概念必须提前厘清:

  • TrustZone:ARM 提供的硬件安全隔离机制,将 CPU 划分为 Secure World 与 Normal World,通过 SMC 指令切换。
  • OP-TEE OS:运行在安全世界的轻量级可信操作系统,负责调度 TA、管理安全内存与外设。
  • TA:可信应用,运行在 OP-TEE OS 之上,拥有独立 UUID 与命令空间。
  • CA:普通世界的客户端应用,通过 libteec 调用 TEE 驱动,与指定 UUID 的 TA 建立会话。
  • TEE Supplicant:普通世界的守护进程,协助 OP-TEE 完成 REE 文件系统访问、加载 TA 镜像等操作。
  • 共享内存:CA 与 TA 之间传递大块数据的通道,必须由 CA 注册、TA 映射,并严格校验长度与方向。

记忆要点:CA 不认识 TA 的函数名,只认识 UUID 和命令 ID;TA 不直接访问普通世界内存,只访问被显式注册的共享内存。

三、整体知识框架:一张图看清调用链

下图给出本课程后续章节反复使用的核心调用链。它把普通世界与安全世界、用户态与内核态、CA 与 TA 的关系压缩到一条路径上,便于在后续编码时随时定位“问题出在哪一层”。

Normal World(普通世界 / REE) CA 客户端应用 libteec / TEEC_Invoke UUID + Command ID TEE 驱动 /dev/tee0 SMC 陷入安全世界 TEE Supplicant 加载 TA 镜像 访问 REE 文件系统 共享内存 TEEC_Register TEEC_Allocate Secure World(安全世界 / TEE) OP-TEE OS 调度 / 内存隔离 SMC 处理入口 TA 可信应用 TA_InvokeCommandEntryPoint 命令分发 / 参数校验 安全服务 安全存储 / 加解密 密钥管理 / 随机数 硬件 TrustZone Crypto 引擎 SMC 切换

这张图同时给出了本课程的知识分层:普通世界侧关注 CA 编程与驱动接口;安全世界侧关注 TA 入口、命令分发与安全服务;跨世界关注共享内存与 SMC 切换;工程侧关注构建、签名与部署。

四、方法流程:一个 TA 从编写到运行的闭环

后续章节会反复走同一条闭环。建议先记住流程,再深入每个环节的细节:

  1. 定义 TA 的 UUID 与命令 ID,明确 CA 与 TA 的接口契约。
  2. 编写 TA 侧代码:实现 TA_CreateEntryPoint、TA_OpenSessionEntryPoint、TA_InvokeCommandEntryPoint、TA_CloseSessionEntryPoint、TA_DestroyEntryPoint。
  3. 编写 CA 侧代码:TEEC_InitializeContext、TEEC_OpenSession、TEEC_InvokeCommand、TEEC_CloseSession、TEEC_FinalizeContext。
  4. 配置构建:在 Makefile 中声明 TA 的 UUID、源文件、依赖库与签名密钥。
  5. 编译与签名:生成 .ta 镜像与 CA 可执行文件,必要时使用开发密钥签名。
  6. 部署与验证:将 TA 放入 /lib/optee_armtz,运行 CA,观察返回值与 OP-TEE 日志。
/* TA 侧命令分发骨架 */
TEE_Result TA_InvokeCommandEntryPoint(void *sess,
                                      uint32_t cmd,
                                      uint32_t ptypes,
                                      TEE_Param params[4])
{
    switch (cmd) {
    case TA_MY_CMD_ENCRYPT:
        return encrypt_data(ptypes, params);
    case TA_MY_CMD_GET_KEY:
        return get_secure_key(ptypes, params);
    default:
        return TEE_ERROR_NOT_SUPPORTED;
    }
}

五、案例与场景:安全存储小工具

课程会以一个“安全存储小工具”为主线案例:CA 请求写入一段敏感配置,TA 将其加密后存入安全存储;CA 再次请求读取时,TA 解密并返回。这个案例虽小,却覆盖了 UUID 匹配、会话建立、命令分发、共享内存、安全存储与错误码返回等全部核心机制。它也是排查问题的良好载体:如果 CA 报 TEEC_ERROR_ITEM_NOT_FOUND,通常说明 TA 未部署或 UUID 不匹配;如果 TA 返回 TEE_ERROR_BAD_PARAMETERS,则要检查参数类型与长度校验。

学习阶段 关键问题 对应产出
概念建立 CA、TA、OP-TEE OS 各自运行在哪里? 能画出调用链与职责边界
接口设计 UUID、命令 ID、参数类型如何约定? 一份 CA/TA 接口契约
编码实现 入口函数与命令分发如何写? 可编译的 TA 与 CA 源码
构建部署 TA 如何签名并放到正确路径? 可运行的 .ta 与 CA 程序
调试验证 返回值与日志如何定位问题? 可复现的测试记录

六、常见误区与复盘建议

误区一:把 TA 当成普通动态库,认为 CA 可以直接调用 TA 的函数。实际上 CA 只能通过 UUID 和命令 ID 发起会话与调用,函数名对 CA 不可见。

误区二:忽略参数校验。TA 运行在安全世界,但传入的参数来自普通世界,必须校验类型、长度与方向,避免越界或类型混淆。

误区三:把密钥写在 CA 侧或普通世界文件中。密钥应留在 TA 或安全存储中,普通世界只传递密文或句柄。

复盘建议:每完成一个 TA,先回答三个问题——UUID 是否唯一且匹配?命令 ID 是否在 CA 与 TA 两侧一致?共享内存是否在调用前正确注册并在调用后释放?这三问能排除大部分入门阶段的问题。

本课程后续将沿着“环境与工具链 → TA/CA 接口设计 → 安全存储与加解密 → 构建签名与部署 → 调试与安全加固”的路径展开。学习时建议边看边动手:先跑通官方示例,再替换为自己的 UUID 与命令,最后加入安全存储与异常处理。只有把调用链真正跑通,OP-TEE TA 开发才算入门。