在嵌入式系统安全启动体系中,U-Boot的FIT(Flattened Image Tree)镜像签名验证是一道关键防线——它能确保启动镜像未被篡改、来源可信,而image-sig.c正是实现这一核心能力的核心文件。本文将从数据结构、函数逻辑、数据流程三个维度,拆解image-sig.c的实现细节,带你吃透U-Boot镜像验签的底层逻辑。
一、背景:为什么需要FIT镜像签名验证?
传统的U-Boot镜像格式(如uImage)功能单一,难以满足复杂场景下的安全需求。FIT镜像以设备树(DTB)为载体,支持多镜像打包、版本管理、签名验证等能力,而签名验证则是安全启动的核心:
•防止镜像被恶意篡改,保障启动流程可信;
•支持多种哈希/加密算法,适配不同安全等级需求;
•区分“必需”和“可选”签名,灵活适配不同场景。
image-sig.c的核心职责就是:管理验签所需的算法、解析FIT镜像的签名节点、完成哈希校验与签名验证,是FIT镜像安全的守护者。
二、核心数据结构:算法与验签信息的载体
在分析函数前,先理解几个核心结构体——它们是整个验签逻辑的“数据骨架”。
1.哈希算法结构体checksum_algo
structchecksum_algo { constchar*name; // 算法名(sha1/sha256) intchecksum_len; // 哈希值长度 intder_len; // DER前缀长度 constuint8_t *der_prefix;// DER编码前缀(适配ASN.1规范) EVP_MD *(*calculate_sign)();// 签名用哈希计算函数 int(*calculate)(); // 通用哈希计算函数};
作用:封装SHA1/SHA256两种哈希算法的核心属性,关联哈希计算的具体实现,是后续生成/验证哈希值的基础。
2.加密算法结构体crypto_algo
structcrypto_algo { constchar*name; // 算法名(rsa2048/rsa4096) intkey_len; // 密钥长度(字节) int(*sign)(); // 签名函数 int(*add_verify_data)(); // 添加验证数据 int(*verify)(); // 验签函数};
作用:封装RSA2048/RSA4096两种非对称加密算法,关联签名/验签的核心逻辑,是验签的核心执行者。
3.填充算法结构体padding_algo
structpadding_algo { constchar*name; // 填充方式(pkcs-1.5/pss) int(*verify)(); // 填充验证函数};
作用:适配不同的RSA填充规范(PKCS1.5是默认,PSS更安全),通过配置开关CONFIG_FIT_ENABLE_RSASSA_PSS_SUPPORT控制是否启用PSS。
4.验签上下文image_sign_info
这个结构体未在代码中显式定义,但从函数参数能看出其核心作用:承载一次验签的所有上下文信息,包括:
•算法指针(哈希/加密/填充);
•FIT镜像指针、节点偏移量;
•密钥名、验证所需的FDT blob;
•必需的密钥节点索引。
三、核心函数解析:从算法查找到验签落地
image-sig.c的函数可分为三大类:算法查找、辅助工具、验签核心,我们逐一拆解。
1.算法查找函数:匹配字符串与算法实现
验签的第一步是“识别算法”——从FIT镜像节点的属性字符串(如sha256,rsa2048)中,匹配到对应的算法结构体。
(1)image_get_checksum_algo:查找哈希算法
structchecksum_algo *image_get_checksum_algo(constchar*full_name);
•输入:完整算法名(如sha256,rsa2048);
•逻辑:遍历checksum_algos数组,匹配前缀(如sha256)且后续字符为逗号,返回对应的哈希算法结构体;
•输出:匹配到的checksum_algo指针(NULL表示未匹配)。
(2)image_get_crypto_algo:查找加密算法
structcrypto_algo *image_get_crypto_algo(constchar*full_name);
•输入:完整算法名(如sha256,rsa2048);
•逻辑:先截取逗号后的字符串(如rsa2048),再遍历crypto_algos数组匹配算法名;
•输出:匹配到的crypto_algo指针(NULL表示未匹配)。
(3)image_get_padding_algo:查找填充算法
structpadding_algo *image_get_padding_algo(constchar*name);
•输入:填充算法名(如pkcs-1.5);
•逻辑:遍历padding_algos数组匹配名称;
•输出:匹配到的padding_algo指针(NULL表示未匹配)。
2.辅助工具函数:FDT区域到镜像区域的转换
structimage_region *fit_region_make_list(constvoid*fit, structfdt_region *fdt_regions,intcount, structimage_region *region);
•核心作用:将FDT(设备树)格式的区域信息,转换为U-Boot镜像验签所需的image_region结构体;
•关键细节:
a.非SPL构建:自动调用calloc分配内存(SPL为节省代码量,要求调用者提前分配);
b.填充image_region的data(镜像数据指针)和size(数据长度);
c.输出调试信息(偏移量、大小),方便调试哈希区域。
3.验签核心函数:从初始化到最终验证
验签逻辑是层层封装的,从“单节点验签”到“批量验签”,再到“配置节点验签”,形成完整的验证链路。
(1)fit_image_setup_verify:验签前的初始化
staticintfit_image_setup_verify(structimage_sign_info *info, constvoid*fit,intnoffset,intrequired_keynode, char**err_msgp);
•核心职责:为验签做准备,填充image_sign_info上下文;
•执行流程:
a.从FIT节点获取哈希算法名、填充方式(默认PKCS1.5);
b.初始化info结构体:密钥名、FIT镜像指针、节点偏移量;
c.调用算法查找函数,绑定哈希/加密/填充算法;
d.校验算法是否有效,无效则返回错误信息。
(2)fit_image_check_sig:单个签名节点的验签
intfit_image_check_sig(constvoid*fit,intnoffset,constvoid*data, size_tsize,intrequired_keynode,char**err_msgp);
•核心职责:验证单个签名节点的有效性;
•执行流程:
a.调用fit_image_setup_verify初始化上下文;
b.从FIT节点读取预计算的哈希值(fit_value);
c.构造镜像区域(image_region),包含待验证数据的指针和长度;
d.调用加密算法的verify函数,验证哈希值与签名是否匹配;
e.返回验证结果(0成功,-1失败)。
(3)fit_image_verify_sig:遍历镜像节点的签名子节点
staticintfit_image_verify_sig(constvoid*fit,intimage_noffset, constchar*data,size_tsize,constvoid*sig_blob, intsig_offset);
•核心职责:遍历镜像节点下的所有签名子节点(以sig-开头),批量验签;
•执行流程:
a.遍历FIT镜像节点的所有子节点,筛选出签名节点;
b.对每个签名节点调用fit_image_check_sig验签;
c.只要有一个签名验证通过,标记verified=1并返回成功;
d.若遍历出错(如FDT结构损坏),返回错误信息。
(4)fit_image_verify_required_sigs:验证“必需”的签名
intfit_image_verify_required_sigs(constvoid*fit,intimage_noffset, constchar*data,size_tsize,constvoid*sig_blob, int*no_sigsp);
•核心职责:只验证标记为“required=image”的签名节点(这类签名必须通过,否则启动失败);
•执行流程:
a.查找签名blob中的sig节点;
b.遍历子节点,筛选出required="image"的节点;
c.调用fit_image_verify_sig验证,失败则直接返回错误;
d.统计验证通过的数量,更新no_sigsp(标记是否有签名)。
(5)fit_config_check_sig:配置节点的验签(进阶)
intfit_config_check_sig(constvoid*fit,intnoffset,intrequired_keynode, char**err_msgp);
•核心场景:验证FIT镜像的配置节点(而非镜像数据),防止配置被篡改;
•特殊逻辑:
a.解析hashed-nodes属性:获取需要哈希的子节点列表;
b.校验节点数量(不超过IMAGE_MAX_HASHED_NODES=100),防止栈溢出;
c.调用fdt_find_regions:查找所有需要哈希的FDT区域;
d.处理hashed-strings属性:将字符串区域加入哈希列表;
e.转换为image_region后调用验签函数,完成配置验证;
f.适配硬件加密:如RK的SPL+Secure OTP,验签后烧录密钥哈希。
(6)配置节点验签的封装函数
fit_config_verify_sig/fit_config_verify_required_sigs/fit_config_verify是对fit_config_check_sig的封装,逻辑与镜像节点验签类似:遍历配置节点的签名子节点→验证“required=conf”的签名→返回最终结果。
4.特殊场景:回滚保护
代码末尾的fit_read_otp_rollback_index/fit_rollback_index_verify是“回滚保护”的弱实现:
•读取OTP中的回滚索引,防止攻击者降级到旧版本(有安全漏洞的版本);
•采用__weak修饰,支持厂商自定义实现(如基于硬件OTP的索引校验)。
四、数据处理全流程:从触发验签到验证完成
我们以“镜像节点验签”为例,梳理完整的数据走向,流程图如下:

流程图核心说明:验签流程层层封装、逐步递进,优先验证“必需签名”,确保启动安全性;单个签名节点验证需完成算法绑定、哈希读取、匹配校验三大核心步骤,任一环节失败则启动终止。
1.启动触发验签 → 传入FIT镜像指针、镜像节点偏移量2.调用fit_image_verify_required_sigs → 筛选required=image的签名节点3.调用fit_image_verify_sig → 遍历镜像节点下的sig-*子节点4. 对每个sig节点调用fit_image_check_sig: a. fit_image_setup_verify → 解析算法名→匹配哈希/加密/填充算法 b. 读取FIT节点中的哈希值(fit_value) c. 构造image_region(待验证数据的指针+长度) d. 调用crypto_algo->verify → 验证哈希值与签名匹配5.验证通过→返回0;验证失败→输出错误信息→返回-16.所有required签名验证通过→启动镜像;否则→启动失败
配置节点验签的流程类似,核心差异是:哈希区域从“镜像数据”变为“配置节点的子节点+字符串区域”。
五、代码设计的亮点与扩展思路
1.设计亮点
•模块化:算法与验签逻辑解耦,新增算法只需修改checksum_algos/crypto_algos数组;
•可配置:通过宏开关(如CONFIG_FIT_ENABLE_RSASSA_PSS_SUPPORT)控制功能,适配不同场景;
•内存适配:区分SPL/非SPL构建,兼顾代码量和易用性;
•调试友好:输出详细的调试信息(哈希区域、算法名),方便问题定位。
2.扩展思路
•新增哈希算法(如SHA512):在checksum_algos数组中添加新项,实现对应的哈希计算函数;
•支持ECDSA加密:扩展crypto_algos结构体,添加ECDSA的签名/验签函数;
•强化回滚保护:基于硬件OTP实现强校验,替换默认的__weak函数;
•适配TEE:结合OP-TEE(代码中已引入OpteeClientInterface.h),将验签逻辑移到安全世界执行。
六、总结
image-sig.c是U-Boot FIT镜像安全的核心,它以“算法抽象+流程封装”的方式,实现了哈希计算、签名验证、配置校验的完整逻辑。理解这份代码,不仅能掌握U-Boot安全启动的底层原理,也能为嵌入式系统的安全加固提供思路——比如如何设计可扩展的验签框架、如何适配不同的硬件安全特性。
在实际开发中,厂商通常会基于这份代码做定制化:比如适配自研的硬件加密模块、强化回滚保护、新增国密算法(SM2/SM3)等。而掌握核心逻辑后,这些定制化开发都会变得清晰可落地。
最后,安全启动的核心是“全链路可信”,image-sig.c只是其中一环,还需要结合镜像加密、OTP烧录、硬件隔离等技术,才能构建真正的安全启动体系。
审核编辑 黄宇
-
嵌入式系统
+关注
关注
41文章
3817浏览量
133865 -
u-boot
+关注
关注
0文章
135浏览量
39920 -
Fit
+关注
关注
0文章
17浏览量
9584
发布评论请先 登录
S32G398 u-boot OCOTP 编程保险丝仅在复位后激活是为什么?
更新 U-boot 时出现的问题求解
更新固件后 U-boot 不运行怎么解决?
更新 SPL 和 U-Boot的提示和技巧
玩转U-Boot bdinfo:嵌入式bsp开发者的定制、扩展与裁剪实战指南
深入解析RK3588 U-Boot板级文件:evb_rk3588.c核心逻辑拆解
U-Boot SPL核心文件spl.c深度解析:从启动流程到调试优化
深入解析U-Boot TPL代码:嵌入式启动的“第一棒”背后的秘密
深入解析U-Boot命令处理核心文件:功能、调试与开发价值
解析Rockchip平台U-Boot核心文件:boot_rkimg.c到底做了什么?
深入解析rk平台Android Bootloader核心代码:从启动流程到AVB验证
深入解析RK平台Android/Linux Bootloader核心文件:android_bootloader.c
深入理解 RK3506 U-Boot 重定位:从代码到原理
深入解析U-Boot FIT镜像签名验证:image-sig.c核心实现
评论