深入解析U-Boot FIT镜像签名验证:image-sig.c核心实现

电子说

1.4w人已加入

描述

在嵌入式系统安全启动体系中,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

 

struct checksum_algo {    const char *name;          // 算法名(sha1/sha256)    int checksum_len;          // 哈希值长度    int der_len;               // DER前缀长度    const uint8_t *der_prefix; // DER编码前缀(适配ASN.1规范)    EVP_MD *(*calculate_sign)(); // 签名用哈希计算函数    int (*calculate)();        // 通用哈希计算函数};

 

作用:封装SHA1/SHA256两种哈希算法的核心属性,关联哈希计算的具体实现,是后续生成/验证哈希值的基础。

2. 加密算法结构体 crypto_algo

 

struct crypto_algo {    const char *name;          // 算法名(rsa2048/rsa4096)    int key_len;               // 密钥长度(字节)    int (*sign)();             // 签名函数    int (*add_verify_data)();  // 添加验证数据    int (*verify)();           // 验签函数};

 

作用:封装RSA2048/RSA4096两种非对称加密算法,关联签名/验签的核心逻辑,是验签的核心执行者。

3. 填充算法结构体 padding_algo

 

struct padding_algo {    const char *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:查找哈希算法

 

struct checksum_algo *image_get_checksum_algo(const char *full_name);

 

•输入:完整算法名(如sha256,rsa2048);

•逻辑:遍历checksum_algos数组,匹配前缀(如sha256)且后续字符为逗号,返回对应的哈希算法结构体;

•输出:匹配到的checksum_algo指针(NULL表示未匹配)。

(2)image_get_crypto_algo:查找加密算法

 

struct crypto_algo *image_get_crypto_algo(const char *full_name);

 

•输入:完整算法名(如sha256,rsa2048);

•逻辑:先截取逗号后的字符串(如rsa2048),再遍历crypto_algos数组匹配算法名;

•输出:匹配到的crypto_algo指针(NULL表示未匹配)。

(3)image_get_padding_algo:查找填充算法

 

struct padding_algo *image_get_padding_algo(const char *name);

 

•输入:填充算法名(如pkcs-1.5);

•逻辑:遍历padding_algos数组匹配名称;

•输出:匹配到的padding_algo指针(NULL表示未匹配)。

2. 辅助工具函数:FDT区域到镜像区域的转换

 

struct image_region *fit_region_make_list(const void *fit,    struct fdt_region *fdt_regions, int count,    struct image_region *region);

 

•核心作用:将FDT(设备树)格式的区域信息,转换为U-Boot镜像验签所需的image_region结构体;

•关键细节:

a.非SPL构建:自动调用calloc分配内存(SPL为节省代码量,要求调用者提前分配);

b.填充image_region的data(镜像数据指针)和size(数据长度);

c.输出调试信息(偏移量、大小),方便调试哈希区域。

3. 验签核心函数:从初始化到最终验证

验签逻辑是层层封装的,从“单节点验签”到“批量验签”,再到“配置节点验签”,形成完整的验证链路。

(1)fit_image_setup_verify:验签前的初始化

 

static int fit_image_setup_verify(struct image_sign_info *info,    const void *fit, int noffset, int required_keynode,    char **err_msgp);

 

•核心职责:为验签做准备,填充image_sign_info上下文;

•执行流程:

a.从FIT节点获取哈希算法名、填充方式(默认PKCS1.5);

b.初始化info结构体:密钥名、FIT镜像指针、节点偏移量;

c.调用算法查找函数,绑定哈希/加密/填充算法;

d.校验算法是否有效,无效则返回错误信息。

(2)fit_image_check_sig:单个签名节点的验签

 

int fit_image_check_sig(const void *fit, int noffset, const void *data,    size_t size, int required_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:遍历镜像节点的签名子节点

 

static int fit_image_verify_sig(const void *fit, int image_noffset,    const char *data, size_t size, const void *sig_blob,    int sig_offset);

 

•核心职责:遍历镜像节点下的所有签名子节点(以sig-开头),批量验签;

•执行流程:

a.遍历FIT镜像节点的所有子节点,筛选出签名节点;

b.对每个签名节点调用fit_image_check_sig验签;

c.只要有一个签名验证通过,标记verified=1并返回成功;

d.若遍历出错(如FDT结构损坏),返回错误信息。

(4)fit_image_verify_required_sigs:验证“必需”的签名

 

int fit_image_verify_required_sigs(const void *fit, int image_noffset,    const char *data, size_t size, const void *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:配置节点的验签(进阶)

 

int fit_config_check_sig(const void *fit, int noffset, int required_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烧录、硬件隔离等技术,才能构建真正的安全启动体系。

审核编辑 黄宇

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分