2. TEE架构概览:GlobalPlatform TEE架构、TEE内部组件
好,我们进入第二章。上一章聊了TEE能解决什么问题,这一章咱们看看TEE里面到底长什么样。说白了,就是拆开TEE这个黑盒子,看看它内部有哪些零件,怎么分工的。
我个人习惯,学一个新系统架构,先看它的标准化组织定下的框架。TEE领域最权威的标准,就是GlobalPlatform(GP)制定的TEE架构。你出去跟同行聊TEE,大家默认说的就是这个框架。
2.1 GlobalPlatform TEE架构
GP把TEE系统分成了几个明确的层次。我画了张图,你先有个整体印象。
这张图从上到下,依次是:
- REE(富执行环境):跑Android、Linux这些通用OS的地方。应用层叫CA(Client Application)。
- TEE通信代理:REE和TEE之间的桥梁。负责把CA的请求安全地传给TEE。
- TEE内部核心:咱们这节课的重点。包含TEE内核、安全存储、内部通信代理等组件。
- 安全硬件层:底层的硬件隔离机制。比如ARM TrustZone、独立的SE芯片、密码学加速器。
核心要点:GP架构的核心思想是「分层隔离」。每一层只做自己的事,层与层之间通过严格定义的API通信。你在REE侧看到的TEE功能,其实都是经过通信代理转发后的「代理服务」。
2.2 TEE内部组件详解
好,咱们把TEE内部拆开,看看三个最关键的组件。
2.2.1 TEE内核
TEE内核,你可以把它理解成一个「微型操作系统」。但它跟Linux那种通用内核完全不同。我刚开始接触TEE时,总拿Linux的思路去套,结果踩了不少坑。
TEE内核的核心职责:
- 任务调度:管理TA(Trusted Application)的执行。TEE里通常只有一个或少数几个TA在运行,调度策略比Linux简单得多。
- 内存隔离:这是TEE的命根子。内核必须保证每个TA的内存空间不被其他TA或REE访问。硬件上靠TrustZone的TZASC(TrustZone Address Space Controller)实现。
- 中断管理:TEE能处理来自硬件的中断,但优先级比REE低。我记得在项目中遇到过一个问题:REE侧频繁中断导致TEE响应超时,后来调整了中断优先级才解决。
- 密码学服务:提供加解密、签名、哈希等基础能力。这些操作通常直接调用硬件密码学引擎,性能比REE侧软实现高一个数量级。
避坑指南:我曾经在移植TEE内核时,忽略了内存隔离的粒度配置。默认配置下,TEE内核把所有TA都放在同一个安全内存区域。结果一个TA的缓冲区溢出,直接污染了另一个TA的密钥数据。排查了两天才找到原因。所以,务必为每个TA分配独立的安全内存区域,哪怕它很小。
2.2.2 TEE安全存储
安全存储是TEE的「保险柜」。REE侧的文件系统,root用户或者攻击者可以随便读。但TEE安全存储里的数据,就算你把手机拆了,用JTAG读内存,也拿不到明文。
它是怎么做到的?
| 安全机制 | 说明 | 我的经验 |
|---|---|---|
| 硬件密钥绑定 | 存储加密密钥由芯片内部硬件生成,软件拿不到。每次启动时,密钥从OTP(一次性可编程)区域或PUF(物理不可克隆函数)中派生。 | 某款芯片的OTP区域在产线烧录时出了问题,导致所有设备的TEE安全存储无法初始化。排查了三天,最后发现是烧录脚本少写了一个字节。 |
| 防回滚保护 | 每次写入数据时,会记录一个单调递增的计数器值。攻击者不能把数据恢复到旧版本。 | 这个机制在OTA升级时特别重要。如果升级失败,系统能检测到数据版本不匹配,拒绝加载旧数据。 |
| 原子操作 | 写入操作要么全部成功,要么全部失败。不会出现写了一半断电,数据损坏的情况。 | 嗯,这个特性在REE侧的文件系统里很少见。TEE必须保证数据完整性,哪怕掉电。 |
安全存储的典型用途:
- 存储设备私钥(比如用于DRM的RSA私钥)
- 保存用户生物特征模板(指纹、人脸特征)
- 缓存支付应用的Token和会话密钥
注意:TEE安全存储的容量通常很小,一般只有几十KB到几MB。别想着拿它存照片或视频。它只适合存「核心机密数据」。我见过有人试图把整个数据库塞进TEE安全存储,结果把存储空间撑爆了,系统直接panic。
2.2.3 TEE通信代理
通信代理,是REE和TEE之间的「信使」。你想想看,REE和TEE是两个完全隔离的世界,它们怎么交换数据?靠的就是这个代理。
通信代理的工作流程:
- CA发起请求:REE侧的客户端应用(CA)调用GP定义的TEE客户端API(比如
TEEC_OpenSession)。 - 陷入TEE:通信代理通过SMC(Secure Monitor Call)指令,从REE世界切换到安全世界。这个切换由Monitor模式处理。
- 消息路由:TEE侧的通信代理收到请求,解析出目标TA的UUID,把消息转发给对应的TA。
- TA处理并返回:TA执行操作,把结果通过同样的路径返回给CA。
这里有个关键点:数据是怎么传递的? 不能直接传指针,因为REE和TEE的地址空间是隔离的。通信代理会使用共享内存区域,这块内存在硬件上被标记为「安全可共享」。CA把数据写入共享内存,TEE侧再从共享内存读取。
性能瓶颈:每次REE到TEE的切换,都有几百到几千个时钟周期的开销。所以,不要频繁调用TEE接口。我建议的做法是:把多次操作打包成一次批量请求,减少上下文切换次数。比如,你要验证10个签名,别调10次TEE接口,而是把10个签名数据一次性传给TA,让TA批量处理。
2.3 组件协作示例:安全启动校验
咱们用「安全启动校验」这个场景,把三个组件串起来看看。
- 启动时:BootROM加载TEE内核镜像。TEE内核验证镜像的签名(使用硬件公钥)。
- TEE内核启动后:加载安全存储驱动,从OTP中读取设备唯一密钥。
- REE侧请求校验:Linux内核通过通信代理,请求TEE校验某个系统分区的签名。
- TEE内核调度:把请求交给负责校验的TA。TA从安全存储中读取公钥,对分区数据进行签名验证。
- 返回结果:TA把校验结果(通过/失败)通过通信代理返回给REE侧。
你看,整个流程里,TEE内核负责调度和安全隔离,安全存储负责保管密钥,通信代理负责跨世界通信。三个组件各司其职,缺一不可。
个人建议:刚开始设计TEE方案时,别急着写代码。先把这三个组件的接口定义清楚。我习惯用GP标准接口作为基础,然后根据项目需求做裁剪。比如,如果你的设备不需要复杂的文件系统,可以精简安全存储的实现,只提供简单的键值对存储接口。