跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

OpenWrt 固件签名三剑客:usign、ucert 与 fwtool 深度解析

OpenWrt 的固件签名验证体系由三个工具分层协作构成:usign 提供最底层的 Ed25519 密码学签名能力,ucert 在其之上构建证书链与吊销机制,fwtool 则负责将签名数据以向后兼容的方式嵌入固件镜像。三者各司其职,缺一不可。以下结合各工具的完整命令行接口与实际使用示例,逐一展开其操作细节与协作关系。

一、usign:Ed25519 签名引擎的使用

usign 是 OpenWrt 对 OpenBSD signify 的衍生实现,使用 Ed25519 算法对文件进行签名与验签。它在 OpenWrt 源码树中是一个独立的包,依赖 libubox,安装后还会创建 signify 符号链接以保持兼容性。

命令行接口

Usage: usign <command> <options>
Commands:
  -V:                   verify (needs at least -m and -p|-P)
  -S:                   sign (needs at least -m and -s)
  -F:                   print key fingerprint of public/secret key or signature
  -G:                   generate a new keypair (needs at least -p and -s)
Options:
  -c <comment>:         add comment to keys
  -m <file>:            message file
  -p <file>:            public key file (verify/fingerprint only)
  -P <path>:            public key directory (verify only)
  -q:                   quiet (do not print verification result, use return code only)
  -s <file>:            secret key file (sign/fingerprint only)
  -x <file>:            signature file (defaults to <message file>.sig)

分步使用指南

生成密钥对:-G 需要同时指定公钥和私钥的输出路径。生成的私钥文件包含 Ed25519 种子,公钥文件包含对应的公钥和指纹。

usign -G -p capubkey -s caseckey

这一步在离线环境中执行,生成的根密钥对应当妥善保管,私钥永不离开离线系统。

签名文件:-S 对指定消息文件进行签名。若省略 -x,签名默认写入 <message file>.sig。

usign -S -m message.bin -s seckey -x message.bin.sig

签名文件内部结构包含算法标识("Ed")、8 字节指纹和 64 字节 Ed25519 签名。usign 在签名时会读取整个消息文件并计算签名,不涉及任何证书或信任链逻辑。

验证签名:-V 需要至少提供消息文件和公钥来源。-p 指定单个公钥文件,-P 指定公钥目录(usign 会根据签名中的指纹在目录中查找对应公钥)。

usign -V -m message.bin -p pubkey
# 或从目录中查找
usign -V -m message.bin -P /etc/opkg/keys

-q 选项在脚本中广泛使用,它抑制所有人类可读输出,仅通过返回码(0 成功,非 0 失败)传达结果。

查看指纹:-F 可打印公钥、私钥或签名文件的指纹,便于在密钥管理中核对身份。

usign -F -p capubkey
usign -F -x message.bin.sig

在 OpenWrt 中的实际定位

usign 的 Ed25519 签名在 OpenWrt 中有两类用途:OPKG 包管理器用它验证仓库元数据(Packages 文件),而固件镜像的签名则由 ucert 封装后使用。usign 本身只回答“某文件的签名是否由对应私钥生成”,不涉及信任决策。

二、ucert:证书链与吊销机制的使用

ucert 是“基于 usign 的签名链封装工具”,使用 libubox 的 blob/blobmsg 机制进行数据封装,并支持导出为 JSON 格式。它增加了证书签发、签名追加和密钥吊销的能力,使构建服务器无需持有根私钥即可完成签名。

命令行接口

Usage: ucert <command> <options>
Commands:
  -A:                   append signature (needs -c and -x)
  -D:                   dump (needs -c)
  -I:                   issue cert and revoker (needs -c and -p and -s)
  -R:                   process revoker certificate (needs -c and -P)
  -V:                   verify (needs -c and -p|-P, may have -m)
Options:
  -c <file>:            certificate file
  -m <file>:            message file (verify only)
  -p <file>:            public key file
  -P <path>:            public key directory (verify only)
  -q:                   quiet (do not print verification result, use return code only)
  -s <file>:            secret key file (issue only)
  -x <file>:            signature file (append only)

分步使用指南:从签发到验证

以下流程完整展示委托签名的操作序列,与 ucert 官方 README 中的示例一致。

步骤一:离线签发委托证书。在 airgap 系统中,用根私钥为构建服务器的公钥签发授权证书。-I 命令同时生成委托证书和对应的吊销者证书。

