垂直应用

信息安全(十二)

X-Cube-SBSFU/STM32G0




大家好,欢迎大家观看 STM32 信息安全(十二):X-Cube-SBSFU/STM32G0。


前两期我们了解了SBSFU的原理,要实现这个服务,拆分下来需要打通哪些关键环节。并且在STM32G0的nucleo板上,运行一个现成的、ST实现的SBSFU软件包,以建立最初关于SBSFU的感性认识。


这一期,我们结合STM32G0上集成的具体安全模块和安全资源,看看SBSFU在G0上是如何实现的。




X-Cube-SBSFU


X-Cube-SBSFU 深入了解

以上从安全启动和安全更新的原理,到G0的安全特性如何实现这些原理,理论部分我们都了解了。并且通过G0-nucleo板子上运行SBSFU参考例程,有了一个大概的使用者角度的直观感受。接下来,我们从开发者角度,来看一下具体的项目实现。




首先希望大家明确的一点,就是X-Cube-SBSFU软件包的定位:它是一个ST开发的,针对目前嵌入式领域,对安全启动和安全升级要求越来越高,越来越迫切,而推出的一个基于STM32的,免费、开源的参考实现。大家可以根据自己的需要,裁剪后,集成到自己的应用中;或者参考它,做自己的实现,都可以。因此,我在这里花一点时间,帮大家理一下整个工程的结构、实现要点,方便大家下来进一步深入理解。


当前最新版本的X-Cube-SBSFU软件包, 2.3.0,支持的STM32系列,除了F0、F1、F2、F3,其他Cortex-M系列的都支持。L5比较特殊,它是trustzone结构,在安全启动和安全固件升级方面,ST目前是以ARM的TFM开源框架来支持。我会在L5的在线课程里专门来讲。现在SBSFU软件包支持的九大STM32系列,按照其安全特性的集成,可以分为四个大类。F4/F7/L1一类,使用MPU做隔离,在RDP、WRP的协助下实现安全启动和固件升级的目标;G0/G4/H7,在其基础之上新增了PCROP对密钥的进一步保护,secure user memory,安全用户存储区进一步隔离了整个SBSFU代码和用户应用代码;WB系列,使用独立M0内核及其专用flash区域,存储和处理对称秘钥,进一步提供密钥保存的安全性;L0和L4(包括L4+)是一类,它们有firewall来在MPU、PCROP的基础上,进一步对敏感数据和操作做隔离。


具体功能实现的分支上,用户代码单image和双image的参考实现都有,各自的好处前面也讲过,这里不再累述;软件包在做密码学相关操作时,使用X-Cube-Crypto安全包里的软实现,和使用ARM开源的mbedtls里的软实现,两种方式都支持。这里的OSC,不是oscillator,是open source crypto开源加解密的意思。另外,在L4的Iot discovery板上,集成了STSAFE,因此可用于存储证书,并提供密码学相关服务




X-Cube-SBSFU 架构概览



这是X-Cube-SBSFU参考例程,总的架构图。下面三行,没什么可说的,与所有Cube软件包一样。重点说一下上面三个部分。


之前我们在G0-nucleo板上,初次体验这个软件包的时候,看到有三个工程项目,并且依次编译。那三个项目就是这里上面的三个部分。第一个编译的工程是SECoreBin,就是中间红色的这块。它的功能就是,通过一个统一的接口,对外提供密码学相关服务;根据它的位置,可以看出它是可以对SBSFU 提供服务的;也可以对用户应用提供服务。但是在我们的G0实现上,若使能了Secure user memory,用户应用执行时就无法享用SE安全引擎提供的密码学服务了。


左上角绿色的部分,也是我们刚才按顺序编译的第二个项目。它的功能有,安全启动,检查是否有新固件,安全更新,固件的版本管理,具体就是防止用户固件版本回滚;在我们的实现里还提供了一个loader,即接收和下载新固件。右上角蓝色的部分,是我们按顺序编译的第三个项目,用户应用。从刚才的打印菜单可以看到,参考实现里做的用户业务逻辑也提供了loader功能;初次之外还有测试SBSFU模块代码,保护功能的例子等。




文件夹目录



从文件夹目录来看,在Nucleo-G071的2_image方案目录下,根目录下的readme文件,说明里三个工程的编译顺序,以及工程间的输入、输出,或者说耦合关系;下面的三个具体工程目录里,各自的readme文件,也包含了从用户角度,要运行起来这个软件包,所需要的有用信息。


