4. TEE 接口基础:Client API 与 Internal API

这一章我们聊聊 TEE 的接口。说白了,就是搞清楚两件事:普通世界(REE)怎么跟 TEE 打招呼,以及 可信应用(TA)内部怎么干活。

我刚开始接触 TEE 时,最困惑的就是这两个 API 的边界。Client API 是 REE 侧调用的,Internal API 是 TA 侧调用的。搞混了,编译都过不去。咱们今天就把它们彻底理清。

核心认知: Client API 是“门外汉”的敲门砖,Internal API 是“内部人”的工具箱。

4.1 TEE Client API:REE 侧的操作入口

Client API 定义在 GlobalPlatform 的 TEE Client API 规范里。它提供了一组 C 语言函数,让普通世界的应用(CA,Client Application)能够与 TEE 里的可信应用(TA)通信。

常用的就三个函数:TEEC_InitializeContext、TEEC_OpenSession、TEEC_InvokeCommand。嗯,还有一个 TEEC_CloseSession 和 TEEC_FinalizeContext,但核心流程就是前面三个。

4.1.1 TEEC_InitializeContext:建立连接

这是第一步。CA 想要跟 TEE 说话,得先初始化一个上下文(Context)。你可以把它理解成“拨号”——先让手机连上基站。

TEEC_Result TEEC_InitializeContext(
    const char* name,
    TEEC_Context* context
);
  • name:实现特定的上下文名称。通常传 NULL 或设备路径,比如 "/dev/tee0"。
  • context:输出参数,初始化成功后得到一个上下文句柄。
我的经验: 我在某款 SoC 上遇到过 TEEC_InitializeContext 返回 TEEC_ERROR_ACCESS_DENIED。查了半天,原来是 SELinux 策略没放行。记住,TEE 通信涉及跨世界访问,权限问题很常见。

4.1.2 TEEC_OpenSession:打开会话

上下文建立后,CA 需要打开一个会话(Session),才能与特定的 TA 通信。会话是逻辑通道,一个 CA 可以同时打开多个会话,连接不同的 TA。

TEEC_Result TEEC_OpenSession(
    TEEC_Context* context,
    TEEC_Session* session,
    const TEEC_UUID* destination,
    uint32_t connectionMethod,
    const void* connectionData,
    TEEC_Operation* operation,
    uint32_t* returnOrigin
);

参数里最重要的就是 destination——TA 的 UUID。每个 TA 都有一个唯一的 UUID,就像身份证号。CA 通过 UUID 找到并加载对应的 TA。

参数 说明 常见取值
context 已初始化的上下文 由 TEEC_InitializeContext 获得
session 输出,会话句柄 —
destination 目标 TA 的 UUID 如 {0x12345678,...}
connectionMethod 连接方法 TEEC_LOGIN_PUBLIC 最常用
operation 传递参数和共享内存 可为 NULL
returnOrigin 错误来源指示 调试用
注意: connectionMethod 决定了 TA 如何验证 CA 的身份。我见过有人图省事全用 TEEC_LOGIN_PUBLIC,结果安全审计没过。生产环境一定要根据安全策略选择合适的登录方法。

4.1.3 TEEC_InvokeCommand:执行命令

会话建立后,CA 就可以调用 TA 里的具体功能了。每个 TA 会暴露一组命令 ID,CA 通过 TEEC_InvokeCommand 触发对应的处理函数。

TEEC_Result TEEC_InvokeCommand(
    TEEC_Session* session,
    uint32_t commandID,
    TEEC_Operation* operation,
    uint32_t* returnOrigin
);

commandID 是 TA 内部定义的,比如 CMD_ENCRYPT、CMD_DECRYPT。参数通过 TEEC_Operation 结构体传递,支持输入、输出以及共享内存引用。

避坑指南: 我曾经在 TEEC_Operation 里传了一个很大的缓冲区,结果 TEEC_InvokeCommand 返回 TEEC_ERROR_OUT_OF_MEMORY。TEE 侧的内存很有限,大块数据要通过共享内存传递,别直接塞在参数里。

4.2 TEE Internal API:TA 内部的工具箱

Internal API 是 TA 开发者直接使用的接口。它提供了密码学、安全存储、时间、算术等基础能力。说白了,TA 里不能直接调用 Linux 的 openssl 或 fopen,得用 Internal API 提供的替代方案。

4.2.1 密码学 API

