结合下面的时序图来看。当第一个地址address0出现在HADDR信号线时,黄色的keystream0就开始进入计算周期了,经过11个周期后,keystream计算完成就绪,可参与和输入明文的异或,那么一个时钟周期后,异或的结果,就是密文就一个word一个word的出现在HRDATA线上了。可见,从读、写访问里的地址信号,到实际数据结果就绪,需要差不多12个时钟周期的延迟,这段时间内HREADY信号是被OTFDEC拉低的,因为OTFDEC模块没有数据可以传输。对下一个16字节数据块的加密,由于将要存放的地址不同,address_1,需要的keystream也不同,需要重新计算。
但是由于我们有keystream buffer,在第一批16字节数据产生并输出的时间内,OTFDEC可以开始产生下一个keystream了,就是图中绿色的部分。这样,当第一批数据输出后,keystream1也计算完毕,那么第二批密文,也可以一个word一个word地出来了。
有了关于OTFDEC初步的一些理论知识,我们在继续深入下去之前,先来运行第一个示例代码,体会一下OTFDEC最基本的一个功能:对存储在外部memory的加密数据和加密代码,实时的在线解密使用,体会一下怎么个“on the fly”法。
我们使用STM32CubeL5固件包里,L562-DK板子下的两个例子。DataDecrypt,是对外部密文数据直接使用;
ExecutingCryptedInstruction,是对外部密文代码直接执行。
在这两个例子中,都是先把预先准备好的密文,使用普通的方式,调用外部memory驱动写进去。然后OTFDEC粉墨登场,我们对它做适当的配置。配置好了以后,CPU就可以直接使用外部的这些数据和代码了,就跟使用片内Flash的明文数据和明文代码一样。
我们先来运行一下例程。
接下来我们再看看,OTFDEC在TrustZone环境下的使用。
TZ=0时,OTFDEC就是一个普通的外设;当TZ=1时,OTFDEC是一个secure外设,它的寄存器只能被安全世界代码访问,因此它只能被安全世界代码来配置。但是,一旦配置完成,无论是安全世界代码,还是非安全世界代码,访问Octo-SPI片外Flash时,如果目标地址是在被OTFDEC管辖的区域里,都可以享受OTFDEC模块的实时on the fl地y解密服务。
在TrustZone环境下,对存储区进行访问,访问规则是一直要特别需要注意的。Octo-SPI片外Flash区域的安全属性由MPCWM来控制,缺省状态片外memory都是安全的,只能由secure trasanction访问;用户可通过选项字节的MPCWM以water mark的方式,划分出部分非安全区域,来被non secure transaction访问。所以,当非安全代码要想访问到外部Flash的安全区域,必须通过安全世界通过NSC区域暴露出来的API调用,切换到安全世界,发出secure transaction,才能完成。
一旦访问请求通过了内核的IDAU/SAU第一级检查,也通过了GTZC模块中MPCWM子模块的第二级检查,对片外Flash的密文访问,就可以享受OTFDEC的实时解密服务,无论此时这个访问是一条secure transaction,还是一条non secure transaction。
之前我们一直在讲OTFDEC模块的缺省全局工作模式,解密功能,这是它的初心不假。OTFDEC也是一个斜杠青年,它也可以工作在加密模式。AES属于”对称“加密技术,加密操作和解密操作是对称的,因此对于OTFDEC模块的加解密硬件引擎来说,这两种操作完全一样。但是我们作为用户有一点一定要注意的就是,前面也提过多次,当OTFDEC模块工作于加密模式,它的输出和Octo-SPI是断开的,就是说经过它加密后的密文,并不是直接被写到了外部memory里,而只是输出到RAM中由用户指定的地方。
密文生成后,用户要自己调用Octo-SPI flash的驱动来把密文数据写到外面flash中。整个过程,凡是涉及到对OTFDEC寄存器的操作,在TZ=1环境下,必须都得由安全代码来执行。
至于从用户角度如何来使用OTFDEC的加密功能,设置了模块全局工作模式,ENC=1,之后需要把明文数据喂到哪里去,再从哪里得到加密后的密文呢?从胶片左边的流程图我们可以看到两个关键的操作步骤:写明文,读出密文。那么,到底是往哪里写?从哪里读呢?
STM32L5的参考手册上是怎么写的呢?请看胶片里下面这一段话:应用代码,把明文数据,32位32位地写到期望被保护的区域,当然这里是指的0x9000 0000开头的Octo-SPI Flash上的地址,然后从相同的地址读出来放到RAM中。那么RAM中读出来的32位数据就是加密后的密文了。
这个操作不是非常常规,L5的HAL库函数对其进行了封装,就是胶片里的HAL_OTFDEC_Cipher()。
调用这个函数做加密,用户需要提供5个参数;首先是使用的OTDEC句柄,这个没啥好说的,和任何一个外设模块的使用都是一样的;第二个参数,指定使用OTFDEC管理的哪个region。一个4个region,不同region使用的密钥是不同的;第三个和第四个参数就是明文指针,和密文将输出在RAM中的位置指针;第五个参数,这段密文拟放在外部区域的地址。我们前面在介绍AES加解密硬件引擎工作原理时就展现过胶片右边这张框图。每次16字节数据块做加密或者解密运算时,初始化向量的低位包含的就是密文所在的外部区域地址信息。
这个HAL API里的实现,就是对前一页胶片两个节点方块的解释:写明文,读出密文。往哪里写?从哪里读?就是往外部memory的地址写,再从那里读,这样一写一读,读到RAM里数据,就是加密后的密文了。
基于刚才讲的这两点,加密操作,以及OTFDEC在TrustZone环境下的使用要点,我们来运行并分析一下另外一个样例程序。
我们使用STM32CubeL5固件包里,L562-DK板子下的第三个例子。OTFDEC_Ciphering_TZ。
在实际运行之前,先简要介绍一下这个例程。中间灰色框里是上一个例程,在普通运行环境下,即TZ=0情况下,OTFDEC工作于解密模式下的操作流程。放在这里,做一个参照,看看在工作于加密模式时,用户的操作流程会有什么些许不同?并且还可以从普通运行环境和TZ=1这个角度去对比一下。
TZ环境下的应用,上电先运行安全世界里的代码:系统初始化,这次多加了一个ICACHE的使用,然后分配一个GPIO引脚到NS世界,共non secure的应用操控。然后马上把OTFDEC设置到加密模式
。这是hands on1没有的。在hands on 1里,由于已经预先准备好了密文,所以第二步是蓝色框里的,调用Octo SPI flash驱动把密文写进去。但是在我们现在这个例程中,密文需要先靠自己产生。那么就是绿色框里的,配置OTFDEC管理的某个区域,使用标准AES加解密算法,配置密钥以及其他加密需要的上下文数据。然后,就可以调用前页介绍的OTFDEC cipher API,对明文Plain[]数组内里的内容加密,并输出到Ciphered[]数组中。
加密完成后,把OTFDEC工作模式切换回默认的解密模式。然后才是蓝色方框里,调用Octo SPI flash驱动把密文写到外部flash中去。到此为止,配置OTFDEC的某个region,使用它来加密,并将密文写入外部flash,都是有安全应用完成的。接下去,跳转到非安全应用去执行。它将通过对外部memory的地址直接访问,读出明文数据。
我们来实操一下
我们看到了,在NS世界想通过外部地址直接访问,在这个例程里是通过NSC区域暴露出来的安全应用调用接口API完成的。这正是因为要遵循TZ环境下对存储区的访问规则。由于Octo-SPI外部 flash区域在缺省状态下处于Secure状态,例程里并没有通过MPCWM来改变其上某些区域的安全属性,因此Non Secure的代码是无法访问它的数据和指令的。
所以,如图所示,OTFDEC模块on the fly地开始解密时,CPU要读取0x9000 4000地址的数据,到非安全SRAM的数组中,由于源地址处于安全区域,非安全世界的代码无法访问,因此需要通过NSC API,去到安全世界来执行图中箭头表示的拷贝操作。而当切换到安全世界之后,CPU处于安全状态,读取0x9000 4000这个CPU看来的安全地址,是一个secure的transaction,因此可以触碰到这个本来物理区域属性也是安全的源地址;
访问0x2001 8010时,该地址在SAU那里已经被配置成了非安全区,因此即使此时CPU处于安全状态,发出的transaction仍然是non secure的,因此可以触碰到这个物理区域属性也是非安全的地方。所以拷贝操作可以正确执行。
进入NonSecureCallable的API后,第一件事是使用cmse_check来对从NS世界传过来的指针进行检查,传过来两个指针,一个是源地址,一个是需要写入的目的地址。是对目的地址指针进行检查。检查的不仅仅是一个指针变量,而是该指针变量和对应的拷贝长度,涉及到的一整段地址区域。
这个被写入的区域,或者说将被改写内容的区域,只能处在非安全世界,即红色的区域,不能处于安全区域,也不能跨越非安全区域和安全区域。总之一句话就是:不允许非安全应用,通过安全代码,把安全世界的区域给修改了。这是大家自己在写安全应用暴露给非安全应用的API时,一定要注意的地方。
回过头来看,这个例程给我们展现了TZ环境下使用OTFDEC的一个典型用法:密钥放在安全区域,只能由安全应用来配置;非安全世界的应用,无需密钥交换也可以享受解密服务。
经过前面两个hands on环节的三个样例程序的学习,在第三个hands on,我们自己来写一段程序,实现使用ST私有AES算法,对一段明文代码进行加密,写到外部Flash后,再使用OTFDEC的在线解密功能,实时对其取指执行。
我们使用hands-on 1里面现成的一段指令,就是执行R0+R1+15的计算。那个例程里我们可以拿到该段指令的明文,就是08,18,0f,30,70,47这六个字节。
从hands-on 2我们学习了如何使用OTFDEC的加密功能。这两个hands on都是使用标准AES算法
把这两个hands on结合起来,我们现在要做的hands on3,就是让OTFDEC先,后工作于加密模式和解密模式,对明文指令加密,写入flash后,再执行密文代码。并且,我们将改变OTFDEC加解密硬件引擎的算法,从标准AES改到ST私有AES算法。
先列一下流程图:最开始也是系统的初始化,我们可以尽量重用hands-on 1和hands-on 2的代码。使能OTFDEC模块全局工作模式为加密功能。选定一个region,配置region要管理的外部flash的地址范围、该region使用的对称加解密密钥和相关加解密上下文。
调用现成的OTFDEC cipher加密API,对6字节明文指令,转换成密文。密文生成完毕后,把OTFDEC模块的全局工作模式再切换为解密功能,配置Octo-SPI接口,把密文指令写到外部flash对应的地址范围中。通过外部flash的地址,直接做函数指针调用,来执行外部的加密指令。
在执行add15这个存储在外部flash的函数指令时,请从IDE的汇编窗口或者memory窗口去观察。你会看到,窗口里显示的不是想象中的明文二级制代码,而全部都是0。回忆一下前两个hands on环节里,在配置了OTFDEC模块的解密模式,使能了外部memory的memory map,明明都可以从IDE窗口看到明文数据或者明文代码啊?这里为什么都是0,那还能执行正确吗?
首先,在你单步执行完这个add15的函数后,可以验证一下,它确实返回了正确的累加值。然后,请想一想为什么在IDE汇编窗口看到的都是0呢?
原因就在于,我们使用的是ST私有AES算法,它的宏的名称就叫做 “instruction access only”,在L5参考手册,对应寄存器的描述之处,也写明了“只对指令访问解密”。而我们的IDE窗口去读取时,显然是数据访问,因此无法得到解密后的明文数据。
最后,我们再总结一下,OTFDEC它实际作为一个加解密硬件引擎,系统对其密钥、和所保护外部flash区域的一些系统级别保护。
首先,OTFDEC管理的每个region所使用的AES对称密钥寄存器,是只写不可读,从而保证key的机密性;为了用户确保key被正确写到寄存器里,通过密钥CRC值位域,来保证key的完整性。
当系统检测到tamper入侵事件,或者OTFDEC所管理的region的工作模式改变,密钥都会被硬件擦除。RDP降级,无论是否有TZEN,也都会把密钥擦除。
外部flash存储的密文,如果攻击者想通过调试模式、或者从ram启动注入代码,或者从系统bootloader启动,企图通过OTFDEC的解密引擎来读取,在RDP读保护非0级别下,都无法成功。
并且,系统在检测到tamper入侵事件后,无论从哪里启动,什么访问模式,什么读保护级别,也都无法通过OTFDEC解密外部flash的加密内容。
通过这一集的学习,大家应该了解并体会到:STM32L5上,OTFDEC模块的集成,可以让客户把大段用户代码加密后放在外部Flash,从而保护敏感代码的机密性。通过对OTFDEC的正确配置,从外部Flash执行代码,对CPU来说就像是执行片上Flash里存储的明文代码一样。
这样,代码安全存储的范围,就可以从片上Flash扩展到安全的片外做存储和执行,突破了STM32L5 512K片上Flash的容量限制。
这一集的内容就讲解完毕,谢谢大家观看。
|