比如,在第一个工程SECoreBin里,选择你要采用的加解密方案:具体就是是否对用户固件有机密性要求?对用户固件的狭义和广义完整性,采用哪种密码学功能来实现。readme文件还告诉我们,这个工程会把用户放在指定路径下的密钥文件,内嵌到一段汇编函数,然后放到PCROP保护的区域段内。


第二个工程SBSFU,readme文件告诉了我们,它主要负责安全启动和安全升级的流程管理,以及下载和运行代码前,芯片需要处在什么样的选项字节状态。


第三个工程UserApp,readme文件提供了这个模拟的用户应用的业务逻辑是什么?下载新固件,测试SBSFU的保护机制。通过postbuild脚本,还要生成UserApp.sfu文件,和SBSFU_UserApp.bin。这两个文件,刚才在初体验环节,大家已经看到它们分别的用途。




X-Cube-SBSFU 项目间的耦合



这三个项目为什么一定要按顺序编译呢?因为它们之间有着紧密的耦合关系。为了方便后面的识别,我们遵循刚才讲X-Cube-SBSFU架构那张图里用到的三个项目的颜色。


SE是最敏感的部分代码,这里我用红色标识该项目;SBSFU是为了帮助客户解决安全方面的问题,我就用绿色标识;UserApplication,是普通的用户应用,我用蓝色标识。


SECoreBin工程,使用prebuild脚本把密钥数据转成可执行的代码,这段代码通过SECoreBin工程的linker文件,和SE 其他组件代码,放在左图中蓝色的的部分。同时这个prebuild脚本还会生成postbuild脚本,主要是把内嵌到MCU里的对称加密密钥,以及验签用的公钥对应的做签名的私钥,放到postbuild脚本。这样,第三个工程,就可以把原始的UserApp.bin,加上固件版本信息、固件摘要等,作为固件的元数据,并对此签名,从而一起构建出对应的UserApp.sfb文件。


第一个工程SECoreBin的所有二进制内容,作为库,被链接到第二个项目SBSFU里。看到中图里蓝色的部分。然后第二个工程自己生成的内容,是浅褐色的部分。中图,这就是我们之前初体验时,首次下载了SBSFU.bin后,芯片里的内容。此时,下面虚线的slot1, slot0, swap扇区的内容都还是空的。


第二个工程的参考实现里,抽象出了调用SECore引擎内服务的接口,该接口也输出给了第三个工程,这样用户应用,也可以使用同样的接口,对SECore引擎的服务进行调用。




工程 · 项目


工程一(SECorebin)的输入输出



我们现在以IAR为例,对每个工程的配置进行分析。


第一个工程,SECoreBin,编译前,通过Prebuild.bat脚本,把放在SECoreBin/Binary下面,分别存放OEM对代码做AES对称加密的key,和ECC公钥的两个文件,转换成代码形式的两个函数SE_ReadKey,和SE_ReadKey_Pub,放到汇编文件se_key.s中。然后这个汇编文件,再和SECoreBin项目中的其他文件,一起编译、链接,生成SE_Core.bin。


同时还生成另外一个批处理文件,postbuild.bat,在第三个工程的时候会用到。




工程一(SECorebin)的链接文件



工程一和其他普通工程一样,指定了一个链接文件,stm32g071_flash.icf。单是这个连接文件里,又包含了其他两个链接文件,mapping_sbsfu.icf 和 mapping_fwimg.icf。因此需要在Linker的Extra option设置被包含进来的linker文件的路径。一共有三个连接文件一起决定,第一个工程输出的内容,在G071的片上flash上怎么放。


Code部分,按照功能,有四块,分别是做call gate调用门,存放数据key,启动,和其余部分。Data部分,除了SE的stack,还有一个记录“上次运行状态,是否有出错”等信息的Bootinf区域。




加解密方案



SECoreBin工程,在头文件《se_crypto_config.h》中,提供了三种对用户固件进行安全操作的密码学方案。分别是:第一,固件无需加密,使用SHA256做固件摘要,使用ECCDSA椭圆曲线签名验证固件广义完整性;第二,固件使用AES128的CBC,密文连接模式加密,同样使用SHA256做固件摘要,使用ECCDSA椭圆曲线签名验证固件广义完整性;第三,固件使用AES128的GCM模式,对固件做加密,同时生成消息认证码来作为消息认证的方式。


工程默认代码,是使用第二种方式。我们通过左边这种图,看一下这种方案,是如何实施的。


