2、TEE接口标准概览:GP TEE Internal API、GP TEE Client API、TEE Socket API、TEE存储API

做TEE开发这些年,我接触过不少接口标准。说实话,最绕不开的就是GlobalPlatform(GP)定义的那一套。你想想看,不同厂商的TEE实现千差万别,但上层应用总不能为每个平台重写一遍吧?GP标准就是来解决这个问题的。

这一章,我把TEE开发中最重要的四类接口给你捋一遍。不是照搬规范文档,而是结合我实际踩过的坑,告诉你每个接口是干什么的、什么时候用、容易出什么问题。

2.1 GP TEE Internal API — TA的“操作系统”

Internal API,说白了就是TEE内部的可信应用(TA)能调用的系统函数。它定义了TA怎么跟TEE内核交互,怎么管理内存、操作密码学算法、处理安全存储。

我个人习惯把Internal API分成几类来记:

  • 基础服务类:TA的入口函数(TA_CreateEntryPoint、TA_OpenSessionEntryPoint等)、内存分配、属性获取
  • 密码学类:对称/非对称加解密、哈希、签名验签、随机数生成
  • 安全存储类:持久化数据的读写(后面会单独讲存储API)
  • 时间与计时器:获取安全时间、设置定时器
  • 通信类:TA与CA通信、TA与TA通信

举个例子,你在TA里要做一次AES加密,代码大概长这样:

TEE_OperationHandle op = NULL;
TEE_ObjectHandle key = NULL;
TEE_Result res;

// 分配操作句柄
res = TEE_AllocateOperation(&op, TEE_ALG_AES_CBC_NOPAD, TEE_MODE_ENCRYPT, 256);
if (res != TEE_SUCCESS) {
    /* 处理错误 */
}

// 设置密钥(从安全存储中加载)
res = TEE_SetOperationKey(op, key);
if (res != TEE_SUCCESS) {
    TEE_FreeOperation(op);
    /* 处理错误 */
}

// 执行加密
res = TEE_CipherInit(op, iv, iv_len);
res = TEE_CipherUpdate(op, plaintext, plaintext_len, ciphertext, &ciphertext_len);
res = TEE_CipherDoFinal(op, last_block, last_block_len, last_cipher, &last_cipher_len);
注意:Internal API的很多函数会返回错误码,但有些错误码在不同厂商的TEE里含义不完全一样。我曾经在迁移一个TA时,发现某个平台把TEE_ERROR_OUT_OF_MEMORY当成了TEE_ERROR_SECURITY,排查了两天才找到原因。所以,不要假设错误码行为是跨平台一致的。

2.2 GP TEE Client API — CA怎么“敲门”进TEE

Client API是运行在REE(普通世界)的客户端应用(CA)用来跟TEE通信的接口。它定义了CA如何打开TEE会话、调用TA的命令、传递参数。

核心流程其实就四步:

  1. 初始化上下文:TEEC_InitializeContext()
  2. 打开会话:TEEC_OpenSession() — 指定要连接的TA的UUID
  3. 调用命令:TEEC_InvokeCommand() — 传命令ID和参数
  4. 关闭会话、释放上下文:TEEC_CloseSession() + TEEC_FinalizeContext()

这里有个容易翻车的地方——参数传递。Client API定义了四种参数类型:

参数类型 说明 典型用途
TEEC_VALUE_INPUT CA传值给TA,只读 传递小整数、标志位
TEEC_VALUE_OUTPUT TA返回值给CA 返回状态码、计算结果
TEEC_MEMREF_TEMP_INPUT CA传内存块给TA,TA可读不可写 传递待加密的数据
TEEC_MEMREF_TEMP_OUTPUT TA写数据回CA的内存 返回解密后的数据
经验之谈:我建议你在CA端尽量使用TEEC_MEMREF_WHOLE(整块共享内存)而不是TEEC_MEMREF_PARTIAL(部分共享内存)。后者在部分TEE实现中会有对齐问题,导致TA读到的数据错位。嗯,这个坑我帮别人排查过好几次了。

2.3 TEE Socket API — TEE也能上网?

