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和证书

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配置。

/dts-v1/;

/ {
	description = "Chrome OS kernel image with one or more FDT blobs";
	#address-cells = <1>;

	images {
		kernel@1 {
			data = /incbin/("test-kernel.bin");
			type = "kernel_noload";
			arch = "sandbox";
			os = "linux";
			compression = "none";
			load = <0x4>;
			entry = <0x8>;
			kernel-version = <1>;
			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 = <1>;
			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 = <1>;

	images {
		kernel@1 {
			data = /incbin/("test-kernel.bin");
			type = "kernel_noload";
			arch = "sandbox";
			os = "linux";
			compression = "lzo";
			load = <0x4>;
			entry = <0x8>;
			kernel-version = <1>;
			hash@1 {
				algo = "sha1";
			};
		};
		fdt@1 {
			description = "snow";
			data = /incbin/("sandbox-kernel.dtb");
			type = "flat_dt";
			arch = "sandbox";
			compression = "none";
			fdt-version = <1>;
			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 = <0x00000001>;
        #size-cells = <0x00000001>;
        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 = <0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x1327f633 0x00000003 0x00000003 0x00000003 0xc7aead6a 0xb4c79f40 0xa82bdf76 0xfb2f8387 0xa1e06dce 0xd451a706 0xc7f865e3 0x3e2d7ca8 0x6a71762e 0x125f1828 0x36ab1a41 0xb7e9e852 0x7bd0011a 0x7279e0b8 0xf37e189c 0x8cf00963 0x00000100 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000377 0x00000004 0x00000004 0x00000004 0x00000002 0x00000003 0x69616c40 0x00000003 0x6d634066 0x00000010 0x66633630 0x73797363>;
        		rsa,c = <0x00000000>;
        		rsa,r-squared = <0x00000000>;
       			rsa,modulus = <0xc25ae693 0xc359f2a4 0xa866c89d 0xb7b1994f 0xf9f9f690 0x518d54a7 0xda0b83e8 0x06606e12 0x6ad1cbf9 0x92438edd 0x81e039c0 0x5d7322cc 0x124cdc80 0xa0c3288a 0x9265c3ae 0x6ac47a4b 0x00000003 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000008 0x00000003 0x00000003 0x00000003 0x00000002 0x73657300 0x2f736572 0x00000000 0x2f64776d 0x00000003 0x6d634066 0x00000001 0x30303000 0x726f636b 0x67726600 0x00000008 0x00000003 0x00000004 0x00000001 0x30303000 0x726f636b 0x706d7567 0x00000003 0x00001000 0x00000003 0x00000002 0x6e616765 0x30000000 0x726f636b 0x706d7500 0x00000008>;
        		rsa,exponent-BN = <0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000003 0x00010001 0xe95771c5 0x00000800 0x64657600 0x616c6961 0x0000002c 0x30303030 0x00000034 0x30303000 0x2f64776d 0x00000002 0x65303030 0x0000001b 0x3132362d 0x00000003 0x00020000 0x00000003 0x00000002 0x65303230 0x0000001b 0x3132362d 0x6e000000 0xfe020000 0x00000042 0x0000006d 0x722d6d61 0x65303030 0x0000001b 0x3132362d 0x00000003 0x00001000 0x00000002 0x6e74726f 0x30000000 0x726f636b 0x706d7563 0x0000003e 0x00000004 0x00000004 0x00000004 0x00000000 0x00000050 0x636c6f63 0x40666634 0x00000014 0x2c727631 0x00000008>;
        		rsa,exponent = <0x00000000 0x00000368>;
        		rsa,n0-inverse = <0xe95771c5>;
        		rsa,num-bits = <0x00000800>;
       			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 = <data for kernel1>
			signature@1 {
				algo = "sha1,rsa2048";
				# kernel image镜像的哈希值,由mkiamge工具自动生成
				value = <...kernel signature 1...>
			};
		};
		kernel@2 {
			data = <data for kernel2>
			signature@1 {
				algo = "sha1,rsa2048";
				value = <...kernel signature 2...>
			};
		};
		fdt@1 {
			data = <data for fdt1>;
			signature@1 {
				algo = "sha1,rsa2048";
				vaue = <...fdt signature 1...>
			};
		};
		fdt@2 {
			data = <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 = <data for kernel1>
			hash@1 {
				algo = "sha1";
				value = <...kernel hash 1...>
			};
		};
		kernel@2 {
			data = <data for kernel2>
			hash@1 {
				algo = "sha1";
				value = <...kernel hash 2...>
			};
		};
		fdt@1 {
			data = <data for fdt1>;
			hash@1 {
				algo = "sha1";
				value = <...fdt hash 1...>
			};
		};
		fdt@2 {
			data = <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进行校验。

总结

从bootrom到kernel为止的安全启动,统一使用一把RSA公钥完成安全校验,并且当前这级的RSA Key已经作为自身固件的一部分,由前一级loader完成了安全校验,从而保证了Key的安全。

  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
# 学习对象在全民造车、造芯的大时代,在努力去解决卡脖子的时代,ASIC硬件、SOC底层软件、Linux Kernel等操作系统软件(内核/驱动)、软硬件方面的系统架构师等的岗位需求也越来越明显,社会一直都是非常缺人的,缺的是核心的那一小撮、领头的那一小撮,社会所缺的更是能够软硬件融合的那一小撮人……总之,要想在这个时代,站稳自己的脚跟,能够在大公司或行业上拥有一席之地,就必需深入学习底层技术原理,核心技术才是您的看家本领。本课程设计之初,主要针对SOC底层软件开发的者、系统开发者,或者励志成为这样的人。既适合资深/高级工程师来查缺补漏,又适合初级工程师入门。(理论上该课程和ASIC硬件电路设计无关,该课程偏软件,但事实购买该课程的做ASIC的同学已然超过了15%)适用人群1、芯片开发者(包括底层软件、或做ASIC硬件的)。不限行业,例如车、云、物联网、移动端等领域;2、汽车行业开发者(主机厂、tier1、SOC厂家、各级供应商);3、嵌入式开发者、kernel开发者、驱动、软件工程师;4、学生。既适合学生从入门到精通,也适合资深工程师查缺补漏;您的收益:1、全体系的掌握ARMv8/ARMv9的核心知识点(ARM基础、异常中断GIC、MMU/Cache、architecture...);2、掌握ARM架构、掌握SOC架构、掌握常规IP(gic、smmu、timer、AXI/ACE/CHI、TZC400...);3、快速熟悉常规系统软件(bootrom、spl、ATF、TEE、bootloader、kernel...), Secureboot安全启动...4、技术水平提升N个level, 掌握快速的学习方法;# 学习什么在ARM蓬勃发展的年代,不仅仅涉及到物联网IOT、移动领域(如手机)、汽车电子领域,现在还涉及到PC、服务器的,简直就是各行各业。ARMv8出来已经有10年了,ARMv9也2年时间了。在技术不断更新迭代的背景下,此时再去学习十五年前的ARMv7、二十年前的ARMv5/v6显然不是明智的选择。本课程主要基于当前最新的架构,ARMv8的aarch64和ARMv9,如涉及具体的ARM Core IP主要还是以最新的ARM Core IP为主,软件架构也是以当前最主流的/未来所趋势的架构来讲解。以下也给大家列举初了一个ARM产品的timeline的总结(在本课程中有着大量的这种总结),从这张图中,您是可以清晰的看到本课程拥有独具一格的风格、拥有全网最新(且唯一)的资料总结或学习路线。# 本课程大纲和规划(课程持续更新中,课程总量统计:2022/10/02  当前是 61节课, 22小时)第一章:主要是快速学习: ARM简介、指令集、寄存器总结等。第二章:本系列视频的一大亮点,系统全面地讲解了arm异常中断gic等相关的软硬件知识,本人一直在倡导“学arm安全其实就是学arm架构,学arm架构其实就是学习arm的异常和中断”,异常中断是领着你进入架构的入门,是让你变成系统软硬件架构师的必走之路。第章:安全专题,这也是本视频最核心的东西。因为你无论买书还是看博客等,你都很难找到讲解安全的教程,这里就是有和无的区别。本人系统的整理的安全的知识,带领你快速入门。第四章:mmu专题,透过事务看本质的讲解,白话式的演讲。在所有模块中,mmu也算是相对较简单模块。相信人人听得懂,人人学得会。第五章:cache专题,一切追求实事求是,不人云亦云,一切知识点都有迹可循,推翻了网络的很多观念。在众多模块中,cache算是一个比较难的模块。了解了cache后,才能算真正了解系统的软硬件架构。第六章:虚拟化,本人不擅长,会啥就随便讲点啥。(以后学会了再来补)第七章:architecture,就是零散和零碎的系统架构知识,如exclusive、arch timer、reset、系统启动、SOC设计、AMBA/AXI/ACE、DSU、WFE/WFI这样的。第八章: 新增的ARMv9 CCA/RME安全架构专题第九章:主要放置一些直播课。# 课程收益1、知道我学习什么,我要怎么去学习,从此之后有了一个明确的学习路线。2、认识一些共同目标的人,相互讨论问题,共同进步。勤学、共学、助学。3、ARM不再神秘,SOC不在神秘,让您短期内就能cover住全局4、熟悉ARM Architecture架构知识5、熟悉SOC架构知识6、熟悉主流的系统软件框架7、熟悉各项硬件原理和机制,如异常中断、MMU、cache、TLB、VMSA、Trustzone6、深入了解当前的系统架构、软硬件架构,能够看懂这些大家,将来也能够自己设计。7、熟悉系统的启动流程、Secureboot等8、熟悉各类标准和规范9、能够进入芯片厂商干活、能够在非芯片产生成为技术担当。10、学习资料的获取方法,会看11500多页的ARM手册,会看数以百计的ARM各项参考手册。 本课程会持续更新。也希望通过本课程的学习,能够让大家的ARMv8/ARMv9开发技术能有质的飞越,能找到自己心仪的工作。在购买之前,也建议大家看一看第一章第一节的课程介绍。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式与Linux那些事

您的鼓励将使我写出更好的文章

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值