在上位机,就是开发应用代码的用户;他首先产生一对ECC公私钥对,私钥自己藏藏好,红色标注,表示是敏感的不能让别人知道的信息。公钥通过刚才讲的prebuild编译成SECorebin中的代码,烧到STM32G0芯片里。由PCROP、MPU、WRP、Secure user memory多种方式保护。然后他准备一个对称密钥,和ECC公钥一样,在build的时候,就hardcode到G0芯片里了。在上位机端,当他完成了应用代码的开发,首先把这个Fw binary使用想好的对称密钥,进行AES CBC运算,得到密文。然后,该Fw binary的摘要通过SHA256计算出来,和其他固件的信息,比如版本号、固件大小,以及AES加密固件时用到的随机数,一起放到Fw header中。整个header的内容经过SHA256得到摘要值后,使用ECC私钥对其签名。签过名的header,和加了密的应用代码,就是上部分这个蓝色框表示的,在设备运行SBSFU时,通过UART下载给到G0。


接下来是G0上的SBSFU出场表演了。它使用早先被hardcode在芯片里的对称秘钥对收到的用户代码解密,解密好先放在那里。然后使用同样是早先被hardcode在芯片里的ECC公钥,来对收到的Fw header签名值进行验签。验签通过就表示该header是完整可靠的,然后拿出里面的Fw binar的摘要,和刚解密好的用户代码做同样SHA256后的结果进行比较。匹配,就说明,该用户固件是完整可靠的。就可以install到slot0区域,然后启动它的运行了。




工程二(SBSFU)的输入输出



第二个工程,把第一个工程生成的SE_Core.bin,连接进来,和SBSFU的其他代码,包括状态机控制、存储与保护模块的使用、下载新固件等功能,一起编译、连接,生成project.bin。它就是我们在初体验的时候,用STM32CubeProgrammer或者拖拽的方式,下载到G0-nucleo板子上的第一个image。


另外,在该项目的Build Action选项里,有一条命令,使用IAR的Absolute Symbol exporter工具isymexport,根据steering_file里面的要求,输出symbol到se_interface_appli.o中。这个是后面要喂给第三个项目UserApp的。这个steering_file很简单,就是一句话:show SE_APP_GetActiveFwInfo。所以IAR的symbol输出工具就把第二个工程编译连接后产生的若干symbol,找到这一条,它就是来自这个工程里Middleware组件中的se_interface_application.c,把它的symbol输出到se_interface_appli.o。注意这个symbol文件,不要和se_interface_application.o,即该源文件的obj文件弄混淆。前者,是IAR从一个项目输出给另外一个项目用的symbol文件。




工程二(SBSFU)的链接文件



工程二和工程一相同:一共有三个连接文件一起决定 本工程输出的内容以及在G071的片上flash上怎么放。


Code部分,按照功能,有四块,分别是中断向量表,第一个工程的SECoreBin部分,调用SE服务的统一接口代码,和其余部分。Data部分,就是对于的stack,没有什么特别的地方。




工程三(UserApp)的输入输出



第三个工程,UserApp,导入了第二个项目输出的symbol文件,然后和自己的应用逻辑一起生成UserApp.bin。


经过postbuilad脚本,这个脚本是第一个工程SECoreBin,根据用户选择的加解密方案,以及对应密钥生成。用户应用代码UserApp.bin就是使用这个脚本,给自己加密,并且生成对应的header,并对header签名,再加上用户在post build命令行上指明的该用户固件版本,从而形成最终的》》sfu文件。




工程三(UserApp)的链接文件



工程三,也和前面两个工程一样:一共有三个连接文件一起决定,本工程输出的内容,在G071的片上flash上怎么放。

但是实际用户应用代码的存放很简单,就是以slot0为运行地址去连接。唯一需要注意的是,slot的前2K字节,是预留给固件header的,真正的固件image要从0x800的位移开始,因此用户应用代码的复位向量是放在slot开始的2K处。




调试 SBSFU


技巧一




说了这么多,要什么理解和厘清SBSFU的运行,最后还是离不开代码调试。


ST release出去的软件包,一般都为了减小程序所占空间,默认把优先级设置到最高。X-Cube-SBSFU里的三个工程也是如此。按照通常的思路,为了方便代码单步跟踪,会把优化都先去掉。你如果这样做,就会发现一个链接错误,生成的image大小超过了linker文件给的空间尺寸。要么,我们修改linker文件,再扩大一些。但是在现阶段,我还不建议大家这样做。刚才前面三个项目大家也看到,有3个linker文件共同作用,如果修改不小心,会造成其他错误,而当前如果你还不熟悉全部项目的结构和细节,还不大容易找打错误的地方。因此,目前我建议,可以把SBSFU工程,设置到medium的优先级,当前默认的linker文件,是放的下的,并且单步调试,问题还不大。但是如果在某个文件,medium级别的优化,也干扰了单步的跟踪,可以把这个文件,或者几个你着重关心的文件,额外修正优化级别到none,如右图所示。


