secure boot(三)secure boot的簽名和驗(yàn)簽方案
FIT 格式支持存儲(chǔ)鏡像的hash值,并且在加載鏡像時(shí)會(huì)校驗(yàn)hash值。這可以保護(hù)鏡像免受破壞,但是,它并不能保護(hù)鏡像不被替換。
而如果對(duì)hash值使用私鑰簽名,在加載鏡像時(shí)使用公鑰驗(yàn)簽則可以保護(hù)鏡像不被替換。因此,公鑰必須保存在一個(gè)絕對(duì)安全的地方。
接下來(lái)的內(nèi)容要求大家了解一些密碼學(xué)的內(nèi)容,之前也介紹過(guò)一些,可以看這篇文章
secure boot (一)FIT Image
secure boot (二)基本概念和框架
secure boot簽名的大致流程:
- 計(jì)算鏡像的hash值
- 利用私鑰對(duì)hash值簽名
- 簽名結(jié)果存在FIT Image 中。
secure boot驗(yàn)簽的大致流程:
- 讀取FIT Image
- 獲得pubkey
- 從FIT Image 提取簽名
- 計(jì)算鏡像的hash
- 使用公鑰驗(yàn)簽獲得hash值,與計(jì)算得到的hash值進(jìn)行對(duì)比
簽名是由mkimage工具完成的,驗(yàn)簽由uboot完成。
簽名算法
原則上講,任何合適的算法都可以用來(lái)簽名和驗(yàn)簽。在uboot中,目前只支持一類算法:SHA&RSA。
RSA 算法使用提前準(zhǔn)備好的公鑰就可以完成驗(yàn)簽,驗(yàn)簽相關(guān)的代碼量也很少。在驗(yàn)簽時(shí),RSA只是在FDT中提取必要的數(shù)據(jù)進(jìn)行校驗(yàn)。
當(dāng)然也可以在uboot中添加合適的算法,如果有其他簽名算法(如DSA),可以直接替換rsa.c,并在
image-sig.c中添加對(duì)應(yīng)算法即可。
創(chuàng)建RSA key和證書
創(chuàng)建RSA key和證書
openssl 創(chuàng)建一副2048的密鑰對(duì):
$ openssl genpkey -algorithm RSA -out keys/dev.key -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537
創(chuàng)建包含pubkey的證書:
$ openssl req -batch -new -x509 -key keys/dev.key -out keys/dev.crt
查看pubkey的值:
$ openssl rsa -in keys/dev.key -pubout
綁定設(shè)備樹
在FIT Image的簽名節(jié)點(diǎn)中需要添加以下 屬性,簽名節(jié)點(diǎn)與哈希節(jié)點(diǎn)處于同一級(jí)別,被稱為signature@1, signature@2等。
-
algo: 算法名稱
-
key-name-hint:用來(lái)簽名的key。密鑰對(duì)必須存放在單獨(dú)的文件夾(mkimage 使用-k 參數(shù)指定),私鑰被命名為 .key,證書命名為.crt。
鏡像被簽名后,以下這些屬性都會(huì)被自動(dòng)強(qiáng)制添加:
-
value: 簽名后的值(RSA-2048 占256 bytes)
以下這些屬性是可選的:
-
timestamp:簽名的時(shí)間
-
signer-name:簽名者的名字(例如mkimage)
-
signer-version:簽名的版本(例如"2013.01")
-
comment:簽名者或者鏡像的額外信息
-
sign-images:簽名鏡像的列表
-
hashed-nodes:簽名者簽名的節(jié)點(diǎn)列表,一般是包含節(jié)點(diǎn)完整路徑的字符串。例如:
hashed-nodes = "/", "/configurations/conf@1", "/images/kernel@1", "/images/kernel@1/hash@1", "/images/fdt@1", "/images/fdt@1/hash@1";
以下是一個(gè)待簽名鏡像的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";
};
};
};
以下是配置項(xiàng)簽名后的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的存儲(chǔ)
為了校驗(yàn)簽名后的鏡像,必須把pubkey存放在可信賴的位置。將pubkey存在鏡像中是不安全的,很容易被破解。一般我們將其存放在uboot的FDT中(CONFIG_OF_CONTROL)。
pubkey應(yīng)該作為一個(gè)子節(jié)點(diǎn)存放在/signature節(jié)點(diǎn)中。節(jié)點(diǎn)中要加上以下特性:
-
algo:算法名稱
-
key-name-hint: 簽名使用的key的名稱
-
required: 校驗(yàn)?zāi)撑渲盟褂玫墓€
除此之外,每個(gè)算法都有一些必要的特性。RSA算法中,以下特性必須被添加:
-
rsa,num-bits:key的位數(shù)
-
rsa,modulus:N,多字節(jié)的整數(shù)
-
rsa,exponent:E,64位的無(wú)符號(hào)整數(shù)
-
rsa,r-squared:(2^num-bits)^2
-
rsa,n0-inverse:-1 / modulus[0] mod 2^32
下面看一個(gè)例子,以下是一個(gè)uboot.dtb存放RSA的例子。RSA key被mkimage打包在u-boot.dtb和u-boot-spl.dtb中,然后它們?cè)俦淮虬M(jìn)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節(jié)點(diǎn)由mkimage工具自動(dòng)插入生成,節(jié)點(diǎn)里保存了RSA-SHA算法類型、RSA核心因子參
//數(shù)等信息。
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";
};
};
簽名方案
上一節(jié)內(nèi)容提到過(guò),在secure boot中一般使用RSA簽名方案。
要完成對(duì)鏡像的簽名,就必須使用私鑰。而私鑰一般是存在服務(wù)器上的,在本地PC上只存公鑰。要想完成對(duì)鏡像的簽名,就必須把所有鏡像上傳到服務(wù)器重新打包。這種方案上傳的文件太多,比較繁瑣。下面我們介紹一種常用的簽名方案。
在PC上,存放一把公鑰和臨時(shí)私鑰,公鑰是打包進(jìn)dtb中的,安全啟動(dòng)時(shí)使用。臨時(shí)私鑰是為了生成簽名數(shù)據(jù)。
在本地打包時(shí),使用臨時(shí)私鑰對(duì)非安全鏡像簽名,將簽名數(shù)據(jù)上傳到服務(wù)器使用真正的私鑰進(jìn)行二次簽名。將二次簽名的數(shù)據(jù)和非安全鏡像打包在一起,就得到了安全鏡像。安全啟動(dòng)時(shí),從dtb中拿出公鑰對(duì)安全鏡像進(jìn)行校驗(yàn)即可。
這樣既可以保證私鑰的安全,又避免了上傳所有鏡像簽名的繁瑣。
簽名鏡像+簽名配置
在secure boot中,除了對(duì)各個(gè)獨(dú)立鏡像簽名外,還要對(duì)FIT Image中的配置項(xiàng)進(jìn)行簽名。
有些情況下,已經(jīng)簽名的鏡像也有可能遭到破壞。例如,也可以使用相同的簽名鏡像創(chuàng)建一個(gè)FIT image,但是,其配置已經(jīng)被改變,從而可以選擇不同的鏡像去加載(混合式匹配攻擊)。也有可能拿舊版本的FIT Image去替換新的FIT image(回滾式攻擊)。
下面舉個(gè)例子。
/ {
images {
kernel@1 {
data = for kernel1>
signature@1 {
algo = "sha1,rsa2048"; # kernel image鏡像的哈希值,由mkiamge工具自動(dòng)生成 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";
};
};
};
兩個(gè)kernel image 都已經(jīng)被簽名了,但是,攻擊者可以很容易的將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";
};
};
攻擊者可以拿到簽名的鏡像,并且鏡像是正確的。這種組合式攻擊會(huì)給設(shè)備帶來(lái)很大風(fēng)險(xiǎn)。
因此,為了解決這個(gè)問(wèn)題,除了給鏡像簽名外,我們可以把配置選項(xiàng)也簽名,每個(gè)鏡像都有自己的簽名,在給配置選項(xiàng)簽名時(shí),把鏡像的hash值也包含進(jìn)去。具體例子如下:
/ {
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"; # 對(duì)配置項(xiàng)簽名,由mkimage工具自動(dòng)生成 value = <...conf 1 signature...>;
};
};
conf@2 {
kernel = "kernel@2";
fdt = "fdt@2";
signature@1 {
algo = "sha1,rsa2048";
value = <...conf 1 signature...>;
};
};
};
};
如上所示,除了給所有鏡像添加了hash值,還為每個(gè)配置添加了簽名。mkimage將會(huì)對(duì)configurations/conf@1簽名(/images/kernel@1, /images/kernel@1/hash@1,/images/fdt@1, /images/fdt@1/hash@1)。簽名會(huì)被寫入 /configurations/conf@1/signature@1/value。
驗(yàn)簽
FIT image 在加載時(shí)會(huì)驗(yàn)簽。如果'required' 指定了驗(yàn)簽的公鑰,則會(huì)使用這把公鑰校驗(yàn)該配置對(duì)應(yīng)的所有鏡像。
為了支持FIT格式,以下配置項(xiàng)必須被選上。
CONFIG_FIT_SIGNATURE :使能FIT image的簽名和驗(yàn)簽
CONFIG_RSA :使能RSA簽名算法
默認(rèn)情況下,使能FIT Image的簽名和驗(yàn)簽后,CONFIG_IMAGE_FORMAT_LEGACY會(huì)被禁用。即FIT uboot image的只能引導(dǎo)FIT kernel Image。
如果需要引導(dǎo)legacy kernel image,需要手動(dòng)添加CONFIG_IMAGE_FORMAT_LEGACY 定義。
測(cè)試
為了校驗(yàn)簽名和驗(yàn)簽是否正確,可以使用測(cè)試腳本test/vboot/vboot_test.sh。下面以sandbox為例子來(lái)說(shuō)明bootm的啟動(dòng)和對(duì)鏡像的驗(yàn)簽。
$ 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
完整校驗(yàn)流程
OTP校驗(yàn)loader
那么,這種鏡像校驗(yàn)方式有個(gè)很重要的問(wèn)題,公鑰存在哪里才是安全的呢?
一般SOC中會(huì)有一個(gè)叫OTP或EFUSE的區(qū)域,這部分區(qū)域比較特殊,只可以寫入一次,寫入后就再也不可以修改了。把公鑰存儲(chǔ)在OTP中,就可以很好地保證其不能被修改。
OTP的存儲(chǔ)空間很小,一般只有幾KB,因此并不適合直接存放RSA公鑰。一般都是將RSA公鑰的hash val 存放在OTP中。像sha256的hash值僅為256 bits,而RSA 公鑰本身一般存放在鏡像中。
在使用公鑰之前,只需要使用OTP中的公鑰hash值驗(yàn)證鏡像附帶公鑰的完整性,即可確定公鑰是否合法。
RSA公鑰需要一般使用芯片廠家的工具寫入loader。安全啟動(dòng)時(shí),bootrom首先從loader固件頭中獲取RSA公鑰并校驗(yàn)合法性;然后再使用該公鑰校驗(yàn)SPL的固件簽名。
spl校驗(yàn)uboot
SPL把RSA公鑰保存在u-boot-spl.dtb中,u-boot-spl.dtb會(huì)被打包進(jìn)u-boot-spl.bin文件(最后打包進(jìn)loader);安全啟動(dòng)時(shí)SPL從自己的dtb文件中拿出RSA公鑰對(duì)uboot.img進(jìn)行安全校驗(yàn)。
uboot校驗(yàn)kernel
U-Boot把RSA公鑰保存在u-boot.dtb中,u-boot.dtb會(huì)被打包進(jìn)u-boot.bin文件(最后打包為uboot.img);安全啟動(dòng)時(shí)U-Boot從自己的dtb文件中拿RSA公鑰對(duì)boot.img進(jìn)行校驗(yàn)。