ucert -I -p pubkey -s caseckey -c newcert

执行后,newcert 包含根私钥对委托公钥的签名,以及一个吊销者证书的种子。

步骤二:构建服务器追加签名。将 newcert 复制为消息证书,然后用委托私钥签名消息,并将签名追加到证书中。

cp newcert message.bin.ucrt
usign -S -m message.bin -s seckey -x message.bin.sig
ucert -A -c message.bin.ucrt -x message.bin.sig

-A 操作将 usign 生成的原始签名嵌入到 message.bin.ucrt 中,形成完整的证书链文件。此时 .ucrt 文件包含了从根到委托的信任路径以及消息本身的签名。

步骤三:设备端验证。设备使用本地公钥目录(/etc/opkg/keys)验证证书链和消息签名。

ucert -V -P /etc/opkg/keys -m message.bin -c message.bin.ucrt

-V 会依次完成:验证证书链中的根签名、验证委托证书中的公钥身份、验证消息本身的签名是否由委托密钥生成。-q 可抑制输出,仅依赖返回码。

步骤四:处理吊销。当某个委托密钥需要作废时,将吊销者证书分发到设备端并执行 -R。

ucert -R -P /etc/opkg/keys -c all-revokers.ucrt

-R 会验证每个吊销者证书的合法性(对照公钥目录),验证通过后在 /etc/opkg/keys 中为被吊销的密钥创建死符号链接,使其永久失效。吊销者证书可以简单地通过 cat 追加,分发者无需了解 ucert 内部格式。

调试工具:-D 将证书内容以 JSON 格式导出,便于检查证书链结构和签名状态。

ucert -D -c message.bin.ucrt

安全边界说明

ucert 的 README 明确承认:它“继承”了 usign 的密码学属性,而 usign 本身并非为证书链用法而设计。解析器继承自 libubox,后者也并非为密码学用途而构建。文档建议为进一步提升密码学标准,可能需要对签名载荷格式进行优化。这意味着 ucert 的安全性完全依赖于 usign 的 Ed25519 实现,而非额外的协议加固。

三、fwtool:固件镜像容器的使用

fwtool 是唯一直接操作固件二进制文件的工具。它在镜像尾部附加元数据或签名数据块,利用文件系统结束标记和 tar 解包忽略尾部数据的特性,实现向后兼容。

命令行接口

Usage: fwtool <options> <firmware>
Options:
  -S <file>:            Append signature file to firmware image
  -I <file>:            Append metadata file to firmware image
  -s <file>:            Extract signature file from firmware image
  -i <file>:            Extract metadata file from firmware image
  -t:                   Remove extracted chunks from firmware image (using -s, -i)
  -T:                   Output firmware image without extracted chunks to stdout (using -s, -i)
  -q:                   Quiet (suppress error messages)

分步使用指南

附加签名到固件:在构建流程的最后一步,将 ucert 组装好的证书文件附加到镜像尾部。

fwtool -S image.bin.ucert image.bin

-S 将签名块追加到文件末尾。块结构包含 CRC32 校验、类型标识和 magic 信息,使 fwtool 能快速定位尾部数据而无需全文搜索。

提取签名:从固件中分离出签名块,用于验证。

fwtool -q -s /tmp/sysupgrade.ucert /tmp/sysupgrade.bin

-q 抑制错误输出。提取的 .ucert 文件随后交给 ucert -V 进行验证。

剥离尾部并输出纯净镜像:这是签名验证流程中的关键操作。usign 当初签名的对象是未附加任何尾部数据的原始镜像,因此验证时必须提供同样的纯净数据流。

fwtool -q -T -s /dev/null /tmp/sysupgrade.bin

-T 将剥离了签名块(输出到 /dev/null)的镜像主体输出到标准输出。-s /dev/null 表示丢弃提取的签名(因为签名已在前一步单独提取)。输出通过管道传给 ucert:

fwtool -q -T -s /dev/null "$1" | ucert -V -m - -c "/tmp/sysupgrade.ucert" -P /etc/opkg/keys

ucert 的 -m - 表示从标准输入读取消息,即 fwtool 输出的纯净镜像流。

移除尾部数据:-t 与 -T 的区别在于,-t 直接修改原始文件,将提取过的数据块从文件中截除;-T 则保留原文件不变,仅将剥离后的内容输出到 stdout。在需要保留原始镜像的场景中应使用 -T。