另外,我们在SBSFU工程里,如何才能调试到SECoreBin的代码呢?需要在debugger选项卡的Images页面,指定SECoreBin项目的输出。注意,要选择out文件,它是带调试信息的。


如果不想连接调试器,还是想像我们刚才初体验那样,直接运行代码,才串口打印窗口,看运行步骤。可以在SBSFU工程里的app_sfu.h头文件,打开VERBOSE_DEBUG_MODE开关,就可以看到更多打印信息。




技巧二



我们在之前的初体验环节,使用代码里的默认配置,使能了所有存储和执行保护,除了Boot_Lock,如左图。只有第175行的保护,是disable了的。大家现在,以及以后学习,深入了解,调试的阶段,建议都不要轻易去碰它,只在开发、调试完成了再去使能。不过我这里要说的是其他一些保护,我们也可以在调试的时候,先关掉。


最方便的,可以在整个项目设置里,添加SECBOOT_DISABLES_SECURITY_IPS,这样STM32G0所有的存储和执行保护,都会关闭。这个适用于先不管片上存储区保护,而着重调试、跟踪,理解整个SBSFU状态机流程,包括密码学操作的使用,这种情况。




技巧三



如果不想这么粗狂,还是精细一点,那我建议可以保留部分保护,比如MPU,WRP;而暂时关闭某些保护。


比如左图:

第160行,读保护可以暂时先关掉。刚才初体验的时候,我们第二次准备走捷径,一次性烧写SBSFU的image,和用户应用初始版本的image的时候,就发现由于之前的默认代码使能了读保护1,再次使用STM32CubeProgrammer连接,用户flash的内容都看不到了,需要手动把选项字节都恢复到初始状态,才能再次烧录新程序。为了调试时候方便不断烧写新代码,可以暂时把读保护去掉。

第163行,DAP,即关闭GPIO引脚上jtag/swd等调试端口的功能复用。这样,在加上RDP没有使能,就随时可以使用调试器连接了。

第165行,WDG,这个在调试的时候一定要关闭,否则你暂停下来查看上下文,而看门狗的时钟还在运行,马上就会触发看门狗复位。

第161行,PCROP。这个保护先把它关掉,这样你可以跟踪进去,看到密钥是如何通过执行一段代码,来最终把caller需要的密钥数据,恢复到SRAM中。然后再把这个保护打开,可以体会一下,前面讲的,对PCROP保护区域调试的感觉。




检查安全环境配置



其实,存储与执行保护,还有环境监测等保护,SBSFU启动运行后,是第一个去检查和设置的,从打印的log信息可以看到,这是SBSFU启动运行后打印的第一条。


静态保护包括RDP,写保护、PCROP,安全用户存储区Secure User memory等。动态保护包括MPU、关掉DMA、启动看门狗、使能入侵检测等。每一个保护,都可以通过宏定义单独使能或者关闭。刚才初次体验SBSFU,运行的安全包里原始代码是打开了所有保护(除了Boot_Lock)。


另外,如果检查到相应保护没有被使能,根据宏定义也有两种处理方式。如果是开发模式,SBSFU的代码会自己去使能;如果是生成模式,即注释了这个宏定义,SBSFU会复位,要求客户自己去手动设置。




继续深入探索



G0上SBSFU的实现的介绍暂时打住。大家下来,就G0本身的实现,还有一些有趣和值得研究的地方。比如Secure User memory是如何激活的?在哪里执行这段代码?它要在执行应用代码之前把背后的SBSFU区域隐藏起来,但是本身又不能在SBSFU区域里执行,否则自己把自己设置为隐藏区域,代码执行都无法完成。答案是:先跳到系统BL中,并且把用户代码指针传过去,在那里调用操作secure user memory的服务,再从系统BL跳到用户代码执行。有关详情请参考AN2606。


另外SE封装了哪些敏感操作,又是如何通过interface暴露给SBSFU代码,或者用户应用去调用的?在G0芯片上的实现,SBSFU内部的代码通过MPU来做隔离,那又是如何做CPU特权级别的升级呢?大家可以后面自己去研究一下。




下一期,我会给大家介绍一下ST的另外一个服务级别的安全方案:SFI。敬请大家关注,谢谢观看。




微信扫一扫