4、TEE接口标准:GlobalPlatform TEE API、TEE Client API、TEE Internal Core API 概览
聊到TEE的接口标准,GlobalPlatform(GP)是绕不开的。说白了,它就是TEE世界的“普通话”。没有这套标准,各家芯片厂的TEE就像方言一样,你写个应用还得针对不同平台各搞一套,那运维成本就上去了。
我最早接触GP标准是在一个金融支付项目里。当时客户要求TEE必须通过GP兼容性认证,我心想不就是调几个API嘛,结果一翻规范文档——好家伙,光TEE Internal Core API就定义了上百个函数。嗯,今天咱们就把这三类API掰开揉碎,看看它们各自管什么、怎么用。
4.1 三层架构:谁在跟谁说话?
先看整体结构。GP把TEE的软件栈分成了三层,每一层都有对应的API:
- TEE Client API:跑在REE侧(也就是Android/Linux等普通操作系统),供CA(Client Application)调用,用来跟TEE建立会话、传输数据。
- TEE Internal Core API:跑在TEE侧,供TA(Trusted Application)调用,提供加解密、安全存储、时钟等核心能力。
- TEE Sockets API / TEE DataStream API:扩展接口,用于网络通信和流式数据传输,咱们后面章节再细聊。
我习惯把这三层想象成“前台-后台-仓库”的关系:CA是前台接待,负责收单;TA是后台处理,负责干活;Internal Core API就是仓库里的工具,锤子扳手一应俱全。
核心要点:CA和TA永远不在同一个世界。CA在REE,TA在TEE,它们之间通过共享内存和SMC(安全监控调用)通信。Client API就是CA的“电话机”,Internal Core API是TA的“工具箱”。
4.2 TEE Client API:CA的“电话机”
Client API是CA与TEE通信的唯一入口。它定义在GP的《TEE Client API Specification》里,核心就几个函数:
| 函数 | 作用 | 我踩过的坑 |
|---|---|---|
TEEC_InitializeContext |
初始化TEE上下文,建立通信通道 | 忘记检查返回值,导致后续调用全挂 |
TEEC_OpenSession |
打开与指定TA的会话 | UUID写错一个字节,会话死活打不开 |
TEEC_InvokeCommand |
向TA发送命令,携带参数 | 共享内存没对齐,TA读数据全是乱码 |
TEEC_CloseSession |
关闭会话 | 忘了调用,导致TA资源泄漏 |
TEEC_FinalizeContext |
释放TEE上下文 | 重复调用导致double-free崩溃 |
一个典型的CA调用流程是这样的:
// 1. 初始化上下文
TEEC_Context ctx;
result = TEEC_InitializeContext(NULL, &ctx);
if (result != TEEC_SUCCESS) {
// 我曾经在这里吃过亏——没判断返回值就直接往下走
return -1;
}
// 2. 打开会话
TEEC_Session session;
TEEC_UUID uuid = MY_TA_UUID; // 注意:UUID必须与TA声明一致
result = TEEC_OpenSession(&ctx, &session, &uuid,
TEEC_LOGIN_PUBLIC, NULL, NULL, NULL);
if (result != TEEC_SUCCESS) {
TEEC_FinalizeContext(&ctx);
return -1;
}
// 3. 调用命令
TEEC_Operation op;
op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_NONE,
TEEC_NONE, TEEC_NONE);
op.params[0].value.a = 42;
result = TEEC_InvokeCommand(&session, CMD_HELLO, &op, NULL);
// 4. 清理
TEEC_CloseSession(&session);
TEEC_FinalizeContext(&ctx);
避坑指南:我曾经在一个项目里发现,CA调用TEEC_InvokeCommand后TA返回的数据总是错的。查了两天才发现——共享内存的物理地址没做cache一致性维护。嗯,ARM平台下记得用TEEC_RegisterSharedMemory注册内存,别自己malloc就传进去。
4.3 TEE Internal Core API:TA的“工具箱”
Internal Core API是TA开发者的主战场。它定义在GP的《TEE Internal Core API Specification》里,涵盖了TA需要的所有基础能力。我把它分成几大类:
4.3.1 加解密与哈希
TEE_AllocateOperation/TEE_FreeOperation:创建/销毁密码操作句柄TEE_AEInit/TEE_AEDecryptFinal:对称加解密(AES、SM4等)TEE_AsymmetricEncrypt/TEE_AsymmetricDecrypt:非对称加解密(RSA、ECC、SM2)TEE_DigestUpdate/TEE_DigestDoFinal:哈希计算(SHA-256、SM3)
你想想看,TA里做加解密,最怕什么?密钥泄露。所以GP规定:密钥对象必须用TEE_GenerateKey或TEE_AllocateTransientObject创建,且存储在TEE安全内存中。CA永远拿不到密钥的明文。
4.3.2 安全存储
TEE_CreatePersistentObject:创建持久化对象(文件)TEE_ReadObjectData/TEE_WriteObjectData:读写数据TEE_OpenPersistentObject:打开已有对象
安全存储的数据是加密后写入flash的,密钥由TEE硬件派生。我记得有一次,客户要求TA存储的密钥必须能跨设备恢复——嗯,这就涉及到安全存储的“迁移”问题了,后面咱们专门讲。
4.3.3 时间与随机数
TEE_GetSystemTime:获取系统时间(注意:这个时间可能被REE篡改,不能用于安全校验)TEE_GenerateRandom:生成真随机数(基于硬件熵源)
警告:千万不要在TA里用TEE_GetSystemTime做超时判断!我曾经在一个支付项目里,攻击者通过修改REE系统时间绕过了TA的支付超时检查。正确的做法是用TEE_GetTAPersistentTime或硬件计时器。
4.4 TEE Internal Core API 的“潜规则”
光看函数签名是不够的。我总结了几个实战中容易翻车的地方:
- 句柄必须显式释放:
TEE_AllocateOperation、TEE_AllocateTransientObject返回的都是句柄,用完不释放就是内存泄漏。TA崩溃了TEE会帮你回收,但优雅退出时一定要自己清理。 - 参数类型要匹配:
TEE_OperationMode有TEE_MODE_ENCRYPT和TEE_MODE_DECRYPT,搞反了加解密结果就是一团乱码。我见过有人把AES加密模式设成TEE_MODE_DECRYPT,然后debug了一整天。 - 共享内存大小有限:GP规范建议单次传输不超过4KB。大数据量要分片传输,或者用
TEE_RegisterSharedMemory注册大块内存。 - 异常处理不能省:每个API都可能返回
TEE_ERROR_OUT_OF_MEMORY、TEE_ERROR_SECURITY等错误码。不检查返回值,线上出问题连日志都找不到。
4.5 三类API的协作关系
最后用一张表总结它们的分工:
| API类别 | 调用方 | 运行环境 | 核心职责 | 典型函数数 |
|---|---|---|---|---|
| TEE Client API | CA | REE | 建立会话、传输命令、管理共享内存 | 约20个 |
| TEE Internal Core API | TA | TEE | 加解密、安全存储、时间、随机数、对象管理 | 约120个 |
| TEE Sockets API | TA | TEE | 网络通信(可选扩展) | 约15个 |
说白了,Client API是“门面”,Internal Core API是“内核”。CA通过Client API敲开TEE的门,TA用Internal Core API干活。两者配合好了,你的TEE应用才能既安全又高效。
我个人习惯在项目初期就把这三类API的调用链路画出来——CA调了哪个Client API、对应TA里哪个Internal Core API、数据怎么流。画清楚了,后面写代码基本不会跑偏。