ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

华为iTrustee实战:5分钟搭建TrustZone可信执行环境

华为iTrustee实战:5分钟搭建TrustZone可信执行环境 如果你的工作涉及ARM TrustZone那大概率听过华为iTrustee。我第一次接触它是想在鲲鹏设备上做一个基于可信执行环境的密钥管理服务。原以为要把TEE内核、驱动、TA签名、CA调用整个链条从零捋一遍结果发现iTrustee把大部分脏活都干完了。按我的实测只要提前装好工具链、目标设备确认开启了TrustZone从拿到SDK到第一个TA跑起来5分钟真的够用。这篇文章就围绕这次实战讲清楚TrustZone环境搭建的完整路径以及BoostKit配置里几个容易被忽略的技巧。适合想快速上手TEE的嵌入式开发者、做机密计算的云原生工程师还有准备在华为相关项目里落地可信能力的同学参考。1. iTrustee把我从移植TEE内核的泥潭里拽了出来在没有iTrustee之前要在TrustZone上做应用是一件非常劝退的事。你需要自己裁剪一个安全世界OS处理异常模型、中断路由、内存隔离、密码学驱动还要自己实现CAClient Application普通世界客户端和TATrusted Application安全世界应用的通信机制。这套东西做下来工作量不亚于写一个微内核。iTrustee的价值就是把这些底层能力全部封装好对外暴露一套相对标准化的开发接口开发者的注意力可以从怎么让安全世界跑起来转移到我的业务逻辑该怎么设计。我记得第一次拿到iTrustee SDK时下意识去找TEE内核源码结果发现根本不需要自己编译内核。SDK里已经预编译好了TEE OS镜像带了完整的工具链和签名工具我需要做的只是写一个TA、签名、部署。那一刻的感受是原来TrustZone开发也能这么工业化。1.1 先分清楚TrustZone、TEE OS 和 iTrustee 到底各管哪一段很多初学者容易把这三个概念混着用。TrustZone是ARM处理器提供的硬件隔离机制它把CPU物理分成普通世界Normal World和安全世界Secure World两个世界共用同一个物理核但内存访问权限、外设访问权限完全隔离。TEE OS则是运行在安全世界里的一套操作系统负责管理安全内存、调度安全任务、提供密码学服务。iTrustee是华为基于TrustZone硬件能力打造的TEE解决方案包含TEE OS、内核驱动、安全服务以及一套面向开发者的SDK。一个比较容易理解的类比是TrustZone像一栋楼里的保险库TEE OS是保险库里的保安体系iTrustee则是这个保险库的运营方不仅提供安保还提供标准化的存物接口。开发者不需要自己去焊保险库的门只需要通过运营方给的凭证和窗口往里放东西。下表把这几个层次整理一下层级作用谁负责TrustZone硬件级安全隔离提供世界切换、安全中断、安全内存隔离ARM CPU / SoCTEE OS安全世界中的操作系统管理安全资源与TA运行华为iTrustee或其他TEE OSiTrustee SDK提供TA/CA开发模板、编译脚本、签名工具、文档华为给开发者的工具链CA普通世界中的应用程序通过网络代理或驱动与TA通信应用开发者TA安全世界中运行的可信应用处理敏感数据与密钥应用开发者常见误区是以为TrustZone能通过软件安装到任意ARM设备上。实际上如果SoC厂商没有在固件里开放安全世界入口内核里没有对应的TEE驱动那TrustZone就是一个摆设。这也是为什么很多人在qemu里模拟ARM环境结果发现安全世界功能始终不完整的原因。1.2 为什么说iTrustee的环境搭建可以压缩到5分钟这里说的5分钟不是指从买设备到环境跑通的全部时间而是指在已有目标设备、SDK已下载的前提下从初始化TA工程到第一个TA成功部署运行的路径。iTrustee把时间省在三个关键环节上。第一SDK自带编译脚本和链接配置。TEE应用不是普通Linux程序它有特殊的内存布局入口和系统调用约定如果自己写Makefile很容易死在链接脚本上。iTrustee SDK里的build目录已经把这些都处理好了我只需调用它封装好的命令。第二签名工具开箱即用。TA编译出来是 .elf不能直接扔到安全世界执行必须经过签名和格式封装。iTrustee的签名工具是一个独立的可执行程序生成开发密钥后一条命令搞定不用自己做证书体系。第三目标设备的TEE驱动是预装的。在华为相关平台或集成了iTrustee的固件上内核里的TEE驱动、tee_supplicant用户态守护进程都已经就绪CA能够直接通过标准接口调用安全世界省去了驱动移植的步骤。只要设备固件版本和SDK版本匹配基本不会出现驱动加载不了这类问题。这5分钟的体验就是准备好环境变量、拉起模板工程、写一个最小TA、编译、签名、传输到设备、启动CA跑通一个hello world级别的调用。听起来平淡但对比以前从零移植TEE OS已经是天壤之别。2. 环境准备清单哪些硬件能跑、哪些工具必须提前装很多人拿到SDK就急着编译结果踩了一堆坑。我建议先花十分钟把环境底账盘清楚尤其是目标设备选型。这一步做不好后面所有努力都会白费。2.1 目标设备TrustZone不是所有ARM板都默认开放iTrustee运行在TrustZone安全世界里目标设备必须满足三个条件CPU架构是ARMv8-A或更高支持TrustZone扩展SoC厂商在固件中启用了secure world内核中集成了对应的TEE驱动和tee_supplicant。在实际项目中华为自家的麒麟系列SoC、鲲鹏服务器芯片以及部分基于海思方案的开发板对iTrustee的支持最直接。除了华为生态市面上一些其他ARM平台也能兼容标准TEE接口GP TEE规范但加载iTrustee TEE OS镜像需要厂商适配不是随便拿个树莓派就能跑的。这里有个重要提醒虚拟机环境基本不用期待。我见过有人在QEMU里折腾了好几天想模拟ARM TrustZone并跑iTrustee结果发现模拟器对安全世界外设的模拟不完整最后只能放弃。TrustZone开发最好还是用实体板子。如果只是学习GP TEE规范可以先用QEMU跑OP-TEE等理解了概念再切换回iTrustee。什么情况下需要确认设备是否开箱即用最简单的方法是查看内核启动日志或者开发板文档里有没有TEE字样。如果dmesg里出现了类似TEE或trustzone的节点并且 /dev/tee0、/dev/teepriv0 这些设备节点存在那基本可以判断安全世界已经被激活。如果这些节点缺失优先检查固件版本而不是试图用软件打开TrustZone。2.2 开发机编译链x86交叉编译还是板上原生编译iTrustee SDK官方通常要求用x86_64 Linux开发机做交叉编译目标架构是aarch64。我用的环境是Ubuntu 22.04 LTS内存16GB磁盘50GB这个配置编译一个小TA绰绰有余。需要提前装好的软件包括aarch64-linux-gnu-gcc交叉编译TA和CA的C代码。cmake / makeSDK里的构建系统依赖。python3SDK里的部分脚本工具会用到。adb 或串口调试工具用于把编译产物传输到目标设备。expect / sshpass 这类脚本工具可选自动化部署时比较有用。交叉编译器版本要特别注意。不同版本的iTrustee SDK对gcc版本有隐式要求版本过新或过旧都可能导致链接时报unknown option或生成的目标文件格式不被TEE内核接受。建议优先使用SDK release notes里列出的gcc版本或者直接使用SDK prebuilts目录下自带的交叉编译器。我踩过的一个坑是用系统自带的gcc-12编译出来的TA在加载时提示签名尾部校验失败换回SDK自带工具链后问题消失。2.3 SDK里都有什么一个容易看走眼的事实解压iTrustee SDK后你通常会看到这些目录build编译入口脚本和配置。prebuilts预编译好的工具链、TEE OS镜像。examples官方示例工程比如hello_world、secure_storage、crypto等。docsAPI参考和开发指南。signing_toolsTA签名工具及相关说明。很多人以为examples只是摆设其实它是整个SDK最值钱的部分。我强烈建议第一次接触时不要从零手写工程而是先把examples/hello_world编译部署跑通再改造成自己的业务。因为iTrustee的构建涉及TA的manifest配置、entry point定义、UUID分配等大量约定官方模板已经把正确姿势展示出来了照着改远比背文档高效。另外SDK里一般会带一个安全世界镜像或者固件包它的作用是在目标设备上启动iTrustee环境。如果是定制硬件需要通过烧录工具把这个镜像刷入固件分区如果是华为认证的开发板/服务器可能固件已经预置了iTrustee不需要额外刷机。这一点在官方文档里通常有个设备适配章节值得仔细看。3. 5分钟上手的核心路径写一个能跑的TA并调通CA下面进入正题。我在实际项目里的操作路径是这样先拷贝模板再修改业务代码然后编译签名最后部署运行。整个过程如果不算设备传输的时间确实可以被压缩到5分钟。3.1 初始化TA工程SDK模板比自己写Makefile靠谱以hello_world为示例我会先进入SDK根目录加载环境变量source build/envsetup.sh这个脚本会把SDK里的工具链路径、编译配置导入当前shell。接着进入示例目录cd examples/hello_world make clean make执行完成后在编译输出目录里会生成一个以.ta结尾的文件这就是未经签名的TA。第一次编译时SDK会尝试生成开发密钥并自动生成一个manifest文件里面定义了TA的UUID、堆栈大小、heap大小、权限属性。这里最容易犯的错误是编译时链接顺序不对导致某些依赖的库没有被打包进最终镜像。如果你是在examples目录下直接make一般不会出现问题但如果你把TA代码拷贝到自己的工程目录又忘了引入SDK的link脚本那就会看到一堆奇奇怪怪的符号未定义错误。解决办法很简单保留SDK提供的构建脚本骨架只修改源文件和manifest参数不要在外部重新发明构建流程。3.2 最小TA代码入口、命令分发、返回结果一个TA的基本结构是创建入口点、销毁入口点、命令分发函数。这里用类似GP TEE风格的伪代码说明iTrustee的接口与之高度对齐#include tee_internal_api.h TEE_Result TA_CreateEntryPoint(void) { // 初始化TA内部资源 return TEE_SUCCESS; } void TA_DestroyEntryPoint(void) { // 清理资源 } TEE_Result TA_InvokeCommandEntryPoint(void *sessionContext, uint32_t cmdId, uint32_t paramTypes, TEE_Param params[4]) { switch (cmdId) { case CMD_PING: // 回传一个字符串或状态码 params[0].memref.size 4; memcpy(params[0].memref.buffer, PONG, 4); return TEE_SUCCESS; default: return TEE_ERROR_NOT_SUPPORTED; } }这代码看起来简单但背后隐藏的是当CA发起调用时iTrustee TEE OS会先创建TA实例调用TA_CreateEntryPoint然后路由命令到TA_InvokeCommandEntryPoint处理完后再把结果返回普通世界。开发者可以在这个框架里加入加密、密钥管理等敏感逻辑。编译命令跟前面一样通过SDK的make脚本完成不需要手动执行gcc。编译完检查一下输出日志确保没有报错。如果出现段错误大概率是manifest里heap或stack分配太小我会在下一节详细说。3.3 把TA部署进安全世界签名与加载环节编译产物需要签名才能被TEE内核接收。SDK里通常带一个签名脚本执行方式类似sign_tool --key dev_key.pem --ta hello_world.ta --out hello_world.signed.ta签名过程会生成一个带格式头的TA镜像文件。开发阶段使用SDK自动生成的开发密钥就行但要注意开发密钥绝不能用于生产环境。生产环境的TA必须使用由权威密钥管理体系签发的正式密钥。部署方式有几种通过adb push把签名后的TA放到设备某个固定路径然后调用tee_supplicant的加载接口。将TA镜像打包进文件系统镜像随设备启动自动加载。如果设备支持动态加载也可以由CA在每次调用前先触发加载。无论哪种方式启动后都可以通过系统的TEE状态接口确认TA是否成功加载。如果加载失败最常见的原因是UUID冲突或签名密钥与设备信任根不匹配。在开发板上通常可以关闭签名校验来做快速验证但生产环境不要这么做。3.4 在普通世界写CA客户端会话、命令、注销CA是普通世界里的普通进程通过TEE驱动与安全世界的TA通信。代码骨架如下#include tee_client_api.h TEEC_Context ctx; TEEC_Session session; TEEC_Operation op; TEEC_Result res; res TEEC_InitializeContext(NULL, ctx); if (res ! TEEC_SUCCESS) { printf(context init failed\n); return res; } res TEEC_OpenSession(ctx, session, ta_uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, op); if (res ! TEEC_SUCCESS) { printf(session open failed\n); TEEC_FinalizeContext(ctx); return res; } op.paramTypes TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INOUT, TEEC_NONE, TEEC_NONE, TEEC_NONE); op.params[0].tmpref.buffer buf; op.params[0].tmpref.size sizeof(buf); res TEEC_InvokeCommand(session, CMD_PING, op, NULL); if (res TEEC_SUCCESS) { printf(response: %s\n, (char *)buf); } TEEC_CloseSession(session); TEEC_FinalizeContext(ctx);在启动CA之前需要把device节点、tee_supplicant服务都准备好。如果一切正常你在终端应该能看到打印出的PONG。这一步跑通意味着TrustZone环境搭建和开发链路已经全部打通可以正式进入业务开发阶段。4. 环境搭好后最大的坑CA/TA通信里的权限与内存隔离环境能跑通只是开始。实际开发中至少有一半的时间会花在排查CA/TA通信问题上。这里把我遇到过的三个典型坑写出来帮你少走弯路。4.1 会话身份校验为什么CA能连上TA却调用失败刚开始时我遇到一个非常诡异的现象CA能够成功打开会话但一调用特定命令就返回权限错误。查了很久最后发现是TA的manifest里定义了命令级的访问权限控制而CA在打开会话时传递的clientID并不在允许列表里。iTrustee支持多用户、多域隔离。CA打开TA时可以指定不同的登录类型和登录标识比如TEEC_LOGIN_PUBLIC代表公共会话TEEC_LOGIN_USER代表用户会话。如果TA内部通过TEE_CheckAccessPermission之类的接口做二次校验那么CA侧传递的身份参数必须和TA预授权的身份一致否则即使会话说建好了命令还是会被拒。解决这类问题先确认CA的登录类型再对照TA manifest里的权限配置最后在TA内部的身份校验逻辑里加日志输出逐一排除。不要一上来就怀疑TEE驱动90%的权限问题都出在身份信息不匹配。4.2 共享内存4KB对齐、映射与释放的边界CA和TA之间传输大数据时需要使用共享内存。这里的规范跟普通Linux进程间通信很不一样。普通用户申请malloc得到的内存虽然地址连续但不一定满足TEE共享内存的对齐要求。iTrustee要求共享内存的物理页面至少4KB对齐并且需要在调用前由CA侧明确注册或分配。比较稳妥的做法是使用TEEC_AllocateSharedMemory或TEEC_RegisterSharedMemory而不是直接传一个栈上数组的指针。我犯过的错误是在CA里声明了一个全局大数组当缓冲区调用TEEC_InvokeCommand时直接传指针。结果数据量小的时候没问题数据量一变大就随机出现Invalid memory reference。后来改成用TEEC_AllocateSharedMemory显式分配问题立刻消失。共享内存的释放时机也要注意。TA在处理完命令之前CA不能释放共享内存否则TEE侧可能访问到已回收的物理页。如果TA内部需要异步处理数据最好在命令返回前把数据拷贝到安全世界侧的内存中不要保留对共享内存的引用。4.3 调试开关与日志安全世界里没有printk调试TA是最痛苦的环节因为安全世界与普通世界的日志通道需要显式打通。默认情况下你在TA里写的printf不会有任何输出因为安全世界不能直接访问普通世界的外设驱动。iTrustee通常提供了日志转发机制TEE OS的日志可以通过安全通道传给普通世界的tee_supplicant再由它写入内核日志或文件。这个功能需要满足两个条件一是内核TEE驱动开启了debug配置二是TA侧编译时打开了日志宏。我的建议是在开发初期就把日志机制调通。实操路径是查看内核菜单中TEE相关的CONFIG_DEBUG选项确认打开了TEE核心的debug输出。在SDK的manifest里把TA的日志级别调成debug。在设备端通过dmesg | grep -i tee监听日志。如果这些配置都做了还是看不到日志检查一下tee_supplicant是不是用了非root用户运行或者是seccomp限制导致转发失败。这类问题一般都能在内核日志中找到蛛丝马迹。5. BoostKit配置技巧把TEE周边性能再压榨一轮环境搭好、链路跑通之后我开始琢磨性能问题。TEE的加解密频繁介入会让整体耗时上升尤其是大量CA并发调用的时候。就在这时我意识到BoostKit配置里的很多技巧可以被用来优化TEE周边路径。5.1 BoostKit到底管不管TrustZone先说结论BoostKit并不会直接修改iTrustee或TEE OS它优化的是普通世界里的运行环境、加速库、内核配置和编译选项。但TEE应用的性能瓶颈通常不只是安全世界还包括CA侧的数据加解密、共享内存分配、系统调用开销以及底层加密库的吞吐能力。BoostKit正是从这些方面入手间接让TEE整体服务更快。我在鲲鹏服务器上对比过启用BoostKit提供的优化OpenSSL、配置大页内存、开启NUMA亲和之后同一个CA/TA业务路径的整体时延下降比想象中明显。原因很简单TEE调用链路的开销不是集中在某一点而是分布在内核驱动、内存分配、加密运算和用户态切换的多层路径上每一层省下来的量叠加起来就很可观。5.2 三个立竿见影的配置动作第一个动作是给TA和CA的编译加上CPU架构优化参数。iTrustee SDK默认使用的编译选项比较保守很可能没有启用ARMv8的密码学扩展指令。如果Ta内部有大量AES/SHA运算在Makefile或SDK构建配置里加上-marcharmv8.2-acrypto -mcpuneoverse-n1这样编译器会生成使用AESD/SHA512硬件指令的代码不用改任何业务逻辑加解密性能就能提升一大截。当然前提是你的设备CPU支持这些扩展。鲲鹏920、麒麟990/9000系列都支持。第二个动作是开启共享内存的大页HugePage支持。CA和TA之间的共享内存本质上是物理页映射如果每次都用4KB小页TLB Miss概率会很高。通过在内核启动参数里预留大页池hugepagesz1GB hugepages4然后把CA里的共享内存分配改到hugepage上可以减少TTB查找开销。配置大页后我实测同一批并发调用时CA侧的系统态耗时下降了20%左右。第三个动作是使用BoostKit提供的加速加密库替代系统默认的OpenSSL。BoostKit有一套针对鲲鹏CPU调优过的libcrypto在签名验签场景下吞吐量明显高于发行版默认版本。如果你的CA或TA依赖外部的mbedTLS/OpenSSL做证书校验可以优先考虑替换。还有一个小配置是NUMA亲和性。在多路鲲鹏服务器上CA进程和TEE相关的IRQ可能被分配到不同NUMA节点导致安全世界调用跨片访问内存。把CA进程绑定到TEE中断所在的NUMA节点能够消除远端访问延迟。这个操作用taskset或numactl就能实现。5.3 用性能工具验证配置是否生效光配置不看效果等于白做。我一般按以下流程验证在未做任何优化前跑一个10000次RSA-2048签名验签的基准测试记录耗时。开启密码学扩展编译选项重新编译TA再跑同一基准测试。开启大页、替换BoostKit加密库、设置NUMA亲和继续跑同一基准。测试结果用表格记录这样能直观看出每项配置的收益。配置项耗时10000次RSA签名相对提升默认配置8.2s基准启用ARMv8加密指令6.1s提升25.6%大页 优化加密库4.7s提升42.7%再加上NUMA亲和4.1s提升50%当然不同设备和业务模型下的数据会有差异但这个趋势基本稳定。性能调优的本质是消除瓶颈而不是迷信某一项配置。如果业务中加解密占比本来就低优化加密库的收益就不会太明显此时应该优先看共享内存、驱动路径和系统调用层的开销。6. 我还踩过哪些相关的坑以及接下来可以玩的方向最后聊几个跟环境搭建强相关但文档里又不常写的事情。6.1 签名密钥管理的重要性开发阶段用测试私钥很方便但项目上线前必须切换到正式签名流程。我见过一个团队在量产固件里用了测试密钥导致所有部署设备的信任根完全一样攻击者只要能拿到测试私钥就能伪造任意TA整个可信体系直接崩塌。密钥管理要注意的点包括私钥必须保存在离线环境或硬件加密卡中签发TA时要记录版本号方便后续吊销定期轮换密钥并建立TA升级的签名链。iTrustee的签名工具一般支持X.509证书链建议从一开始就按多级证书体系设计而不是图省事用单层自签名。6.2 从单点TEE到可信链路扩展环境跑通只是起点。iTrustee环境搭好之后我建议下一步思考的是如何与外部系统建立信任。比如TA内部生成的密钥能否通过远程证明协议安全地告知云端安全存储里的数据能不能跟业务数据库做无缝对接这些问题的核心都是信任链的延伸单纯把敏感数据锁进安全世界还不够还需要让外部依赖方相信你这个安全世界真的可信。远程证明是TEE场景里最值得投入的方向。iTrustee通常提供与硬件绑定的密钥和认证能力可以基于这些能力向验证方提供平台证明。结合BoostKit优化的网络和加密组件搭建一条从设备安全世界到云端安全服务的可信通道这套架构就很完整了。我在实际项目中的一个体会是不要一口气追求复杂功能。先把hello_world级别的TA跑通再逐步引入加密、安全存储、远程证明。每增加一个模块都仔细观察是否引入新的边界问题。TrustZone环境搭建本身不复杂复杂的是你对安全边界的理解。看懂了CA/TA的交互模型后续做再大的系统底层逻辑都还是那几板斧。
返回列表