尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

ARM新架构物联网安全实践:TrustZone、安全启动与固件防护全解析

ARM新架构物联网安全实践:TrustZone、安全启动与固件防护全解析 物联网安全这几年被提起的频率越来越高但真正落到产品上很多团队还停留在“把数据加密一下”的层面。我做嵌入式开发快十年最近半年把团队三个IoT产品线的核心平台全部切换到ARM新架构包括Cortex-M33、Cortex-M85和Cortex-A35最大的驱动力不是性能而是安全。你去看现在市面上那些被爆出漏洞的物联网设备十有八九栽在同一个地方固件被人扒出来逆向、调试口没锁、启动链没有校验、通信密钥硬编码。ARM近几代架构在安全上做了非常多底层改进只是大部分资料都比较零散这篇就把我们实际选型、移植、踩坑的过程完整梳理一遍。这篇文章适合三类人看一是准备做安全IoT产品但还没定芯片方案的可以参考ARM不同安全技术到底怎么选二是已经用上Cortex-M33/M55/M85或者Cortex-A系列想把TrustZone、安全启动、可信固件这些真正用起来的三是纯粹想搞懂ARM新架构安全特性到底解决了什么问题的。我尽量用项目实战的视角来讲不堆砌概念能直接抄作业的就直接抄。1. 整体设计与思路拆解为什么物联网安全必须从芯片层解决1.1 物联网设备的安全困境每一层都不靠谱先说一个真实的项目背景。我们做的是工业数据采集网关跑Linux主控从Cortex-A8换到了Cortex-A35。原来那套方案的维护成本高得吓人因为设备部署在客户机房安全补丁基本靠人工去现场升级。后来做了一次安全审计发现几个触目惊心的问题U-Boot没有校验内核镜像攻击者只要拿到串口就能换掉整个系统root密码是统一的且固件里直接写死了更离谱的是射频模块的固件升级包没签名理论上任何人都可以伪造一个固件包刷进设备。这些问题的根源不在应用层代码而是底层的安全信任模型缺失。传统的物联网设备安全思路很像“给房子加锁”应用层加个密码、传输层加个TLS就以为安全了。但攻击者根本不需要撬锁他可能直接拆墙——从物理上读取Flash、通过调试接口接管CPU、替换Bootloader。ARM新架构的核心思路变了它不再试图让每一层都坚不可摧而是建立一个从硬件信任根开始的、逐级验证的信任链。1.2 ARM新安全技术全景TrustZone、ARMv8-M、PAC/BTI到底分别解决什么问题ARM在安全领域的布局是一条清晰的演进线。早期的Cortex-M系列基本没有硬件安全机制安全完全依赖外部加密芯片或者软件混淆。Cortex-M23/M33开始引入ARMv8-M架构这是第一个把TrustZone带到微控制器级别的大版本。Cortex-A系列则是从ARMv8-A开始全面强化TrustZone并在后续架构版本中加入PAC指针认证码和BTI分支目标识别等高级防御特性。打个比方如果你是做智能门锁、传感器节点这类资源受限设备Cortex-M33的TrustZone会把整个系统切成安全世界和非安全世界两个“房间”。安全世界跑密钥管理和安全启动逻辑非安全世界跑应用逻辑哪怕应用被攻破攻击者也够不到密钥。如果你是做边缘网关、视频监控这类需要跑完整操作系统的设备Cortex-A35/A53的TrustZone OP-TEE可以提供一个独立的可信执行环境配合PAC和BTI防护系统调用和跳转攻击。不同的设备形态必须选对相应的安全方案否则要么性能浪费要么安全能力不足。1.3 我们为什么放弃外置安全芯片的方案团队早期也考虑过外置SE安全芯片的方案当时选了一款主流SE支持国密算法也通过了相关认证。但在实际做产品规划的时候算了一笔账问题不小。第一是成本。以我们网关的出货量级外置SE每颗增加的成本不小而且还要额外设计PCB布局和天线隔离。第二是产品体积网关内部本身就紧凑加一颗SE芯片对结构设计压力很大。第三更关键外置SE和主控之间的通信总线比如I2C或SPI本身就暴露在PCB走线上有经验的攻击者可以挂逻辑分析仪抓总线数据破解成本其实没有想象中高。后来我们把方案改成ARM TrustZone 安全启动 硬件加密引擎把信任根放在芯片内部的OTP和TrustZone保护的内存区域里彻底绕开了外部总线暴露的问题。这个决策的前提是ARM新架构已经把这些安全能力做进了芯片内部不需要我们额外去买一颗独立芯片。Cortex-M33的TrustZone把安全域隔离做得非常干净加上ARM的Trusted Firmware-M开源项目可以直接用开发成本远低于自己从头搞一套安全体系。2. 核心细节解析与实操要点从芯片选型到安全特性落地2.1 芯片选型时如何评估ARM安全特性的完整度选芯片不能只看主频和Flash大小尤其是做安全IoT必须把安全特性完整度放在前面。我整理了我们在选型时重点关注的能力清单给正在选型的读者做个参考。首先是TrustZone支持情况。Cortex-M23/M33/M55/M85全系列支持ARMv8-M TrustZone而老款Cortex-M0/M3/M4/M7都不支持。Cortex-A系列从ARMv8-A开始TrustZone是标准配置但具体实现程度各芯片厂有差异有些低端A系列芯片把TZPC和TZASC等组件裁剪了要仔细看参考手册。其次是安全启动。这里有两个层面ROM里的BootROM是否校验Bootloader的签名以及Bootloader是否校验应用固件的签名。做安全IoT产品这两级缺一不可。有些芯片只做了前者后者完全留给用户实现如果开发团队经验不足很容易留下后门。第三是硬件密码学加速。AES、SHA、RSA/ECC这些算法如果纯软件实现在Cortex-M33上性能会非常吃紧而且密钥会暴露在内存里。硬件加密引擎不仅能提速有的还能做到密钥不出硬件比如ARM的CryptoCell或者各芯片厂商的加密模块这是安全IoT的一个关键加分项。第四是调试接口管控能力。安全产品最终要批量出货SWD/JTAG调试口必须能锁死。有的芯片支持多级调试锁定从完全开放到完全关闭之间还有几个中间档位比如可以配置成安全世界可调试但非安全世界不可调试这对开发调试期又保留现场分析能力非常有用。我们最终选的Cortex-M33主控这几项全部满足而且芯片厂商给出了完整的安全启动参考代码省了不少时间。2.2 ARMv8-M TrustZone开发时的内存隔离与中断处理要点TrustZone在Cortex-M33上的落地方式简单说就是把地址空间分成安全和非安全两个区安全区只能被安全代码访问非安全区可以被任何代码访问。这个隔离是硬件强制执行的不是软件约定所以攻击者哪怕拿到了应用层的任意代码执行权限也无法直接读安全区的密钥。实际开发中有两个特别容易踩坑的地方。第一个是内存属性配置。Cortex-M33通过SAU和IDAU来定义哪些地址是安全的、哪些是非安全的。IDAU是芯片厂商固定死的改不了SAU可以由软件配置。如果SAU配置不当比如把某个包含密钥的外设寄存器地址漏配成非安全那攻击者可能通过外设绕过隔离。我的习惯是所有安全相关的地址配置统一放在一个独立的SAU初始化文件里做代码审查的时候第一时间看这个文件。第二个是中断处理。ARMv8-M的中断可以安全和非安全两套向量表安全中断必须由安全代码响应非安全中断正常响应。但如果安全中断在非安全代码执行时到达CPU会自动切换到安全状态去处理处理完再切回来。这个切换涉及栈指针和状态保存容易出问题。我们踩过一个坑安全中断处理函数里调用了非安全回调函数结果直接触发UsageFault。后来定了铁律安全世界和非安全世界的函数调用边界必须严格定义安全代码绝不能反向回调非安全代码一切通信通过定义的跨世界调用接口完成。2.3 PAC和BTICortex-A系列上的高级攻击缓解手段如果做的是跑Linux或RTOS的高性能IoT设备ARMv8.3-A引入的PAC和ARMv8.5-A引入的BTI值得重点研究。这两个东西是专门针对传统攻击手法的硬件级防御。PAC的全称是Pointer Authentication Code简单说就是给函数返回地址和函数指针加上一个基于密钥的签名。攻击者想通过栈溢出改写返回地址来劫持控制流会因为拿不到密钥而失败。这个听起来很美好实际用起来有几个注意点。一是编译器需要开-mbranch-protectionpac-ret之类的选项GCC和Clang都支持二是PAC密钥存在系统寄存器里由EL3或EL2固件管理不是随便谁都能读的三是启用之后性能会有一点损耗主要是函数进出栈和间接跳转时多了几条指令实测Cortex-A35上大约增加2%到4%的开销对IoT设备来说完全可以接受。BTI则是限制间接跳转的目标必须是合法的前置BTI指令可以理解为给所有合法的跳转目标“盖了章”间接跳转只允许跳到盖过章的地方。它防御的是面向跳转编程这类攻击技术。我们团队在移植主线内核和发行版系统到Cortex-A35平台时把PAC和BTI都打开了配合SELinux和读-only的代码段整个攻击面小了很多。3. 实操过程与核心环节实现安全启动、调试口管控、SWD与固件签名3.1 安全启动链完整落地从BootROM到应用固件逐级验证安全启动是整个安全体系的基石。道理很简单如果设备启动时连自己跑的是什么代码都不验证后面所有安全机制都建立在沙滩上。ARM新架构的处理器普遍支持从BootROM开始的多级启动校验我们需要做的就是把这套机制完整用起来。先看Cortex-M33平台。我们用的是Trusted Firmware-MTF-M来担当安全启动和安全运行时环境。第一级校验从芯片BootROM开始芯片出厂时烧录了厂商根公钥哈希BootROM会用这把根公钥验证TF-M的签名验证通过后才加载TF-M。TF-M起来之后它会去安全存储区读取应用固件的公钥校验应用固件签名校验通过后才跳转到非安全世界的应用代码。应用固件在客户现场也可以升级但每次升级都必须用对应的私钥签名否则设备拒绝启动。这里有个细节值得展开签名算法选择。ECC在同等安全强度下比RSA短很多对资源受限的IoT设备更友好我们用的P-256 ECDSA。签名时先把固件做SHA-256哈希再用ECDSA对哈希签名。映像文件结构一般是固定头部 固件数据 签名值头部里包含版本号和哈希算法标识。固件签名流程我在CI里写成了自动化步骤核心逻辑如下# 1. 计算固件哈希 sha256sum app_firmware.bin app_firmware.hash # 2. 用私钥对哈希签名 openssl dgst -sha256 -sign signing_private_key.pem -out app_firmware.sig app_firmware.bin # 3. 打包成带签名的镜像 srec_cat app_firmware.bin -Binary -header APP -o app_firmware_signed.srec -Motorola # 4. 生产阶段可以用脚本把哈希和签名拼接进自定义镜像头签名验证的细节在于固件头设计。我们自定义了一个镜像头包含魔数、版本号、固件长度、算法标识、公钥索引和签名值。Bootloader读到镜像头后先查版本号是否大于等于当前运行版本防止回滚攻击再用预设公钥验签全部通过才跳转执行。这个版本号防回滚逻辑很容易被忽略但如果你不检查攻击者完全可以把设备降级到有已知漏洞的旧固件上。3.2 SWD调试口保护为什么锁死后连自己都差点救不回来调试口是双刃剑开发时它是救命恩人出货时它就是安全漏洞。攻击者只要用J-Link或DAPLink连上SWD口就能读取Flash、修改内存、甚至接管CPU。所以安全IoT产品出厂前必须锁定调试口。SWD协议其实只有两根线SWDIO和SWCLK但能做的事情非常多。它通过DPDebug Port和APAccess Port两层结构访问芯片内部资源。AP里有多个寄存器组比如Base Address寄存器、IDR寄存器、甚至Memory-AP可以直接读写总线内存空间。Cortex-M33内核还有一个SysBus接口Debug Access Port可以通过它访问整个系统总线地址空间。这本来是为了方便调试但如果不加保护就等于是给攻击者开了一扇后门。我们踩过的那个坑是这样的第一版产品在量产固件里直接调用了芯片厂商提供的调试锁定接口烧完锁死之后发现因为SAU配置的一个小错误设备启动时会卡死在Bootloader阶段而我们自己也无法通过SWD重新烧录了。当时量产线已经备好了幸好研发阶段保留了几个未锁的样机花了三天时间用未锁样机复现问题、修改代码最后重新生成固件解锁一批回炉板卡才解决。调试锁定这块的经验总结下来是三条。第一条锁定前必须完整验证启动链路锁定动作要放在启动流程最后一步确保固件本身没有问题再锁。第二条锁定等级要设计成可配置的通过OTP里的配置位控制而且要在Bootloader里实现解锁开关给售后留个后门但这个后门必须带强认证不然等于没锁。第三条安全产品最好用芯片的Secure Fuse或OTP来做永久锁定而不是仅靠Flash里的配置因为攻击者可以物理擦除或替换Flash。实现上Cortex-M33厂商通常提供SOC特定的调试锁定接口。以我们用的芯片为例通过写安全控制寄存器来控制调试使能/* 锁定调试接口示例伪代码具体寄存器以芯片手册为准 */ void lock_debug_port(void) { /* 1. 擦除并烧写安全配置区设置DBG_LOCK位 */ flash_program_otp(OTP_DEBUG_LOCK_ADDR, 0x1); /* 2. 复位后BootROM会检查该OTP位置1则禁用SWD访问 */ NVIC_SystemReset(); }重要提醒量产前做一次锁定验证和锁定解除演练。我们后来专门做了个实验板把锁定/解锁流程反复跑了二十多次确认在任何异常场景下都能恢复设备才在产线放开批量锁定。3.3 通过SWD读取PC寄存器定位崩溃现场虽然调试口最终要锁但在研发调试阶段SWD的价值无可替代。这里分享一个利用ARM调试架构定位崩溃现场的有效方法。Cortex-M内核有一个Core Debug寄存器组其中DHCSRDebug Halting Control and Status Register控制内核停摆DCRSRDebug Core Register Selector Register选择要读写的寄存器DCRDRDebug Core Register Data Register是数据通道。要读取因为HardFault死掉的内核的PC寄存器即使没有正确触发异常回调也可以强制停摆内核再读取现场。具体做法先用SWD连接目标读取DHCSR确认调试使能然后写入DHCSR让内核进入Halt状态接着通过DCRSR写入要访问的寄存器编号。R15对应PCR14对应LRR13对应SP。最后从DCRDR读出数值就是死机瞬间的寄存器现场。我用J-Link Commander做过这个操作比反复加打印日志要高效得多。实际项目里我们曾遇到过一类在低频复现的HardFault设备正常跑几天后会随机复位重启看门狗都没来得及触发。常规调试手段很难抓到现场后来在脚本里实现了一套自动抓取机制设备死机后由另一个MCU通过SWD主动读取PC、LR、SP和几个关键寄存器再和栈上的数据一并上报。靠这个机制定位到一个中断服务函数里未对齐访问的野指针问题。等调试口正式锁定后这个能力就关闭了但同时也保留了故障时把现场信息写入片内Flash断电保存的机制后续运维还能拿到第一手现场数据。4. 安全通信与OTA升级实战从mbedTLS到AWS IoT按策略下发4.1 arm交叉编译工具链与产品构建环境搭建安全IoT产品开发绕不开交叉编译。我们产品线包含Cortex-M33裸机固件、Cortex-A35 Linux用户态程序、以及跑在网关上的边缘计算模块三套代码需要三套工具链环境统一非常关键。Cortex-M33固件我们选用arm-none-eabi-gcc 13.2.rel1工具链的下载和配置比较简单注意把bin目录加进PATH同时用-mcpucortex-m33 -mthumb -mfloat-abihard -mfpufpv5-sp-d16指定CPU和浮点特性不然链接时会报错或生成为错误的指令集。这个架构相关的选项不能写错Cortex-M33的FPU是单精度FPv5如果编译时用了-mfloat-abisoft很多浮点库就不会用硬件加速导致性能大打折扣。Cortex-A35 Linux用户态程序使用arm-linux-gnueabihf-gcc或aarch64-linux-gnu-gcc取决于用户态是32位还是64位。我们的网关用户态是64位所以用aarch64-linux-gnu-gcc。工具链官网的下载包比较大但整个配置过程不复杂关键是设置好sysroot和交叉编译的pkg-config路径否则编译时找不到目标板的依赖库。如果你做的是带了Qt界面的边缘设备编译流程会更复杂一些。交叉编译Qt5时必须明确指定设备类型、GCC版本和sysroot而且Qt的mkspec要额外调整。我们花过一段时间写了一套交叉编译Qt5的配置脚本把qmake、moc、rcc、uic这些工具全部指向目标平台的交叉版本。这个配置不处理好最常见的报错是运行时提示/usr/lib/arm-linux-gnueabihf/libstdc.so.6: version GLIBCXX_3.4.21 not found本质就是编译侧和目标侧的C标准库版本不一致。工具链这块最需要注意的版本一致性问题是编译用户态程序时用的libc、libstdc、libssl等库版本必须和目标板系统里的一致。如果你的目标板是Debian/Ubuntu的ARM镜像或者用busybox裁剪的系统建议在制作根文件系统时就固定好这些库的版本后续编译任何程序都基于同一个sysroot能避免大量的ABI兼容问题。busybox的交叉编译我顺手提一下在busybox源码根目录执行make CROSS_COMPILEaarch64-linux-gnu- ARCHarm64 defconfig make CROSS_COMPILEaarch64-linux-gnu- ARCHarm64 -j$(nproc)就行但要确认menuconfig里各项配置满足你的应用场景比如是否打开udhcpc、telnetd等busybox默认配置经常不够用。4.2 为Cortex-M33裁剪并配置mbedTLS安全通信栈安全IoT设备既要支持本地加密存储也要支持与云端的安全通信。Cortex-M33平台上我们使用mbedTLS现在叫Mbed TLS作为TLS实现它体积小、可裁剪性强在微控制器领域几乎是行业标准。但直接把PC上的TLS配置搬过来是不行的。Mbed TLS提供了非常细致的编译宏必须根据目标MCU的资源情况裁剪。我们用的Cortex-M33芯片有2MB Flash和512KB RAMTLS全部功能打开根本塞不下。实际裁剪思路如下只启用TLS 1.2关闭SSLv3、TLS 1.0、TLS 1.1和DTLS如果不用UDP则关闭DTLS以省Flash密钥交换算法只保留ECDHE-RSA和ECDHE-ECDSA关闭DHE和RSA key exchange对称算法只保留AES-128-GCM和AES-128-CCM关闭CBC模式因为GCM是AEAD安全性更好且性能更好椭圆曲线只保留secp256r1P-256并启用MBEDTLS_ECP_DP_SECP256R1_ENABLED启用MBEDTLS_SSL_PROTO_TLS1_2、MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED等宏关闭所有用不到的宏/* mbedtls_config.h 关键裁剪项 */ #define MBEDTLS_SSL_PROTO_TLS1_2 #define MBEDTLS_AES_C #define MBEDTLS_GCM_C #define MBEDTLS_ECDHE_RSA_C #define MBEDTLS_ECP_DP_SECP256R1_ENABLED #define MBEDTLS_ECP_C #define MBEDTLS_BIGNUM_C /* 可选优化 */ #define MBEDTLS_SSL_MAX_CONTENT_LEN 4096 #define MBEDTLS_SSL_IN_CONTENT_LEN 4096 #define MBEDTLS_SSL_OUT_CONTENT_LEN 4096 /* 调试时开发布时关 */ // #define MBEDTLS_DEBUG_C连接方式上设备与云端之间的TLS握手需要设备端存放根CA证书和自己的设备证书。根CA证书直接编进固件设备证书可以预置也可以首次启动时通过安全通道申请。预置证书的问题在于密钥备份和更新很麻烦我们最终采用了首次启动注册的方式设备首次上电后通过TrustZone保护的密钥对生成证书签名请求通过带外预置的注册密钥和云端CA完成签发之后TLS正常使用这个证书。安全和性能之间需要平衡。TLS握手在Cortex-M33上做一次ECDHE-RSA握手大约需要1到2秒这个时间在非交互式IoT设备联网时完全不是问题但要注意不要在握手期间看门狗超时复位。我们在网络线程里把握手的超时设成了10秒保证弱网环境下能正常完成。4.3 ARM Linux网关上用OpenSSL做证书管理和安全通信Cortex-A35网关跑Linux安全通信栈直接使用OpenSSL。但这里有个很重要的经验OpenSSL的版本选择直接决定安全基线。我们当时调研后确定至少用OpenSSL 1.1.1系列这是维护周期最长且广泛用于嵌入式系统的版本。如果是新项目直接用OpenSSL 3.x它引入了provider机制加密算法可以按需加载对系统的安全更新更友好。证书管理上网关作为边缘设备需要双向认证与云端通信同时还要管理子设备的证书形成一个信任链。我们要做的核心工作包括CA证书的轮换、设备证书的签发和吊销、以及密钥的存储保护。全部放文件系统不安全后来把私钥放到了安全存储区也就是ARM TrustZone保护的存储分区或者专用安全芯片里。OpenSSL通过engine或provider机制对接硬件密钥存储让私钥运算在安全世界完成不暴露给Linux用户态。回看我们的实现网关主控Cortex-A35用OPTEE提供的TATrusted Application来管理密钥OpenSSL通过custom engine调用OPTEE里的安全密钥操作。这套架构的好处是即使Linux用户态被攻破攻击者也拿不到私钥明文只能调用签名和验签接口。代价是开发和调试复杂度上了一个台阶但对于需要高强度安全的边缘网关设备这是必须付出的成本。4.4 AWS IoT OTA升级按用户策略控制固件下发与防回滚设计安全IoT另一个核心环节是OTA升级。这里以我们接入的AWS IoT服务为例关键是设计好OTA的用户策略确保只有符合安全条件的设备才能从云端拉取新固件并能安全完成升级验证。AWS IoT的OTA功能基于Job服务。设备端订阅指定的MQTT主题云端下发Job文档文档里包含固件的下载URL、版本号、校验和、签名值等元信息。设备拿到Job文档后先下载固件到临时分区校验签名和版本再切换启动标志重启后由Bootloader完成最终固件切换。这个过程的安全性取决于两个东西一是签名校验是否可靠二是Job策略是否最小化权限。AWS IoT策略的权限控制可以做到很细。我们典型的一条策略是允许设备接收$aws/things/{thingName}/jobs/*主题但只允许访问与自身设备名匹配的主题不允许通配符访问其他设备的消息。同时设备只能读取自己的Shadow数据这样即使设备被攻破横向移动的路径也基本被切断。{ Version: 2012-10-17, Statement: [ { Effect: Allow, Action: iot:Connect, Resource: arn:aws:iot:region:accountId:client/${iot:Connection.Thing.ThingName} }, { Effect: Allow, Action: iot:Subscribe, Resource: [ arn:aws:iot:region:accountId:topicfilter/$aws/things/${iot:Connection.Thing.ThingName}/jobs/* ] }, { Effect: Allow, Action: iot:Receive, Resource: arn:aws:iot:region:accountId:topic/$aws/things/${iot:Connection.Thing.ThingName}/jobs/* } ] }设备端固件升级流程我用伪代码描述一下核心判断逻辑bool handle_ota_job(const char *job_doc) { /* 1. 解析job文档获取固件版本号、下载URL、签名 */ char *version parse_version(job_doc); char *url parse_url(job_doc); uint8_t *signature parse_signature(job_doc); /* 2. 版本号必须大于当前版本这是防回滚的第一道闸 */ if (compare_version(version, current_fw_version) 0) { return false; } /* 3. 下载固件到临时分区下载完成后用公钥验签 */ download_to_staging(url); bool sig_ok verify_firmware_signature(staging_buf, signature); if (!sig_ok) { return false; } /* 4. 计算哈希确认固件完整 */ bool hash_ok verify_firmware_hash(staging_buf); if (!hash_ok) { return false; } /* 5. 设置启动标志重启后Bootloader切换分区 */ set_boot_partition(BOOT_PARTITION_NEW); system_reboot(); return true; }OTA升级失败是非常常见的问题尤其是弱网环境下下载中断、断电导致写Flash失败、签名不匹配等。我们后来增加了断点续传、双分区备份和启动失败自动回滚机制。具体来说就是设备至少要有两个固件分区当前运行的是A分区新固件下载到B分区下载完成后标记B分区可启动在Bootloader里校验B分区的签名和哈希校验失败则回滚到A分区。这套机制做扎实后OTA升级就算在客户现场断电也不会把设备变成砖。4.5 密钥管理与安全存储的实践经验安全IoT产品最核心的资产是密钥私钥一旦泄露所有安全机制就形同虚设。我在实际项目中踩了不少坑这里总结几条关键经验。第一条是密钥分级管理。设备端至少要分成三层设备唯一私钥、固件签名公钥、以及云端通信证书私钥。设备唯一私钥用于证明设备身份出厂时烧录固件签名公钥用于验证OTA固件签名云端通信证书私钥用于TLS可以在首次启动时通过设备唯一私钥签发也可以用预置方式。我建议分层管理不要一把钥匙通吃所有场景否则任何一把钥匙泄露都等于全盘失守。第二条是私钥尽可能不出硬件。ARM TrustZone保护的安全存储区或者硬件加密引擎都可以实现这点。Cortex-M33平台可以用TF-M里面的Secure Storage服务把私钥放在受TrustZone保护的Flash区域。Cortex-A35平台用OP-TEE的Secure Storage或者在OpenSSL里通过engine对接加密引擎。如果实在没有硬件安全存储能力至少也要把私钥放在文件系统的加密分区里并限制读取权限绝不能明文躺在根文件系统里。第三条是密钥轮换与吊销。很多团队做完一次性密钥烧录后就再也不会去管它但产品生命周期长密钥总有泄露风险。密钥轮换机制要提前设计到产品架构里比如设备端支持云端通过安全通道下发新的通信证书并替换旧证书固件签名公钥则通过安全OTA升级一并更换。这在实际项目里比想象中麻烦因为要避免轮换过程中设备掉线或中间人攻击但如果不做轮换一旦密钥在市场上泄露就只能批量召回设备了。5. 常见问题与排查技巧实录5.1 安全启动失败类问题速查安全启动失败是ARM安全IoT开发里最常见的故障可能的原因非常多而且排查链路长。这里整理成表格方便大家对照定位。现象可能原因排查方法解决方法设备上电后完全不启动BootROM没找到有效Bootloader用调试器确认BootROM执行状态查看复位向量核对Bootloader地址是否正确烧录Bootloader启动后反复复位签名验签失败或跳转地址错打开Bootloader的调试串口日志用正确私钥重新签名核对应用入口地址不能进入安全世界SAU/IDAU配置错误或NSC区域错误查看安全属性区映射确认SAU各区的地址和属性重新配置SAU确认NSC区域包含所有跨世界调用函数入口应用固件校验失败固件头部哈希或签名计算方式不匹配检查签名脚本和Bootloader验签算法是否一致重点看SHA256哈希的输入范围统一签名脚本和验签代码的输入范围建议固定为一个文件OTA后设备变砖但可恢复OTA只写了一个分区且没有回滚机制检查分区表安排看是否有双分区备份增加双分区与启动回滚能力SWD连接失败的问题也比较常见。如果SWD完全连不上目标首先查目标板供电和复位电路是否正常很多芯片需要拉低复位引脚才能进入调试模式。其次是检查SWCLK和SWDIO是否接反SWD线序在杜邦线连接时非常容易搞反。最后是检查是否曾经烧录过把SWD引脚复用成GPIO的程序如果SWD引脚被复用且当前程序没有释放调试口就需要用启动模式引脚进入BootROM模式再连接。5.2 Keil如何兼容C51和ARM双平台开发很多工程师同时维护51单片机和ARM项目Keil的C51和MDK-ARM两个版本可以共存但有个安装顺序和路径的坑。它俩不能装进同一个安装目录安装时路径必须分开比如C盘下Keil_v5和Keil_C51两个目录。安装完成后需要分别设置各自的许可证和工具链路径。如果你用Keil同时打开C51和ARM工程建议在工程文件里显式指定编译器版本避免Keil自动选择错误。比如C51工程在Options for Target对话框里选C51编译器ARM工程选ARM Compiler 5或6。ARM Compiler 6基于Clang代码体积和优化比ARM Compiler 5好但部分老的CMSIS库可能不兼容我们Cortex-M33新项目直接用ARM Compiler 6老项目过渡时先用ARM Compiler 5做兼容测试。5.3 移植到ARM新架构后的崩溃与性能排查建议从老平台迁移到Cortex-M33或Cortex-A35后最容易出问题的几个地方值得重点排查。第一是Cortex-M33对未对齐访问的处理比Cortex-M3/M4严格部分未对齐的指针访问会触发HardFault代码里如果有手写的数据包解析逻辑需要检查结构体内存对齐。我习惯在每个结构体定义后加_Static_assert来做编译期对齐检查。第二是Cortex-M33的SysTick优先级问题。TrustZone场景下如果SysTick被配置为非安全中断而安全世界也在使用SysTick两者会冲突。我遇到过一次安全世界看门狗失效排查很久发现是SysTick Timeout被非安全世界反复重置导致安全世界的中断一直没触发。解决办法是把SysTick等时间基准统一放到安全世界管理非安全世界通过API获取时间。第三是M系列内核的MPU配置。MCU的MPU如果配置不当可能把某段非安全内存配置成不可执行导致应用跳转到该区域时直接触发异常。排插问题时先确认当前MPU配置有时最直接的排查方式是先关闭MPU看是否能跑通确认是MPU导致的之后再逐条配置。5.4 arm-none-eabi-gcc与商业编译器混用时的linker脚本兼容性问题我们团队在切换编译器时发现一个很隐蔽的问题在不同编译器和链接器之间切换时链接脚本里有一部分配置对Clang和GCC的语义理解不同。比如ARM Compiler 6基于Clang默认使用__attribute__((section(.bss.noinit)))指定不初始化数据段而GCC可能对同样的section名字处理方式不同导致内存重叠或数据丢失。后来我们统一了链接脚本格式把所有的section配置特意对齐到双方的语法并在CI里增加了一道编译验证每次代码提交同时用ARM Compiler 6和arm-none-eabi-gcc各编译一次确认产出固件在功能一致的前提下链接后的section布局一致。这个方法不复杂但能有效避免本地编译正常、CI里编译产物无法启动这类隐蔽问题。6. 性能开销与安全配置平衡量化数据与调优建议6.1 实际测试数据用了安全特性后性能损耗多少很多人担心引入安全特性会拖慢设备我们专门做了一轮基准测试。以Cortex-M33平台为例未启用TrustZone时一段纯计算代码运行时间为100ms启用TrustZone后安全世界和非安全世界的切换需要消耗一些周期但整体运行时间仅增加了不到5%。这个损耗主要来自两部分的跨世界调用例如每个加解密操作要从非安全世界陷入安全世界再返回。再来看通信性能。启用TLS 1.2且使用ECDHE-RSA握手时Cortex-M33在没有硬件加密加速的情况下一次完整握手耗时在2到4秒这对非交互式设备完全够用如果启用硬件AES加速对称加密阶段几乎不占用CPU。但要注意的是TLS握手期间CPU占用率接近100%如果同时还有传感采集任务最好把握手优先级调低或者将握手放到任务调度器允许抢占的时段执行。Cortex-A35网关上的测试数据更直观。启用PAC和BTI后我们跑了一轮sysbench CPU基准和多线程网络测试性能损耗大约在2%到4%。作为对比如果不启用PAC/BTI设备在公网环境下每天被扫描尝试的次数可能多达几十次一次控制流劫持成功就能导致整机失陷相比之下这点性能损耗完全值得。6.2 安全配置臃肿问题排查与精简建议安全配置不是越多越好配置过多可能导致资源浪费甚至引入新的攻击面。我们有一次在网关系统里同时启用了SELinux、AppArmor、防火墙、入侵检测、多个安全模块结果整机内存占用暴涨CPU持续高负载且不安全因为规则之间互相冲突反而出现盲区。后来做了一次安全配置精简按资产重要程度重新排列优先级只保留了SELinux强制访问控制、系统级防火墙、日志审计和内核的PAN/KASLR等基础防护。系统负载大幅下降安全性反而提升了。如果你遇到安全配置导致系统运行缓慢或者功能异常建议先做减法。逐个关闭安全模块确认问题是否缓解再针对性地优化。安全是一个系统性问题不是组件越多越好而是每个组件都要在恰当的位置发挥着不可替代的作用。6.3 ARM固件与系统镜像的安全加固清单最后整理一份我们在ARM IoT设备上线前必做的加固清单给各位参考确认调试口处于锁定状态能禁用就禁用必须保留的售后口要有强认证确认所有固件镜像都有签名签名私钥离线保管不放在CI服务器上确认启动链每一级都做了完整性校验BootROM → Bootloader → 应用固件逐级验证确认文件系统只读挂载运行时数据写入独立可写分区且该分区开启加密确认系统服务最小化删掉不用的shell、telnetd、FTP、调试接口确认SSH配置使用证书登录禁止密码登录修改默认端口确认所有通信使用TLS并且证书有效期和设备生命周期匹配设置好自动续期机制确认设备端日志不记录敏感信息比如证书私钥、会话票据、明文密码确认OTA升级支持双分区和回滚升级过程中断电不丢固件这份清单虽然简单但能覆盖90%以上IoT设备的基础安全问题。安全建设没有终点每一层防护都可能被绕过但纵深防御的价值在于攻击者需要同时攻破多层才能拿下设备成本和门槛就会成倍上升。最后说一点个人体会。安全技术选型和实现最忌“为安全而安全”。TrustZone、PAC、安全启动这些能力都要结合产品实际威胁模型来设计。比如一个温度传感器节点攻击者物理接触设备的概率极低你给它上全套Cortex-A级别安全方案成本反而成了短板反过来一个暴露在公共场所的支付终端如果还停留在“加密一下通信”的层面那设计本身就是失职。想清楚设备将在什么样的环境下运行、被谁使用、被什么级别的攻击者盯上再决定安全投入的方向和深度这才是ARM新架构带给我们最大的价值。
返回列表