ArduPilot Secure Boot 安全引导完全指南:密钥生成、签名固件构建与远程密钥管理
2026/9/14 2:19:35 网站建设 项目流程

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 的完整链路由以下环节构成:

  1. 生成密钥对:generate_keys.py 基于 Monocypher 库生成私钥与公钥文件;
  2. 构建安全引导加载器:build_bootloaders.py 编译引导加载器,再经 make_secure_bl.py 将公钥写入其中,产物落盘到Tools/bootloaders/
  3. 签名固件./waf configure --signed-fw配置启用签名固件构建,最终由 make_secure_fw.py 用私钥对 APJ 固件签名;
  4. 刷写与验证:通过地面站常规方式或 DFU 刷入安全引导加载器,再加载签名固件;
  5. 远程密钥管理:通过 MAVLink2 的 SECURE_COMMAND 消息(如 MAVProxysecurecommand模块)远程增删公钥,甚至实现厂商间的设备移交。

一、生成密钥对

安装依赖

签名工具链依赖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):

  1. 检查libraries/AP_HAL_ChibiOS/hwdef/<board>/hwdef-bl.dat是否存在(不存在该文件的板卡没有引导加载器,会被跳过);
  2. --bootloader模式配置并编译引导加载器;
  3. 将产物build/<board>/bin/AP_Bootloader.bin复制为Tools/bootloaders/BOARDNAME_bl.bin,同时生成.elf
  4. 调用make_secure_bl.py将公钥写入.bin.elf
  5. 通过Tools/scripts/bin2hex.py --offset 0x08000000生成对应的.hex文件。

下一次构建该板卡固件时,这个安全引导加载器会被自动打进 ROMFS,后续可以走"固件内嵌引导加载器刷写"的方式升级。

密钥校验

build_bootloaders.py 对--signing-key传入的密钥文件做了两层校验:

  • 文件必须真实存在;
  • 必须使用公钥——文件名中带有private字样的私钥文件会被拒绝("You must use the public key in the bootloader")。

注意:--signing-keyaction='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.datArduPilot_public_key2.datArduPilot_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 = 10key_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 的执行流程(全部细节都可在源码中核对):

  1. 读取 APJ 文件,其本质是 JSON,image字段是zlib 压缩 + base64 编码的固件镜像(第 41-47 行);
  2. 在解压后的镜像中搜索 8 字节的APP_DESCRIPTOR0x41a3e5f265699207),找到固件中的应用描述符位置(第 62-68 行);
  3. 把镜像拆成描述符前、后两部分,用monocypher.signature_sign(private_key, flash12)除描述符外的全部固件内容做 EdDSA 签名(第 70-74 行);
  4. struct.pack("<IQ64s", ...)将签名打包进描述符:4 字节签名长度(72)、8 字节签名版本(30437)、64 字节签名本体(第 79-83 行)。签名版本号的存在是为了将来升级签名体系时保持向后兼容;
  5. 重新压缩回写 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.dathwdef-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-v10

Windows:在设备管理器中查看引导加载器运行时的设备属性,找到 "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

其中Xgetpublickeys返回的公钥数量。例如标准固件包含 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 空间用于存放公钥表与校验逻辑:

  • 原生支持:所有基于STM32H7STM32F7的板卡;
  • 其他较老板卡:可以自行修改hwdef.dathwdef-bl.dat,为引导加载器增加更多 Flash 空间后同样可以使用。

决定板卡是否有引导加载器构建的判据是libraries/AP_HAL_ChibiOS/hwdef/<board>/hwdef-bl.dat是否存在(build_bootloaders.py)。作为参考,仓库 CI 脚本 build_ci.sh 对CubeOrange-ODIDMatekL431-DShot两个板卡执行了完整的"配置--signed-fw+ 私钥签名 + 构建安全引导加载器"验证流程,可作为落地时的样板。

八、通过 MAVLink 远程管理公钥(SECURE_COMMAND)

如果持有引导加载器中某个公钥对应的私钥,就可以通过MAVLink2 的 SECURE_COMMAND 消息远程更改公钥,甚至移除全部公钥以允许加载未签名固件。这为"远程恢复误锁设备""厂商间移交设备管理权"提供了通道。

支持的操作

MAVProxy1.8.55 及以上版本内置securecommand模块,提供:

  • 生成会话密钥(用于远程更新的安全会话建立);
  • 获取当前公钥列表
  • 设置新的公钥(作为追加或替换);
  • 移除全部公钥

预期未来版本的 MissionPlanner 也会内置功能相同的插件。

底层实现

从源码看,SECURE_COMMAND 的处理链路是完整闭环的:

  1. 地面站消息进入 GCS_Common.cpp,MAVLINK_MSG_ID_SECURE_COMMAND被转发给AP_CheckFirmware
  2. 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:用全零数据清空指定槽位;
  3. 公钥区通过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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询