ArduPilot Secure Boot 安全引导完全指南:密钥生成、签名固件构建与远程密钥管理
【免费下载链接】ardupilotArduPlane, ArduCopter, ArduRover, ArduSub source项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot
导读
Secure Boot(安全引导)是 ArduPilot 为应对 RemoteID(远程识别)等高防篡改需求场景提供的一套固件签名与校验机制:引导加载器内置最多 10 个公钥,任何未经对应私钥签名的固件都会被拒绝启动。本文以 Tools/scripts/signing/README.md 为核心,完整讲解从密钥生成、安全引导加载器构建、签名固件构建,到 DFU 刷写、状态验证、回退恢复以及基于 MAVLink SECURE_COMMAND 的远程密钥管理的全流程,并深入 Tools/scripts/signing 下的三个 Python 脚本与 libraries/AP_CheckFirmware 引导校验实现,帮助厂商与开发者一次性掌握 ArduPilot 安全引导的落地细节。
为什么需要 Secure Boot
ArduPilot 是一个开放源码自动驾驶仪项目,正常情况下任何人都可以编译固件并刷写到飞控上。但当设备用于需要"高强度防篡改"(high levels of tamper resistance)的合规场景时——典型代表是 RemoteID 远程识别——普通固件无法向监管方证明"运行在飞控上的代码确实是厂商发布的版本"。
Secure Boot 的解决思路是密码学签名校验:
- 引导加载器中内置最多 10 个公钥;
- 固件使用对应的一个私钥签名;
- 引导加载器在启动固件前校验签名,只要签名不匹配引导加载器中任何一个公钥,就拒绝启动。
也就是说,篡改者即使拿到飞控、刷入自编译固件,只要没有厂商私钥,固件就无法运行。这为 RemoteID 等合规审计提供了可验证的信任根。
整体工作流
从源码仓库看,Secure Boot 的完整链路由以下环节构成:
- 生成密钥对:generate_keys.py 基于 Monocypher 库生成私钥与公钥文件;
- 构建安全引导加载器:build_bootloaders.py 编译引导加载器,再经 make_secure_bl.py 将公钥写入其中,产物落盘到
Tools/bootloaders/; - 签名固件:
./waf configure --signed-fw配置启用签名固件构建,最终由 make_secure_fw.py 用私钥对 APJ 固件签名; - 刷写与验证:通过地面站常规方式或 DFU 刷入安全引导加载器,再加载签名固件;
- 远程密钥管理:通过 MAVLink2 的 SECURE_COMMAND 消息(如 MAVProxy
securecommand模块)远程增删公钥,甚至实现厂商间的设备移交。
一、生成密钥对
安装依赖
签名工具链依赖pymonocypher,且强制要求版本3.1.3.2。在 generate_keys.py 与 make_secure_fw.py 中都做了版本硬校验,版本不符会直接退出:
python3 -m pip install pymonocypher==3.1.3.2生成密钥
Tools/scripts/signing/generate_keys.py NAME执行后将产生两个文件:
NAME_private_key.dat—— 私钥文件;NAME_public_key.dat—— 公钥文件。
NAME可以是任意字符串,通常建议使用厂商名称,它只影响本地文件名,不参与任何密码学运算。
密钥文件格式
从 generate_keys.py 的实现可以看到,密钥文件并非裸二进制,而是带类型的文本格式:
- 私钥文件内容为
PRIVATE_KEYV1:<base64编码的32字节私钥>; - 公钥文件内容为
PUBLIC_KEYV1:<base64编码的32字节公钥>。
这里使用的算法是 Monocypher 的 EdDSA 签名体系:monocypher.generate_key()生成私钥,monocypher.compute_signing_public_key(private_key)推导公钥。make_secure_bl.py 和 make_secure_fw.py 在读取时会按_KEYV1:前缀校验密钥类型,PRIVATE/PUBLIC不匹配会直接报Invalid key type退出。
私钥保管要求
私钥必须保存在安全位置。公钥只是用来制作"只接受对应私钥签名固件"的安全引导加载器,泄露无妨;而私钥一旦泄露,攻击者即可签发出"合法"固件,安全引导形同虚设。建议将私钥离线存放、严格访问控制,绝不进入版本库或构建服务器明文目录。
二、构建安全引导加载器
构建命令
Tools/scripts/build_bootloaders.py BOARDNAME --signing-key=NAME_public_key.dat该命令会完成以下工作(见 build_bootloaders.py):
- 检查
libraries/AP_HAL_ChibiOS/hwdef/<board>/hwdef-bl.dat是否存在(不存在该文件的板卡没有引导加载器,会被跳过); - 以
--bootloader模式配置并编译引导加载器; - 将产物
build/<board>/bin/AP_Bootloader.bin复制为Tools/bootloaders/BOARDNAME_bl.bin,同时生成.elf; - 调用
make_secure_bl.py将公钥写入.bin与.elf; - 通过
Tools/scripts/bin2hex.py --offset 0x08000000生成对应的.hex文件。
下一次构建该板卡固件时,这个安全引导加载器会被自动打进 ROMFS,后续可以走"固件内嵌引导加载器刷写"的方式升级。
密钥校验
build_bootloaders.py 对--signing-key传入的密钥文件做了两层校验:
- 文件必须真实存在;
- 必须使用公钥——文件名中带有
private字样的私钥文件会被拒绝("You must use the public key in the bootloader")。
注意:--signing-key是action='append'参数,可以多次使用以同时嵌入多个公钥,例如:
Tools/scripts/build_bootloaders.py BOARDNAME \ --signing-key=NAME1_public_key.dat \ --signing-key=NAME2_public_key.dat默认包含 ArduPilot 官方密钥
构建安全引导加载器时,默认会同时嵌入 3 个 ArduPilot 官方公钥,它们位于 Tools/scripts/signing/ArduPilotKeys/(ArduPilot_public_key1.dat、ArduPilot_public_key2.dat、ArduPilot_public_key3.dat)。make_secure_bl.py 会遍历该目录自动追加这些密钥。
这样做的目的是:当用户在 Secure Boot 设置过程中出现失误(例如丢失私钥、误操作导致设备变砖)时,ArduPilot 核心开发团队可以用官方密钥签名固件帮助用户恢复设备,也避免出现"厂商倒闭/失联后用户无法再获得固件更新"的锁死局面。
如果确有充分理由不希望包含 ArduPilot 官方密钥,可以追加参数:
Tools/scripts/build_bootloaders.py BOARDNAME \ --signing-key=NAME_public_key.dat \ --omit-ardupilot-keys公钥数量的硬限制
make_secure_bl.py 定义了max_keys = 10与key_len = 32:引导加载器中公钥区最多容纳 10 个 32 字节公钥,超出会报Too many key files并退出。也就是说,厂商密钥 + ArduPilot 官方密钥合计不得超过 10 个。
签名写入原理
make_secure_bl.py 在引导加载器二进制中搜索 8 字节的签名描述符(0x4ecf4ea5a6b6f729),从描述符之后的位置开始,用若干个 32 字节公钥替换原占位数据,将公钥表直接烧录进引导加载器镜像。因此引导加载器固件必须在设计时预留出这块密钥区(链接脚本 / 描述符结构),这也是下文"支持板卡"一节中要求至少 32KB 引导加载器 Flash 空间的根本原因。
三、构建签名固件
方法一:先构建后签名(推荐用于发布)
以 Copter 固件为例:
./waf configure --board BOARDNAME --signed-fw ./waf copter ./Tools/scripts/signing/make_secure_fw.py build/BOARDNAME/bin/arducopter.apj NAME_private_key.dat--signed-fw配置项在 chibios_hwdef.py 中定义,它让固件构建流程为签名做好准备(应用描述符结构启用签名模式)。最后一步make_secure_fw.py用私钥对 APJ 固件完成签名。
方法二:构建参数内置私钥(适合开发迭代)
./waf configure --board BOARDNAME --signed-fw --private-key NAME_private_key.dat ./waf copter --upload这种方式把签名步骤并入waf构建链,可以一条命令完成构建 + 签名 + 上传,适合日常开发调试。
签名原理:make_secure_fw.py
make_secure_fw.py 的执行流程(全部细节都可在源码中核对):
- 读取 APJ 文件,其本质是 JSON,
image字段是zlib 压缩 + base64 编码的固件镜像(第 41-47 行); - 在解压后的镜像中搜索 8 字节的APP_DESCRIPTOR(
0x41a3e5f265699207),找到固件中的应用描述符位置(第 62-68 行); - 把镜像拆成描述符前、后两部分,用
monocypher.signature_sign(private_key, flash12)对除描述符外的全部固件内容做 EdDSA 签名(第 70-74 行); - 用
struct.pack("<IQ64s", ...)将签名打包进描述符:4 字节签名长度(72)、8 字节签名版本(30437)、64 字节签名本体(第 79-83 行)。签名版本号的存在是为了将来升级签名体系时保持向后兼容; - 重新压缩回写 APJ,并置
signed_firmware = true标志(第 89-96 行)。
引导加载器一侧的校验逻辑与之严格对应:AP_CheckFirmware.cpp 同样检查signature_length == 72、比对sig_version = 30437,然后用引导加载器中的每个公钥尝试验签,任何一个公钥验签通过即放行。
加载签名固件
签名后的固件(含内嵌的安全引导加载器)可以像普通固件一样通过地面站加载:
- MissionPlanner 的"Load custom firmware"功能;
- Linux 下使用 Tools/scripts/uploader.py。
四、刷写安全引导加载器
将安全引导加载器写入板卡有两种方法。
方法一:通过 ROMFS 内嵌引导加载器升级(最简)
按上一节步骤构建好签名固件后,签名固件的 ROMFS 中已经包含安全引导加载器。之后按常规引导加载器升级流程操作即可:向飞控发送 MAVLink 命令,让运行中的固件把 ROMFS 内嵌引导加载器刷入引导区。具体可参考 ArduPilot 官方的 bootloader 升级文档(MissionPlanner / QGroundControl / MAVProxy 均有对应操作指引)。
方法二:DFU 模式刷写
如果板卡的hwdef.dat与hwdef-bl.dat中启用了ENABLE_DFU_BOOT选项,且板卡基于STM32H7,地面站就可以让板卡进入 DFU 模式,随后用任意支持 DFU 的客户端将引导加载器.bin文件刷写到地址0x08000000。
重要限制:如果飞控上已经在运行安全引导加载器,它会拒绝切换到 DFU 模式(防止通过 DFU 绕过签名检查直接改写引导区)。因此 DFU 方式只适合"尚未启用安全引导"或"已回退到普通引导"的板卡。
五、如何确认正在使用安全引导
安全引导加载器在 USB 枚举时会在产品名中加入"Secure"字样。
Linux:断开 USB 后执行sudo dmesg -w,再插入 USB,终端中会出现类似如下行:
Product: BOARDNAME-Secure-BL-v10Windows:在设备管理器中查看引导加载器运行时的设备属性,找到 "Bus reported device description",其中同样包含上述 "Secure" 字符串。
需要注意:
- 该 "Secure" 字符串只出现在引导加载器阶段(USB 设备名中的版本号
v10即引导加载器版本标识); - 若想延长停留在引导加载器的时间以便观察,可以刷入一个普通未签名固件——安全引导加载器会因签名校验失败而永远停在引导加载器,不会启动固件,正好方便查看 USB 描述符。
六、回退到普通引导模式
Secure Boot 一旦启用,引导加载器只接受签名固件。要恢复普通模式,需要同时做两件事:换回普通引导加载器+清空引导加载器中的公钥。
步骤 1:准备普通引导加载器
将Tools/bootloaders/BOARDNAME_bl.bin替换为普通(未启用安全引导)的引导加载器文件。
步骤 2:MAVProxy 连接并清空公钥
用 MAVProxy 连接飞控,加载SecureCommand模块并用私钥建立安全会话:
module load SecureCommand securecommand set private_keyfile NAME_private_key.dat securecommand getsessionkey建立安全会话后,先查询当前公钥数量,再全部移除:
securecommand getpublickeys securecommand removepublickeys 0 X其中X是getpublickeys返回的公钥数量。例如标准固件包含 3 个 ArduPilot 公钥加 1 个自有公钥,则X = 4。
移除后再次执行securecommand getpublickeys确认返回 "No public keys"。这是设计上的关键点:当引导加载器中没有任何公钥时,ArduPilot 会无条件接受所有 SECURE_COMMAND,并且允许引导未签名固件(对应源码 AP_CheckFirmware.cpp 的all_zero_public_keys()判定逻辑)。
步骤 3:刷入带普通引导加载器的固件
退出 MAVProxy,用--signed-fw选项构建(仍按签名固件流程,但此时 ROMFS 内嵌的是普通引导加载器):
./waf configure --board BOARDNAME --signed-fw ./waf copter --upload步骤 4:升级引导加载器
按常规方式更新引导加载器,MAVProxy 中直接执行:
flashbootloader至此,板卡恢复普通引导模式,可以正常加载和运行未签名固件(包括刚刷入的这份固件)。
源码侧的防呆保护
值得注意的是,固件在允许刷写新引导加载器之前会做安全检查:AP_CheckFirmware_secure_command.cpp 的check_signed_bootloader()规定——如果当前引导加载器已配置公钥,则不允许刷入一个不含公钥的引导加载器,除非先清空当前公钥。这一机制避免了"安全引导设备误刷普通引导加载器导致安全机制被静默移除"的失误。
七、支持的板卡
Secure Boot 要求引导加载器拥有至少 32KB 的 Flash 空间用于存放公钥表与校验逻辑:
- 原生支持:所有基于STM32H7与STM32F7的板卡;
- 其他较老板卡:可以自行修改
hwdef.dat与hwdef-bl.dat,为引导加载器增加更多 Flash 空间后同样可以使用。
决定板卡是否有引导加载器构建的判据是libraries/AP_HAL_ChibiOS/hwdef/<board>/hwdef-bl.dat是否存在(build_bootloaders.py)。作为参考,仓库 CI 脚本 build_ci.sh 对CubeOrange-ODID与MatekL431-DShot两个板卡执行了完整的"配置--signed-fw+ 私钥签名 + 构建安全引导加载器"验证流程,可作为落地时的样板。
八、通过 MAVLink 远程管理公钥(SECURE_COMMAND)
如果持有引导加载器中某个公钥对应的私钥,就可以通过MAVLink2 的 SECURE_COMMAND 消息远程更改公钥,甚至移除全部公钥以允许加载未签名固件。这为"远程恢复误锁设备""厂商间移交设备管理权"提供了通道。
支持的操作
MAVProxy1.8.55 及以上版本内置securecommand模块,提供:
- 生成会话密钥(用于远程更新的安全会话建立);
- 获取当前公钥列表;
- 设置新的公钥(作为追加或替换);
- 移除全部公钥。
预期未来版本的 MissionPlanner 也会内置功能相同的插件。
底层实现
从源码看,SECURE_COMMAND 的处理链路是完整闭环的:
- 地面站消息进入 GCS_Common.cpp,
MAVLINK_MSG_ID_SECURE_COMMAND被转发给AP_CheckFirmware; - AP_CheckFirmware_secure_command.cpp 的
handle_secure_command()在验签通过后分发到四种操作:SECURE_COMMAND_GET_SESSION_KEY:生成会话密钥;SECURE_COMMAND_GET_PUBLIC_KEYS:按索引读取引导加载器首扇区中的公钥(跳过全零槽位);SECURE_COMMAND_SET_PUBLIC_KEYS:写入新公钥(校验索引与数量边界,最多 10 个槽位);SECURE_COMMAND_REMOVE_PUBLIC_KEYS:用全零数据清空指定槽位;
- 公钥区通过
find_public_keys()定位——它假定公钥存放在引导加载器的第一个 Flash 扇区(AP_CheckFirmware_secure_command.cpp),由链接脚本保证该假设成立。
厂商间移交场景
利用 SECURE_COMMAND 与 MAVLink 转发(MAVLink forwarding)组合,可以实现将一架飞行器的管理权从一家厂商移交给另一家厂商:先由当前厂商用其私钥建立安全会话,删除自己的公钥、写入新厂商的公钥,之后新厂商即可用自己的私钥正常签名并管理固件。整个过程无需物理接触设备。
九、安全注意事项总结
- 私钥即信任根:
NAME_private_key.dat必须离线安全保管,泄露即意味着安全引导可被绕过; - 公钥数量上限 10 个:厂商密钥与 ArduPilot 官方密钥合计不超过 10 个;
- 默认含 ArduPilot 官方密钥:这是刻意的兜底设计,除非有非常明确的理由,否则不建议使用
--omit-ardupilot-keys; - DFU 与 Secure Boot 互斥:运行安全引导加载器的板卡拒绝进入 DFU,回退时必须先清空公钥再刷普通引导加载器;
- 签名工具链版本锁定:
pymonocypher必须为3.1.3.2,密钥文件必须匹配_KEYV1:格式前缀; - 签名版本号机制:固件签名携带版本号
30437(AP_CheckFirmware.cpp),为未来签名算法演进预留了升级空间。
十、快速参考:完整命令清单
# 1. 安装签名依赖(版本锁定) python3 -m pip install pymonocypher==3.1.3.2 # 2. 生成密钥对 Tools/scripts/signing/generate_keys.py MYVENDOR # 3. 构建安全引导加载器(默认附带 3 个 ArduPilot 官方密钥) Tools/scripts/build_bootloaders.py BOARDNAME --signing-key=MYVENDOR_public_key.dat # 4. 构建签名固件(先构建后签名,适合发布) ./waf configure --board BOARDNAME --signed-fw ./waf copter ./Tools/scripts/signing/make_secure_fw.py build/BOARDNAME/bin/arducopter.apj MYVENDOR_private_key.dat # 4'. 或构建参数内置私钥(构建 + 签名 + 上传一步完成,适合开发) ./waf configure --board BOARDNAME --signed-fw --private-key MYVENDOR_private_key.dat ./waf copter --upload # 5. 验证安全引导状态(Linux) sudo dmesg -w # 插入 USB 后查找 "Product: BOARDNAME-Secure-BL-v10"围绕本文涉及的工具链,仓库中还提供了 Tools/scripts/signing/ArduPilotKeys/ 官方公钥样例、CI 落地样板 Tools/scripts/build_ci.sh,以及引导校验核心实现 libraries/AP_CheckFirmware,可供进一步研读与复用。
【免费下载链接】ardupilotArduPlane, ArduCopter, ArduRover, ArduSub source项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考