secure boot(三)secure boot的签名和验签方案
扫描二维码
随时随地手机看文章
FIT 格式支持存储镜像的hash值,并且在加载镜像时会校验hash值。这可以保护镜像免受破坏,但是,它并不能保护镜像不被替换。
而如果对hash值使用私钥签名,在加载镜像时使用公钥验签则可以保护镜像不被替换。因此,公钥必须保存在一个绝对安全的地方。
接下来的内容要求大家了解一些密码学的内容,之前也介绍过一些,可以看这篇文章
secure boot (一)FIT Image
secure boot (二)基本概念和框架
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和证书
创建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 参数指定),私钥被命名为 .key,证书命名为.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配置。
/dts-v1/;
/ {
description = "Chrome OS kernel image with one or more FDT blobs"; #address-cells =; images {
kernel@1 {
data = /incbin/("test-kernel.bin"); type = "kernel_noload";
arch = "sandbox";
os = "linux";
compression = "none";
load =;
entry =;
kernel-version =;
signature@1 {
algo = "sha1,rsa2048";
key-name-hint = "dev";
};
};
fdt@1 {
description = "snow";
data = /incbin/("sandbox-kernel.dtb"); type = "flat_dt";
arch = "sandbox";
compression = "none";
fdt-version =;
signature@1 {
algo = "sha1,rsa2048";
key-name-hint = "dev";
};
};
};
configurations {
default = "conf@1";
conf@1 {
kernel = "kernel@1";
fdt = "fdt@1";
};
};
};
以下是配置项签名后的its文件。
/dts-v1/;
/ {
description = "Chrome OS kernel image with one or more FDT blobs"; #address-cells =; images {
kernel@1 {
data = /incbin/("test-kernel.bin"); type = "kernel_noload";
arch = "sandbox";
os = "linux";
compression = "lzo";
load =;
entry =;
kernel-version =; hash@1 {
algo = "sha1";
};
};
fdt@1 {
description = "snow";
data = /incbin/("sandbox-kernel.dtb"); type = "flat_dt";
arch = "sandbox";
compression = "none";
fdt-version =; hash@1 {
algo = "sha1";
};
};
};
configurations {
default = "conf@1";
conf@1 {
kernel = "kernel@1";
fdt = "fdt@1";
signature@1 {
algo = "sha1,rsa2048";
key-name-hint = "dev";
sign-images = "fdt", "kernel";
};
};
};
};
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。
ubuntu:~/uboot-nextdev$ fdtdump u-boot.dtb | less
/dts-v1/;
....
/ { #address-cells =; #size-cells =; compatible = "rockchip,rv1126-evb", "rockchip,rv1126";
model = "Rockchip RV1126 Evaluation Board";
// signature节点由mkimage工具自动插入生成,节点里保存了RSA-SHA算法类型、RSA核心因子参
//数等信息。
signature {
key-dev {
required = "conf";
algo = "sha256,rsa2048";
rsa,np =;
rsa,c =;
rsa,r-squared =;
rsa,modulus =;
rsa,exponent-BN =;
rsa,exponent =;
rsa,n0-inverse =;
rsa,num-bits =;
key-name-hint = "dev";
};
};
签名方案
上一节内容提到过,在secure boot中一般使用RSA签名方案。
要完成对镜像的签名,就必须使用私钥。而私钥一般是存在服务器上的,在本地PC上只存公钥。要想完成对镜像的签名,就必须把所有镜像上传到服务器重新打包。这种方案上传的文件太多,比较繁琐。下面我们介绍一种常用的签名方案。
在PC上,存放一把公钥和临时私钥,公钥是打包进dtb中的,安全启动时使用。临时私钥是为了生成签名数据。
在本地打包时,使用临时私钥对非安全镜像签名,将签名数据上传到服务器使用真正的私钥进行二次签名。将二次签名的数据和非安全镜像打包在一起,就得到了安全镜像。安全启动时,从dtb中拿出公钥对安全镜像进行校验即可。
这样既可以保证私钥的安全,又避免了上传所有镜像签名的繁琐。
签名镜像+签名配置
在secure boot中,除了对各个独立镜像签名外,还要对FIT Image中的配置项进行签名。
有些情况下,已经签名的镜像也有可能遭到破坏。例如,也可以使用相同的签名镜像创建一个FIT image,但是,其配置已经被改变,从而可以选择不同的镜像去加载(混合式匹配攻击)。也有可能拿旧版本的FIT Image去替换新的FIT image(回滚式攻击)。
下面举个例子。
/ {
images {
kernel@1 {
data = for kernel1>
signature@1 {
algo = "sha1,rsa2048"; # kernel image镜像的哈希值,由mkiamge工具自动生成 value = <...kernel signature 1...>
};
};
kernel@2 {
data = for kernel2>
signature@1 {
algo = "sha1,rsa2048";
value = <...kernel signature 2...>
};
};
fdt@1 {
data = for fdt1>;
signature@1 {
algo = "sha1,rsa2048";
vaue = <...fdt signature 1...>
};
};
fdt@2 {
data = for fdt2>;
signature@1 {
algo = "sha1,rsa2048";
vaue = <...fdt signature 2...>
};
};
};
configurations {
default = "conf@1";
conf@1 {
kernel = "kernel@1";
fdt = "fdt@1";
};
conf@1 {
kernel = "kernel@2";
fdt = "fdt@2";
};
};
};
两个kernel image 都已经被签名了,但是,攻击者可以很容易的将kernel1 和fdt2 作为configuration 3去加载。
configurations {
default = "conf@1";
conf@1 {
kernel = "kernel@1";
fdt = "fdt@1";
};
conf@1 {
kernel = "kernel@2";
fdt = "fdt@2";
};
conf@3 {
kernel = "kernel@1";
fdt = "fdt@2";
};
};
攻击者可以拿到签名的镜像,并且镜像是正确的。这种组合式攻击会给设备带来很大风险。
因此,为了解决这个问题,除了给镜像签名外,我们可以把配置选项也签名,每个镜像都有自己的签名,在给配置选项签名时,把镜像的hash值也包含进去。具体例子如下:
/ {
images {
kernel@1 {
data = for kernel1> hash@1 {
algo = "sha1";
value = <...kernel hash 1...>
};
};
kernel@2 {
data = for kernel2> hash@1 {
algo = "sha1";
value = <...kernel hash 2...>
};
};
fdt@1 {
data = for fdt1>; hash@1 {
algo = "sha1";
value = <...fdt hash 1...>
};
};
fdt@2 {
data = for fdt2>; hash@1 {
algo = "sha1";
value = <...fdt hash 2...>
};
};
};
configurations {
default = "conf@1";
conf@1 {
kernel = "kernel@1";
fdt = "fdt@1";
signature@1 {
algo = "sha1,rsa2048"; # 对配置项签名,由mkimage工具自动生成 value = <...conf 1 signature...>;
};
};
conf@2 {
kernel = "kernel@2";
fdt = "fdt@2";
signature@1 {
algo = "sha1,rsa2048";
value = <...conf 1 signature...>;
};
};
};
};
如上所示,除了给所有镜像添加了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的启动和对镜像的验签。
$ make O=sandbox sandbox_config
$ make O=sandbox
$ O=sandbox ./test/vboot/vboot_test.sh
/home/hs/ids/u-boot/sandbox/tools/mkimage -D -I dts -O dtb -p 2000
Build keys do sha1 test Build FIT with signed images
Test Verified Boot Run: unsigned signatures:: OK
Sign images
Test Verified Boot Run: signed images: OK
Build FIT with signed configuration
Test Verified Boot Run: unsigned config: OK
Sign images
Test Verified Boot Run: signed config: OK
check signed config on the host
Signature check OK
OK
Test Verified Boot Run: signed config: OK
Test Verified Boot Run: signed config with bad hash: OK do sha256 test Build FIT with signed images
Test Verified Boot Run: unsigned signatures:: OK
Sign images
Test Verified Boot Run: signed images: OK
Build FIT with signed configuration
Test Verified Boot Run: unsigned config: OK
Sign images
Test Verified Boot Run: signed config: OK
check signed config on the host
Signature check OK
OK
Test Verified Boot Run: signed config: OK
Test Verified Boot Run: signed config with bad hash: OK
Test passed
完整校验流程
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进行校验。