这是 Internal API 里最常用的部分。TEE 要求所有密码学操作必须在安全世界内完成,密钥不能泄露到 REE。

  • TEE_AllocateOperation:分配一个密码学操作句柄,指定算法(如 TEE_ALG_AES_CBC_PKCS7)。
  • TEE_SetOperationKey:设置密钥。密钥可以是从安全存储加载的,也可以是临时生成的。
  • TEE_CipherInit / TEE_CipherUpdate / TEE_CipherDoFinal:对称加解密的三步曲。
  • TEE_GenerateRandom:生成真随机数。这个比 REE 侧的 /dev/random 更可靠。
// 示例:AES 加密
TEE_OperationHandle op = NULL;
TEE_AllocateOperation(&op, TEE_ALG_AES_CBC_PKCS7, TEE_MODE_ENCRYPT, 256);
TEE_SetOperationKey(op, keyHandle);
TEE_CipherInit(op, iv, sizeof(iv));
TEE_CipherUpdate(op, plaintext, plaintextLen, ciphertext, &ciphertextLen);
TEE_CipherDoFinal(op, NULL, 0, ciphertext + ciphertextLen, &lastBlockLen);
TEE_FreeOperation(op);
个人习惯: 我写 TA 时,每次用完密码学操作都会调用 TEE_FreeOperation。TEE 侧内存泄漏很难排查,而且一旦泄漏,整个 TA 可能被 OOM 杀掉。

4.2.2 安全存储 API

TA 需要持久化数据时,不能写文件系统。得用 TEE_OpenPersistentObject、TEE_WriteObjectData、TEE_ReadObjectData 等接口。这些数据会被加密存储在 REE 的文件系统中,但密钥由 TEE 管理,REE 无法解密。

函数 功能 类比
TEE_CreatePersistentObject 创建安全存储对象 类似 fopen + fwrite
TEE_OpenPersistentObject 打开已有对象 类似 fopen
TEE_ReadObjectData 读取数据 类似 fread
TEE_WriteObjectData 写入数据 类似 fwrite
TEE_CloseAndDeletePersistentObject 关闭并删除 类似 fclose + unlink

4.2.3 其他重要 API

  • TEE_GetTime:获取安全时间。注意,这个时间来自 TEE 的 RTC,不一定与 REE 同步。
  • TEE_BigInt:大数运算,用于 RSA、ECC 等非对称密码学。
  • TEE_CheckMemoryAccessRights:检查共享内存的访问权限,确保 TA 不会意外访问非法地址。

4.3 整体调用流程

把 Client API 和 Internal API 串起来,一个典型的 TEE 调用流程是这样的:

  1. CA 调用 TEEC_InitializeContext 初始化上下文。
  2. CA 调用 TEEC_OpenSession,传入 TA 的 UUID,TEE 加载并启动 TA。
  3. CA 调用 TEEC_InvokeCommand,传入命令 ID 和参数。
  4. TEE 将请求转发给 TA,TA 内部使用 Internal API 处理(加解密、存储等)。
  5. TA 返回结果,TEE 将结果传回 CA。
  6. CA 调用 TEEC_CloseSession 关闭会话,TEEC_FinalizeContext 释放上下文。
TEE 调用流程:Client API → Internal API REE(普通世界) TEE(安全世界) CA(Client Application) TEEC_InitializeContext TEEC_OpenSession TEEC_InvokeCommand TEEC_CloseSession TEEC_FinalizeContext TA(Trusted Application) Internal API 处理 密码学 / 安全存储 时间 / 大数运算 返回结果 命令 + 参数 结果返回

4.4 共享内存与参数传递

REE 和 TEE 是两个隔离的世界,不能直接访问对方的地址空间。数据传递靠共享内存。Client API 提供了 TEEC_RegisterSharedMemory 和 TEEC_AllocateSharedMemory 来管理共享缓冲区。

你想想看,如果 CA 把一个指针直接传给 TA,TA 根本解引用不了——地址空间都不一样。所以必须通过共享内存,由 TEE 的驱动负责映射。

我曾经踩过的坑: 共享内存的缓存一致性问题。在 ARM 平台上,如果 REE 侧写入了共享内存,但 cache 没刷,TA 读到的是脏数据。后来我强制在共享内存区域使用非缓存(non-cacheable)映射才解决。

4.5 小结

这一章我们拆解了 TEE 的两大接口体系。Client API 是 CA 与 TEE 通信的桥梁,核心就是初始化、开会话、调命令这三板斧。Internal API 是 TA 的生存工具,密码学、安全存储、时间等功能一个都不能少。