很多人以为TEE是封闭的,不能联网。其实GP标准定义了Socket API,让TA可以发起网络连接。当然,这个网络流量通常要经过REE的代理转发,不是TA直接操作网卡。

Socket API的用法跟POSIX socket很像:

TEE_SocketHandle sock = TEE_Socket(TEE_AF_INET, TEE_SOCK_STREAM, 0);
if (sock == TEE_HANDLE_NULL) {
    /* 创建失败 */
}

struct TEE_SockAddr addr;
addr.sin_family = TEE_AF_INET;
addr.sin_port = TEE_HTONS(443);  // HTTPS端口
// 设置IP地址...

TEE_Connect(sock, &addr, sizeof(addr));

// 发送/接收数据
TEE_Send(sock, data, len, 0);
TEE_Recv(sock, buf, buf_len, 0);

TEE_CloseSocket(sock);
安全提醒:TEE Socket API虽然方便,但网络数据在REE和TEE之间传递时,可能会被REE侧的恶意软件篡改。我建议你在TA内部再做一次应用层的加密或签名验证,不要完全依赖TEE的传输通道安全。

2.4 TEE存储API — 数据怎么安全地存下来

存储API是Internal API的一部分,但因为太重要,我单独拿出来说。它提供了两种存储方式:

  • 持久化对象(Persistent Object):存储在TEE的安全存储区域,掉电不丢失。每个TA有自己的存储空间,其他TA不能访问。
  • 临时对象(Transient Object):只在TA运行期间存在,用于存放密钥等敏感数据。

持久化对象的操作流程:

TEE_ObjectHandle obj = TEE_HANDLE_NULL;
TEE_CreatePersistentObject(
    TEE_STORAGE_PRIVATE,          // 私有存储
    &obj_id,                      // 对象ID
    TEE_DATA_FLAG_ACCESS_WRITE,   // 访问权限
    TEE_HANDLE_NULL,              // 初始数据(可选)
    NULL, 0,                      // 初始数据长度
    &obj
);

// 写入数据
TEE_WriteObjectData(obj, data, data_len);

// 关闭对象
TEE_CloseObject(obj);

关键点:存储API的底层实现各厂商差异很大。有的用RPMB分区,有的用文件系统加密,有的用OTP。你在做存储适配时,一定要确认:

  • 存储容量上限是多少?有的平台只有几十KB
  • 写入次数有没有限制?RPMB有寿命限制
  • 原子性保证如何?写入一半掉电会不会丢数据?

2.5 四类接口的关系 — 一张图看懂

下面这张图展示了CA、TA、TEE内核以及四类接口之间的调用关系。我画这张图的时候,特意把数据流向标清楚了,方便你理解谁跟谁通信。

TEE接口标准关系图 REE(普通世界) CA(客户端应用) Client API TEE(安全世界) TA(可信应用) Internal API TEE内核(TEE Core / TEE Kernel) 存储API(持久化对象) Socket API 密码学API(Internal API子集) 说明:CA通过Client API与TEE内核通信,TEE内核将请求路由到对应的TA TA通过Internal API调用TEE内核提供的存储、网络、密码学等服务

2.6 接口适配的常见误区

最后,我总结几个接口适配时容易犯的错误:

  • 误区一:以为所有平台都完整实现了GP标准。实际上,很多厂商只实现了子集。比如有的平台不支持Socket API,有的平台存储API的容量限制很严格。
  • 误区二:忽略参数对齐要求。Internal API的共享内存缓冲区通常要求4字节或8字节对齐,不满足对齐会导致性能下降甚至崩溃。
  • 误区三:在TA里直接使用REE的socket库。TEE里只能用TEE Socket API,不能用标准的BSD socket。我见过有人把Linux socket代码直接搬进TA,编译都过不了。
  • 误区四:存储API的并发访问。多个TA同时写同一个持久化对象,可能会造成数据损坏。GP标准没有强制要求存储操作的原子性,你得自己加锁或使用事务机制。
我的建议:刚开始做TEE接口适配时,先拿一个最小的“Hello World”TA跑通Internal API的基本流程,再逐步添加存储、网络等功能。这样出了问题,你能快速定位是接口适配的问题,还是业务逻辑的问题。