兼容性设计要点

fwtool 附加的数据位于 squashfs/jffs2 文件系统结束标记之后,系统首次启动时该区域会被覆盖;对于 sysupgrade tar 包,tar 解包过程会忽略尾部多余数据。数据块本身包含 CRC32,即使镜像未签名也能检测文件损坏。后续的 fwtool 版本还修复了一个边界情况:当镜像不含签名时,工具不会错误地截断最后 16 字节,确保管道操作不会损坏非签名镜像。

四、完整协作链条与验证流程

构建阶段的完整命令序列

OpenWrt 的 image-commands.mk 中对每个需要签名的镜像执行:

cp "$(BUILD_KEY).ucert" "$@.ucert"
usign -S -m "$@" -s "$(BUILD_KEY)" -x "$@.sig"
ucert -A -c "$@.ucert" -x "$@.sig"
fwtool -S "$@.ucert" "$@"

依次完成:复制证书模板 → usign 对镜像主体签名 → ucert 将签名装入证书 → fwtool 将证书附加到镜像尾部。

设备端的验证逻辑

/lib/upgrade/fwtool.sh 中的验证函数执行以下步骤:

  1. 用 fwtool -q -s /tmp/sysupgrade.ucert "$1" 从固件中提取签名证书。
  2. 用 fwtool -q -T -s /dev/null "$1" | ucert -V -m - -c "/tmp/sysupgrade.ucert" -P /etc/opkg/keys 剥离尾部并验证。

若 ucert 验证通过,返回 0;失败则返回非 0。REQUIRE_IMAGE_SIGNATURE 变量控制验证失败时是否拒绝升级:默认未设置时,验证失败会被放行(静默跳过);设置为 1 时则强制拒绝。

一个被默认配置掩盖的路径缺陷

在 OpenWrt 的 apk 构建分支中,usign 公钥的安装逻辑位于 opkg 分支内,apk 构建中永远不会执行。而 fwtool.sh 硬编码从 /etc/opkg/keys 读取公钥。结果:apk 构建的自签名镜像在设备上找不到任何信任密钥。由于 REQUIRE_IMAGE_SIGNATURE 默认关闭且验证函数在无法验证时返回 0,这一缺陷在默认配置下完全静默,签名验证实际上从未执行。只有手动开启强制校验时才会暴露问题。

总结

usign 提供纯 Ed25519 签名与验签(-G 生成密钥、-S 签名、-V 验证、-F 查指纹),ucert 在签名之上构建委托与吊销(-I 签发、-A 追加、-V 验证、-R 吊销、-D 导出),fwtool 负责镜像封装(-S 附加、-s 提取、-T 剥离输出)。三者的命令行操作共同构成了从密钥管理到固件验证的完整工作流。理解每个工具的分步用法,不仅有助于手动执行签名或排查验证失败,也能揭示 OpenWrt 在“向后兼容”与“安全验证”之间所做的工程权衡。

作者

老丹

关注我
其他文章
上一个

SSH 算法协商完全解析:密钥交换、主机密钥与加密算法的角色与协作

下一个

应用级 MQTT 服务搭建完全指南:Mosquitto + 宿主机 Nginx

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

近期文章

  • 在 Windows 里养一只 Linux:WSL 完整指南 2026年10月24日
  • Paho MQTT C 与 C++ 客户端库完全指南:从概念到 Ubuntu 26.04 下的编译、安装与使用 2026年10月23日
  • Mosquitto $SYS 主题详解:Broker 自监控指南 2026年10月23日
  • 应用级 MQTT 服务搭建完全指南:Mosquitto + 宿主机 Nginx 2026年10月22日
  • OpenWrt 固件签名三剑客:usign、ucert 与 fwtool 深度解析 2026年10月21日

文章分类

  • C/C++开发 (22)
  • Docker容器 (5)
  • Linux工具包 (21)
  • Linux服务配置 (53)
  • Linux系统 (16)
  • MQTT技术 (6)
  • OpenWrt路由 (5)
  • Shell脚本 (3)
  • 代码管理 (1)
  • 安防技术 (4)
  • 数据安全 (36)
  • 网络协议 (25)
  • 计算机理论 (23)
  • 问题解决 (3)
  • 防火墙 (6)
  • 音视频技术 (6)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号