secure boot(三)secure boot的签名和验签方案
0赞简介
FIT 格式支持存储镜像的hash值,并且在加载镜像时会校验hash值。这可以保护镜像免受破坏,但是,它并不能保护镜像不被替换。
而如果对hash值使用私钥签名,在加载镜像时使用公钥验签则可以保护镜像不被替换。因此,公钥必须保存在一个绝对安全的地方。
接下来的内容要求大家了解一些密码学的内容,之前也介绍过一些,可以看这篇文章
secure boot签名的大致流程:
计算镜像的hash值
利用私钥对hash值签名
签名结果存在FIT Image 中。
secure boot验签的大致流程:
读取FIT Image
获得pubkey
从FIT Image 提取签名
计算镜像的hash
使用公钥验签获得hash值,与计算得到的hash值进行对比
签名是由mkimage工具完成的,验签由uboot完成。
签名算法
原则上讲,任何合适的算法都可以用来签名和验签。在uboot中,目前只支持一类算法:SHA&RSA。
RSA 算法使用提前准备好的公钥就可以完成验签,验签相关的代码量也很少。在验签时,RSA只是在FDT中提取必要的数据进行校验。
当然也可以在uboot中添加合适的算法,如果有其他签名算法(如DSA),可以直接替换rsa.c
,并在image-sig.c
中添加对应算法即可。
创建RSA key和证书
openssl 创建一副2048的密钥对:
$ openssl genpkey -algorithm RSA -out keys/dev.key -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537
创建包含pubkey的证书:
$ openssl req -batch -new -x509 -key keys/dev.key -out keys/dev.crt
查看pubkey的值:
$ openssl rsa -in keys/dev.key -pubout
绑定设备树
在FIT Image的签名节点中需要添加以下 属性,签名节点与哈希节点处于同一级别,被称为signature@1, signature@2
等。
algo: 算法名称
key-name-hint:用来签名的key。密钥对必须存放在单独的文件夹(mkimage 使用-k 参数指定),私钥被命名为
<name>.key
,证书命名为<name>.crt
。
镜像被签名后,以下这些属性都会被自动强制添加:
value: 签名后的值(RSA-2048 占256 bytes)
以下这些属性是可选的:
timestamp:签名的时间
signer-name:签名者的名字(例如mkimage)
signer-version:签名的版本(例如"2013.01")
comment:签名者或者镜像的额外信息
sign-images:签名镜像的列表
hashed-nodes:签名者签名的节点列表,一般是包含节点完整路径的字符串。例如:
hashed-nodes = "/", "/configurations/conf@1", "/images/kernel@1",
"/images/kernel@1/hash@1", "/images/fdt@1",
"/images/fdt@1/hash@1";
以下是一个待签名镜像的its配置。
以下是配置项签名后的its文件。
以下是配置项签名后的its文件。
pubkey的存储
为了校验签名后的镜像,必须把pubkey存放在可信赖的位置。将pubkey存在镜像中是不安全的,很容易被破解。一般我们将其存放在uboot的FDT中(CONFIG_OF_CONTROL)。
pubkey应该作为一个子节点存放在/signature
节点中。节点中要加上以下特性:
algo:算法名称
key-name-hint: 签名使用的key的名称
required: 校验某配置所使用的公钥
除此之外,每个算法都有一些必要的特性。RSA算法中,以下特性必须被添加:
rsa,num-bits:key的位数
rsa,modulus:N,多字节的整数
rsa,exponent:E,64位的无符号整数
rsa,r-squared:
(2^num-bits)^2
rsa,n0-inverse:
-1 / modulus[0] mod 2^32
下面看一个例子,以下是一个uboot.dtb存放RSA的例子。RSA key被mkimage打包在u-boot.dtb和u-boot-spl.dtb中,然后它们再被打包进u-boot.bin和u-boot-spl.bin。
签名方案
上一节内容提到过,在secure boot中一般使用RSA签名方案。
要完成对镜像的签名,就必须使用私钥。而私钥一般是存在服务器上的,在本地PC上只存公钥。要想完成对镜像的签名,就必须把所有镜像上传到服务器重新打包。这种方案上传的文件太多,比较繁琐。下面我们介绍一种常用的签名方案。
在PC上,存放一把公钥和临时私钥,公钥是打包进dtb中的,安全启动时使用。临时私钥是为了生成签名数据。
在本地打包时,使用临时私钥对非安全镜像签名,将签名数据上传到服务器使用真正的私钥进行二次签名。将二次签名的数据和非安全镜像打包在一起,就得到了安全镜像。安全启动时,从dtb中拿出公钥对安全镜像进行校验即可。
这样既可以保证私钥的安全,又避免了上传所有镜像签名的繁琐。
签名镜像+签名配置
在secure boot中,除了对各个独立镜像签名外,还要对FIT Image中的配置项进行签名。
有些情况下,已经签名的镜像也有可能遭到破坏。例如,也可以使用相同的签名镜像创建一个FIT image,但是,其配置已经被改变,从而可以选择不同的镜像去加载(混合式匹配攻击)。也有可能拿旧版本的FIT Image去替换新的FIT image(回滚式攻击)。
下面举个例子。
两个kernel image 都已经被签名了,但是,攻击者可以很容易的将kernel1 和fdt2 作为configuration 3去加载。
攻击者可以拿到签名的镜像,并且镜像是正确的。这种组合式攻击会给设备带来很大风险。
因此,为了解决这个问题,除了给镜像签名外,我们可以把配置选项也签名,每个镜像都有自己的签名,在给配置选项签名时,把镜像的hash值也包含进去。具体例子如下:
如上所示,除了给所有镜像添加了hash值,还为每个配置添加了签名。mkimage将会对configurations/conf@1
签名(/images/kernel@1, /images/kernel@1/hash@1,/images/fdt@1, /images/fdt@1/hash@1)
。签名会被写入 /configurations/conf@1/signature@1/value
。
验签
FIT image 在加载时会验签。如果'required' 指定了验签的公钥,则会使用这把公钥校验该配置对应的所有镜像。
为了支持FIT格式,以下配置项必须被选上。
CONFIG_FIT_SIGNATURE :使能FIT image的签名和验签
CONFIG_RSA :使能RSA签名算法
默认情况下,使能FIT Image的签名和验签后,CONFIG_IMAGE_FORMAT_LEGACY会被禁用。即FIT uboot image的只能引导FIT kernel Image。
如果需要引导legacy kernel image,需要手动添加CONFIG_IMAGE_FORMAT_LEGACY 定义。
测试
为了校验签名和验签是否正确,可以使用测试脚本test/vboot/vboot_test.sh
。下面以sandbox为例子来说明bootm的启动和对镜像的验签。
完整校验流程
OTP校验loader
那么,这种镜像校验方式有个很重要的问题,公钥存在哪里才是安全的呢?
一般SOC中会有一个叫OTP或EFUSE的区域,这部分区域比较特殊,只可以写入一次,写入后就再也不可以修改了。把公钥存储在OTP中,就可以很好地保证其不能被修改。
OTP的存储空间很小,一般只有几KB,因此并不适合直接存放RSA公钥。一般都是将RSA公钥的hash val 存放在OTP中。像sha256的hash值仅为256 bits,而RSA 公钥本身一般存放在镜像中。
在使用公钥之前,只需要使用OTP中的公钥hash值验证镜像附带公钥的完整性,即可确定公钥是否合法。
RSA公钥需要一般使用芯片厂家的工具写入loader。安全启动时,bootrom首先从loader固件头中获取RSA公钥并校验合法性;然后再使用该公钥校验SPL的固件签名。
spl校验uboot
SPL把RSA公钥保存在u-boot-spl.dtb中,u-boot-spl.dtb会被打包进u-boot-spl.bin文件(最后打包进loader);安全启动时SPL从自己的dtb文件中拿出RSA公钥对uboot.img进行安全校验。
uboot校验kernel
U-Boot把RSA公钥保存在u-boot.dtb中,u-boot.dtb会被打包进u-boot.bin文件(最后打包为uboot.img);安全启动时U-Boot从自己的dtb文件中拿RSA公钥对boot.img进行校验。
总结
从bootrom到kernel为止的安全启动,统一使用一把RSA公钥完成安全校验,并且当前这级的RSA Key已经作为自身固件的一部分,由前一级loader完成了安全校验,从而保证了Key的安全。
原文链接:https://mp.weixin.qq.com/s/eUcSPFi4OzZdAlvM-086RQ
电子技术应用专栏作家 嵌入式与Linux那些事