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系统中的位置:

GlobalPlatform TEE API 整体架构 REE(普通世界) Rich OS (Android/Linux) Client Application (CA) TEE Client API TEE(可信世界) Trusted OS (OPTEE/Trusty) Trusted Application (TA) TEE Internal API SMC / 共享内存 通信 关键说明 • Client API 运行在REE侧,CA通过它发起对TA的调用 • Internal API 运行在TEE侧,TA通过它使用安全服务 • 两者通过SMC指令或共享内存机制进行数据交换

再来看一张TA内部调用Internal API的流程图,帮你理解TA启动后的典型执行路径:

TA内部调用Internal API典型流程 TA入口:TA_CreateEntryPoint 分配内存:TEE_Malloc 初始化密码学操作:TEE_AllocateOperation 处理CA请求:TEE_InvokeCommand 清理资源:TEE_Free / TEE_FreeOperation

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侧,这是铁律。