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);
2.2 GP TEE Client API — CA怎么“敲门”进TEE
Client API是运行在REE(普通世界)的客户端应用(CA)用来跟TEE通信的接口。它定义了CA如何打开TEE会话、调用TA的命令、传递参数。
核心流程其实就四步:
- 初始化上下文:TEEC_InitializeContext()
- 打开会话:TEEC_OpenSession() — 指定要连接的TA的UUID
- 调用命令:TEEC_InvokeCommand() — 传命令ID和参数
- 关闭会话、释放上下文: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的内存 | 返回解密后的数据 |
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);
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内核以及四类接口之间的调用关系。我画这张图的时候,特意把数据流向标清楚了,方便你理解谁跟谁通信。
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标准没有强制要求存储操作的原子性,你得自己加锁或使用事务机制。