在嵌入式系统安全启动的整个流程中,密钥对是信任链的起点。我习惯使用OpenSSL工具链来生成RSA密钥,这是目前最成熟可靠的方案之一。先创建一个专门的工作目录,比如~/secure_boot,所有操作都在这个目录下进行,避免文件散落各处。
生成2048位RSA私钥的命令看起来简单,但有几个细节需要注意:
bash复制openssl genpkey -algorithm RSA -out dev.key \
-pkeyopt rsa_keygen_bits:2048 \
-pkeyopt rsa_keygen_pubexp:65537
这里特意指定了公钥指数为65537(即0x10001),这是行业标准值,既能保证安全性又不会显著影响性能。曾经有个项目使用默认参数生成的密钥导致验签速度慢了30%,排查半天才发现是这个参数的问题。
接着用私钥生成自签名证书:
bash复制openssl req -batch -new -x509 -key dev.key -out dev.crt
这个证书会包含公钥信息,后续会被编译进设备树。批处理模式(-batch)跳过了交互式提问,适合自动化脚本。在实际产线环境中,建议将密钥生成环节放在安全的离线环境中进行,生成后立即备份私钥并清除工作痕迹。
Image Tree Source (ITS)文件是FIT镜像的"配方",它定义了镜像组成结构和签名规则。我通常会为每个硬件平台创建独立的ITS文件,比如s32g.its。这个文件本质上是一种设备树格式的配置文件,但新手经常被它的语法搞糊涂。
一个完整的ITS文件包含三个关键部分:
dts复制/dts-v1/;
/ {
description = "Secure boot image for S32G";
images {
kernel {
data = /incbin/("Image");
type = "kernel";
arch = "arm64";
signature {
algo = "sha256,rsa2048";
key-name-hint = "dev";
};
};
fdt-1 {
data = /incbin/("s32g.dtb");
type = "flat_dt";
signature {
algo = "sha256,rsa2048";
key-name-hint = "dev";
};
};
};
configurations {
default = "conf-1";
conf-1 {
kernel = "kernel";
fdt = "fdt-1";
};
};
};
特别注意signature节点中的algo参数,我推荐使用sha256而不是原文中的sha1,因为SHA-1已经被证明不够安全。曾经有个客户坚持用SHA-1导致产品无法通过安全认证,最后不得不重新发布固件。
要让U-Boot支持验签,需要在设备树中添加签名相关的节点。这个步骤经常被忽视,但却是整个流程能否成功的关键。以S32G平台为例,我们需要创建或修改arch/arm/dts/s32g-verified-boot.dts:
dts复制/dts-v1/;
/ {
signature {
key-dev {
required = "conf";
algo = "sha256,rsa2048";
key-name-hint = "dev";
};
};
};
这里的required = "conf"表示必须验证配置节点的签名,如果改为required = "images"则要求验证所有镜像的签名。根据我的经验,生产环境应该设置为conf,而开发阶段可以设为none方便调试。
编译这个设备树时需要特别注意:
bash复制dtc -@ -I dts -O dtb -o verified-boot.dtb s32g-verified-boot.dts
-@参数保留了符号信息,这对后续的签名验证至关重要。曾经有个团队漏了这个参数,导致验签总是失败,花了整整一周才找到原因。
现在进入U-Boot的配置环节,这是最容易出错的地方。首先确保你的U-Boot版本支持FIT和签名验证功能。执行make menuconfig后,需要重点检查以下配置项:
code复制CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_RSA=y
CONFIG_OF_SEPARATE=y
CONFIG_DEFAULT_DEVICE_TREE="s32g-verified-boot"
特别注意CONFIG_FIT_SIGNATURE,这个选项控制是否启用签名验证功能。有个常见的误区是开启了选项但忘记重新编译工具链,导致mkimage不支持签名操作。我建议在配置完成后执行:
bash复制make tools
这会重新编译包括mkimage在内的所有工具,确保它们具备签名验证能力。
万事俱备,现在可以生成最终的FIT镜像了。使用以下命令组合:
bash复制mkimage -f s32g.its -K verified-boot.dtb -k . -r s32g.fit
参数说明:
-f 指定ITS文件-K 指定包含公钥的设备树-k 密钥目录(存放dev.key和dev.crt)-r 输出文件名生成后立即进行本地验证是个好习惯:
bash复制fit_check_sign -f s32g.fit -k verified-boot.dtb
如果看到"Verified OK"就说明签名验证通过。但要注意,这个测试是在开发主机上进行的,最终还需要在实际硬件上验证。我遇到过在PC上验证通过但在目标板失败的情况,原因是设备树中的内存地址配置不正确。
最后将编译好的U-Boot和FIT镜像烧写到设备上。关键步骤包括:
bash复制cat u-boot.bin u-boot.dtb > u-boot.img
使用编程器或fastboot工具烧写镜像
上电测试时,在U-Boot命令行输入:
bash复制bootm ${loadaddr}#conf-1
观察控制台输出,应该能看到签名验证的过程。如果验证失败,U-Boot会拒绝启动内核。
测试阶段建议尝试以下破坏性测试:
这些测试能确保安全机制真正起作用。记得有一次,客户报告说他们的设备"很容易被破解",结果发现是他们跳过了这些测试环节,实际上验签功能根本没生效。
在实际项目中,我遇到过各种稀奇古怪的问题,这里分享几个典型案例:
问题1:验签总是失败,但PC端验证通过
原因:设备树中的公钥与签名使用的私钥不匹配
解决:检查dev.crt是否被正确编译进设备树,可以用fdtdump工具查看
问题2:启动时卡在"Verifying Hash Integrity"
原因:内存地址冲突,内核加载地址与验签缓冲区重叠
解决:调整loadaddr环境变量或修改ITS文件中的加载地址
问题3:签名验证通过但内核无法启动
原因:ITS文件中指定的内核压缩方式与实际不符
解决:确保compression参数与内核镜像的实际压缩方式一致
对于更复杂的问题,可以启用U-Boot的调试输出:
bash复制setenv verify yes
setenv bootargs uboot.debug=7
saveenv
这样能获取详细的验签过程信息,对定位问题非常有帮助。
当系统能正常验签启动后,可以考虑以下优化措施:
签名性能优化:
sha256代替sha512,在保证安全性的前提下提升验签速度密钥管理增强:
安全启动链扩展:
自动化构建集成:
我在最近一个项目中采用了分级签名策略:开发阶段使用临时密钥,量产阶段切换为正式密钥,通过环境变量控制验签级别。这样既保证了生产环境的安全性,又不影响开发效率。