3. TEE接口标准:GlobalPlatform TEE API概览
聊到TEE开发,绕不开一个组织——GlobalPlatform。说白了,它就是制定TEE接口标准的那个“话事人”。你想想看,如果每家芯片厂都搞一套自己的TEE接口,那上层应用开发者不得疯掉?GlobalPlatform就是来解决这个问题的。
我个人习惯把GlobalPlatform TEE API分成两大部分:TEE Client API 和 TEE Internal API。前者是普通世界(Rich OS)访问TEE的通道,后者是TEE内部可信应用(TA)的开发接口。这两套API,基本覆盖了TEE开发的所有场景。
核心理解:Client API是“桥”,Internal API是“房”。桥让外面的人能进来,房里面怎么装修、怎么住人,全靠Internal API。
3.1 TEE Client API:普通世界与TEE的桥梁
Client API运行在REE侧(比如Android/Linux)。它的任务很简单:让CA(Client Application)能找到并跟TA(Trusted Application)通信。
我记得刚接触TEE时,最困惑的就是CA和TA到底怎么“握手”。后来发现,Client API就四个核心操作:
- TEEC_InitializeContext —— 初始化TEE上下文,相当于“敲门”
- TEEC_OpenSession —— 打开与TA的会话,相当于“进门坐下”
- TEEC_InvokeCommand —— 调用TA里的具体命令,相当于“谈正事”
- TEEC_CloseSession —— 关闭会话,相当于“告辞”
嗯,这里要注意:TEEC_InitializeContext 如果返回失败,别急着调后面的API。我在项目中遇到过,某款手机在安全启动没通过时,这个接口直接返回 TEEC_ERROR_ACCESS_DENIED。当时排查了半天,还以为是代码写错了。
避坑指南:我曾经在调试支付类CA时,发现 TEEC_OpenSession 总是超时。后来定位到是TA里做了硬件绑定校验,而测试机的安全元件(SE)没初始化。所以,Session打不开,不一定是Client API的问题,先检查TA侧的状态。
来看一个典型的Client API调用流程:
// 1. 初始化TEE上下文
TEEC_Context context;
TEEC_Result result = TEEC_InitializeContext(NULL, &context);
if (result != TEEC_SUCCESS) {
// 处理错误,比如打印日志或重试
return result;
}
// 2. 打开与TA的会话
TEEC_Session session;
TEEC_UUID uuid = { 0x12345678, 0x1234, 0x1234, ... };
result = TEEC_OpenSession(&context, &session, &uuid,
TEEC_LOGIN_PUBLIC, NULL, NULL, NULL);
if (result != TEEC_SUCCESS) {
TEEC_FinalizeContext(&context);
return result;
}
// 3. 调用TA命令
TEEC_Operation operation;
operation.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_NONE,
TEEC_NONE, TEEC_NONE);
operation.params[0].value.a = 42;
result = TEEC_InvokeCommand(&session, CMD_HELLO, &operation, NULL);
// 4. 关闭会话并清理
TEEC_CloseSession(&session);
TEEC_FinalizeContext(&context);
这段代码看着简单,但有几个细节容易翻车:
TEEC_UUID必须跟TA里注册的一致,否则Session打不开TEEC_LOGIN_PUBLIC只是最基础的登录方式,实际产品中常用TEEC_LOGIN_USER或TEEC_LOGIN_IDENTIFY- 别忘了
TEEC_FinalizeContext,否则内存泄漏等着你
3.2 TEE Internal API:可信应用的内功心法
Internal API是TA开发者最常打交道的。它提供了TEE内部的所有基础能力:内存管理、密码学操作、安全存储、时间服务等。
你想想看,TA运行在TEE里,不能直接调Linux的系统调用。那它怎么分配内存?怎么算SHA256?怎么存密钥?全靠Internal API。
我把常用的Internal API分成几类:
| 类别 | 典型API | 用途 |
|---|---|---|
| 内存管理 | TEE_Malloc, TEE_Free |
在TEE安全内存中分配/释放空间 |
| 密码学 | TEE_AllocateOperation, TEE_DigestUpdate |
执行AES/RSA/SHA等加解密和哈希运算 |
| 安全存储 | TEE_OpenPersistentObject, TEE_WriteObjectData |
将数据加密后写入RPMB或文件系统 |
| 时间服务 | TEE_GetSystemTime |
获取TEE内部的可靠时间 |
| 属性获取 | TEE_GetPropertyAsString |
读取TEE配置或设备属性 |
我个人觉得,密码学API是Internal API里最“坑”的部分。为什么?因为它的操作模式跟OpenSSL完全不一样。OpenSSL是“一把梭”,而TEE Internal API是“分步走”:先分配操作句柄,再设置密钥,然后分多次Update,最后Finalize。
警告:千万不要在TA里直接调用 memcpy 或 malloc 这些C库函数。TEE的安全内存管理有严格限制,必须用 TEE_Malloc 和 TEE_MemMove。我曾经见过一个同事,在TA里用了 malloc,结果在某个TEE实现上直接崩溃,因为那个TEE根本没实现标准C库。
来看一个TA内部使用密码学API的示例:
// 分配SHA256操作句柄
TEE_OperationHandle op_handle;
TEE_Result res = TEE_AllocateOperation(&op_handle,
TEE_ALG_SHA256,
TEE_MODE_DIGEST,
0);
if (res != TEE_SUCCESS) {
// 分配失败,可能是TEE资源不足
return res;
}
// 分多次输入数据
TEE_DigestUpdate(op_handle, data1, data1_len);
TEE_DigestUpdate(op_handle, data2, data2_len);
// 获取最终哈希值
uint8_t hash[32];
uint32_t hash_len = sizeof(hash);
TEE_DigestDoFinal(op_handle, NULL, 0, hash, &hash_len);
// 用完别忘了释放
TEE_FreeOperation(op_handle);
嗯,这里有个细节:TEE_DigestDoFinal 的第二个参数是 NULL,表示没有额外的输入数据。如果你还有数据要喂,可以传非空指针。但更常见的做法是,所有数据都在 Update 阶段喂完。
3.3 两张图看懂整体架构
说了这么多,不如一张图来得直观。下面这张图展示了Client API和Internal API在整个TEE系统中的位置:
再来看一张TA内部调用Internal API的流程图,帮你理解TA启动后的典型执行路径:
3.4 版本与兼容性
GlobalPlatform TEE API目前主流版本是 1.0 和 1.1。1.1主要增加了对多核TEE、大内存支持等特性。但说实话,市面上大部分设备还在用1.0。
我建议你在开发前,先确认目标平台的TEE版本。怎么确认?调用 TEE_GetPropertyAsString 读取 gpd.tee.version 属性。如果拿不到,大概率是1.0。
小技巧:如果你在适配不同厂商的TEE(比如高通QSEE、华为iTrustee、OPTEE),你会发现它们对GlobalPlatform API的实现细节有差异。比如,有的TEE要求 TEE_Malloc 的内存必须按32字节对齐,有的则没这个限制。我的经验是:写TA时,尽量用最保守的方式——该对齐的对齐,该检查的检查,别依赖厂商的“宽松实现”。
3.5 本章小结
GlobalPlatform TEE API是整个TEE生态的基石。Client API让你能从普通世界“敲门”,Internal API让你在TEE里“干活”。两者配合,才能构建出完整的安全应用。
嗯,最后提醒一句:别把Client API和Internal API搞混了。我见过有人把 TEE_Malloc 写在CA里,编译直接报错——因为CA根本链接不到Internal API的库。记住,Client API在REE侧,Internal API在TEE侧,这是铁律。