# nextpilot-secure-mavlink **Repository Path**: nextpilot/nextpilot-secure-mavlink ## Basic Information - **Project Name**: nextpilot-secure-mavlink - **Description**: 针对 MAVLink 明文通信问题,先通过 X.509 证书 + ECDSA 签名完成双向身份认证,经 P-256 ECDHE 协商会话密钥(HKDF 派生),再用该密钥对 MAVLink payload 做 AES-128-GCM 认证加密。 - **Primary Language**: Unknown - **License**: BSD-3-Clause - **Default Branch**: master - **Homepage**: https://nextpilot.org/blog/mavlink/ - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-07 - **Last Updated**: 2026-09-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![](logo.png) # MAVLink 安全通信:X.509 双向认证握手 + AES-128-GCM 加密 在飞控(PX4)与地面站(QGC)之间的 MAVLink 链路上实现**双向身份认证**与 **端到端加密**。链路建立后,无人机与地面站先通过 X.509 证书互相认证身份, 再经 P-256 ECDHE 协商出一次性会话密钥,后续业务数据均使用该密钥进行 AES-128-GCM 认证加密。由此提供三项安全能力:机密性(窃听者无法解密)、 完整性(篡改会被认证校验检测并丢弃)、身份认证(伪造身份会被拒绝)。 --- ## 快速开始 > 完整的「下载 → 生成证书 → 替换 QGC → 替换 PX4 → 编译联调」步骤见 > [使用教程.md](使用教程.md)。 不依赖真实飞控,即可在 PC 上验证完整链路: ```bash # 1) 克隆仓库 git clone https://gitee.com/nextpilot/nextpilot-secure-mavlink.git cd nextpilot-secure-mavlink # 2) 运行自测(Windows / Linux) cd test run_test.bat # Windows # sh run_test.sh # Linux / Git Bash ``` 输出四行 `ALL ... TESTS PASSED` 即表示算法库通过验证。 ```bash # 3) 运行双端示例(编译后分别在两个终端启动 UAV 与 GCS) cd ../example build.bat # Windows;Linux 使用 sh build.sh # 终端 1(无人机,server) uav\uav_main.exe 20 # 终端 2(地面站,client) gcs\gcs_main.exe 20 ``` 两端分别打印 `HANDSHAKE ESTABLISHED`,随后出现 `ATTITUDE (decrypted)` 与 `COMMAND_ACK arming ACCEPTED`,即表示握手与加密链路均正常。 > Windows 需要 gcc。脚本默认使用 `C:\nextpilot-windows-toolchain\...\gcc.exe`, > 可通过 `set CC=` 覆盖(Linux 使用 `export CC=...`)。 --- ## 1. 项目概述 ### 1.1 背景 无人机与地面站之间通过 **MAVLink** 协议通信(心跳、姿态、GPS、指令等消息), 底层为串口或数传链路,本质是明文传输。相关组件: - **PX4**:开源飞控固件,运行于无人机; - **QGC**(QGroundControl):开源地面站软件,运行于 PC / 平板; - **MAVLink**:两者的通信消息格式(本仓库 `common/` 目录即消息定义)。 ### 1.2 待解决的问题 MAVLink 链路在安全上有三块短板: **① 窃听(机密性)**:链路本质是明文串口/数传,任何拿到链路的人都能直接 读出位置、姿态、航点、任务等全部飞行数据——敏感信息完全暴露。 **② 篡改 / 伪造(完整性 + 真实性)**:不加密意味着第三方不仅能"看",还能 "改"——篡改指令、注入假消息,甚至冒充地面站下发命令。官方 signing 只能发现 "消息被改过",却加密不了内容,也识别不了"这条消息是谁发的"。 **③ 密钥管理(密钥分发)**:即使加了对称加密,若全网共用一把固定密钥,单台 设备泄露即全网泄露;更换密钥要重新刷固件,机群规模一大根本无法轮换。 ### 1.3 解决方案 核心思路:**先通过 X.509 证书 + ECDSA 签名完成双向身份认证,经 P-256 ECDHE 协商会话密钥(HKDF 派生),再用该密钥对 MAVLink payload 做 AES-128-GCM 认证加密。** | 目标 | 技术手段 | 效果 | | --- | --- | --- | | 身份认证 | 双方出示同一 CA 签发的 X.509 证书 + ECDSA 签名 | 防止身份伪造 | | 密钥协商 | P-256 ECDHE,每次连接生成独立密钥 | 前向保密,无需预置固定密钥 | | 加密与防篡改 | 使用握手协商出的会话密钥,对 MAVLink Payload 进行 AES-128-GCM 认证加密 | 机密性 + 完整性 | > 详细设计文档:[docs/MAVLINK_AES_GCM.md](docs/MAVLINK_AES_GCM.md)(加密帧)、 > [docs/MAVLINK_X509_HANDSHAKE.md](docs/MAVLINK_X509_HANDSHAKE.md)(握手协议)。 ### 1.4 目录结构 ``` ├── docs/ # 设计文档 │ ├── MAVLINK_AES_GCM.md # AES-GCM 帧结构、打包/解析流程 │ └── MAVLINK_X509_HANDSHAKE.md # 握手协议、集成 API、设计局限 ├── src/ # 源码 │ ├── c_library_v2/ # ★ 核心:MAVLink C 库(全部改动所在) │ ├── pymavlink/ # MAVLink 代码生成器改动 │ ├── PX4-Autopilot-1.17.0/ # PX4 参考改动 │ └── qgroundcontrol-4.4.5/ # QGC 参考改动 ├── test/ # PC 自测 ├── example/ # UAV↔GCS 握手 + 加密通信模拟器(UDP) ├── tools/ # 量产脚本:CA 生成、给设备发证 ├── 使用教程.md # ★ 完整集成教程(下载/发证/替换/编译) └── logo.png ``` 集成时要覆盖/新增到 PX4 与 QGC 的,是 `src/c_library_v2/` 下的 **7 个头文件** (下面 6 个自制 + `mavlink_types.h`)。均为 header-only(仅 `.h`、无 `.c`)、 无动态内存分配、C99,可直接集成到嵌入式固件: | 头文件 | 职责 | | --- | --- | | `mavlink_sha256.h` | SHA-256 哈希 | | `mavlink_aes_gcm.h` | AES-128-GCM 认证加密 | | `mavlink_helpers.h` | 加密发送 / 解密接收的接入点 | | `mavlink_ecc.h` | P-256 ECDH 密钥交换、ECDSA 签名验签、HMAC-SHA256 | | `mavlink_x509.h` | 最小 DER/X.509 证书校验(钉扎单一私有 CA) | | `mavlink_handshake.h` | 握手状态机、分片重组、HKDF、重传、密钥热替换 | | `mavlink_types.h` | 新增 `aes_nonce`/`aes_tag` 字段、`ENCRYPTED` 标志、解析状态 | --- ## 2. 方案设计 ### 2.1 术语表 下文用到的术语速查(正式定义见各设计文档): | 术语 | 定义 | | --- | --- | | MAVLink | 无人机与地面站之间的通信协议 | | PX4 / QGC | 飞控固件 / 地面站软件 | | X.509 | 证书标准格式 | | CA | 证书颁发机构;本项目为自建根证书 | | 证书 / 私钥 | 绑定设备公钥的身份凭证 / 仅持有者掌握的密钥 | | ECDHE | 椭圆曲线临时密钥交换,用于协商共享密钥 | | ECDSA | 椭圆曲线数字签名,私钥签名、公钥验签 | | HKDF | 密钥派生函数,将协商结果加工为目标密钥 | | AES-GCM | 对称认证加密算法 | | nonce / tag | 随机数 / 认证标签,分别用于防重放与防篡改 | | PSK | 预共享密钥(本项目已由握手协商机制取代) | | 会话密钥 | 本次连接临时协商的密钥 | | transcript | 握手过程哈希链,保证握手消息未被篡改 | | DER | 证书的二进制编码格式 | ### 2.2 总体流程 ``` 链路建立 │ ▼ ┌───────────────────────────────────────┐ │ 第 1 步:握手(3 个往返) │ │ · 双方互验证书,确认对方为可信成员 │ │ · 通过 ECDHE 协商临时会话密钥 │ │ · 将会话密钥写入加密状态(热替换) │ └───────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────┐ │ 第 2 步:加密通信 │ │ · 业务帧 payload 使用 AES-GCM 加密 │ │ · 每帧携带随机 nonce 与认证 tag │ │ · 篡改将被 tag 校验检测并丢弃 │ └───────────────────────────────────────┘ ``` ### 2.3 身份认证(X.509) 握手复用 MAVLink 现成的 **TUNNEL 消息(#385)** 承载握手数据(私有 payload_type 32769),无需修改 MAVLink 协议。GCS(client)主动发起,UAV (server)应答: 1. **证书交换**:双方各发送自己的 X.509 leaf 证书(内含公钥),对端使用 固件中钉扎的 CA 公钥点验证证书是否由该 CA 签发,否则拒绝连接; 2. **签名认证**:证书为公开数据,可被复制,因此双方还需使用各自私钥对握手 过程做 ECDSA 签名,证明持有与证书匹配的私钥; 3. **密钥协商**:双方各生成临时密钥对并交换公钥,经 ECDHE 各自计算出相同的 共享密钥;临时密钥每次连接重新生成,因此具备前向保密性; 4. **密钥派生**:通过 HKDF 将协商结果派生为 16 字节 AES 会话密钥,写入 `aes_state.key`。 握手的关键在于 **transcript**(哈希链):所有握手消息依次纳入哈希,最终的 ECDSA 签名与 FINISHED 校验均基于该哈希。中间人篡改任意握手字节都会导致签名 或校验失败,握手立即终止。 完整协议细节(消息序列、字节布局、状态机、超时重传、错误码)见 [docs/MAVLINK_X509_HANDSHAKE.md](docs/MAVLINK_X509_HANDSHAKE.md)。 ### 2.4 数据加密(AES-GCM) **AES-GCM** 为认证加密算法,单次操作同时实现加密与认证: - **加密**:明文转为密文; - **认证**:附带认证 tag,接收方校验 tag 即可检测数据是否被篡改。 选用理由:STM32、ESP32 等主流 MCU 内置 AES 硬件加速;纯软件实现资源占用低 (一个 S-box + 176 字节轮密钥),满足嵌入式资源约束。 相关术语: - **对称加密**:加密与解密使用同一密钥(AES-GCM); - **非对称加密**:公钥 / 私钥成对(ECC 用于签名与密钥交换); - **nonce**:随机数,确保同一密钥下各帧密文不同,防止重放; - **tag**:认证标签,用于完整性校验。 ### 2.5 加密帧结构 仅加密 payload,其余字段保持不变;帧尾追加 nonce 与 tag,共 28 字节: ``` ┌───────────┬──────────────┬─────┬──────────┬──────────────┬─────────────┐ │ header │ payload │ CRC │ signature│ nonce │ auth_tag │ │ (10B) │ (N bytes) │(2B) │(opt,13B) │ (12B,明文) │ (16B,明文) │ └───────────┴──────────────┴─────┴──────────┴──────────────┴─────────────┘ ← 原始 MAVLink v2 帧 → ← 加密追加部分 → ``` - `len` 仍为 payload 原始长度,不含 nonce/tag; - CRC 算法不变,仅覆盖 `header[1..9] + payload + crc_extra`; - header 的 `incompat_flags` 置 `0x02`(`ENCRYPTED` 位),标识加密帧; - 仅 MAVLink v2 支持(v1 无 `incompat_flags`); - 接收端在 CRC 之后若检测到 `ENCRYPTED` 位,则依次读取 nonce → tag,完成 解密与校验;非加密帧走原有路径。 > 完整打包 / 解析流程与状态机见 [docs/MAVLINK_AES_GCM.md](docs/MAVLINK_AES_GCM.md)。 --- ## 3. 软件实现 ### 3.1 C 库(`src/c_library_v2/`)——核心 | 文件 | 改动 | 说明 | | --- | --- | --- | | `mavlink_ecc.h` | 新增 | 纯软件 P-256:ECDH、ECDSA(RFC6979 确定性签名)、HMAC | | `mavlink_x509.h` | 新增 | 严格 DER 解析 + 证书校验(钉扎单一 CA) | | `mavlink_handshake.h` | 新增 | 握手状态机、分片重组、HKDF、超时重传 | | `mavlink_sha256.h` | 修改 | 补充完整 32 字节输出(原仅有截断的 48 位) | | `mavlink_aes_gcm.h` | 修改 | 新增握手旁路标志与加密判定函数 | | `mavlink_helpers.h` | 修改 | 收发两处加密判定统一调用判定函数 | | `mavlink_types.h` | 修改 | 新增 nonce/tag 字段、ENCRYPTED 标志、解析状态 | 握手对外暴露 6 个函数(集成示例见 `example/`): ```c void mavlink_handshake_init(ctx, role, aes, ca_pubkey65, cert_der, cert_len, privkey32, rng_cb, now_unix_cb); int mavlink_handshake_start(ctx); // 仅 GCS 主动发起 uint8_t mavlink_handshake_poll_tx(ctx, out128); // 取待发送的握手记录 void mavlink_handshake_handle_tunnel(ctx, payload, len, sysid, compid); // 收到握手帧时调用 void mavlink_handshake_tick(ctx, now_ms); // 周期调用,驱动超时重传 mavlink_handshake_state_t mavlink_handshake_state(ctx); uint8_t mavlink_handshake_alert(ctx); ``` 安全设计:证书与私钥均运行时注入(CA 公钥点、证书 DER、32 字节私钥标量), 库与示例不硬编码任何凭据;握手完成或失败后临时私钥立即清零。 ### 3.2 pymavlink 生成器(`src/pymavlink/`) pymavlink 为 MAVLink 的 C 代码生成器。共两处改动: 1. 将 7 个头文件复制到 `src/pymavlink/C/include_v2.0/`(覆盖 / 新增); 2. 修改 `src/pymavlink/mavgen_c.py` 的 `copy_fixed_headers()`,在 `"2.0"` 列表末尾追加 `mavlink_ecc.h / mavlink_x509.h / mavlink_handshake.h`。 > 详细改动说明见 [src/pymavlink/README.md](src/pymavlink/README.md)。 ### 3.3 PX4(`src/PX4-Autopilot-1.17.0/`) 飞控在协议里是 **server**:开机就在等 GCS 的 CLIENT_HELLO,自己永不主动发起。 - 将 7 个固定头覆盖到 `src/modules/mavlink/mavlink/pymavlink/generator/C/include_v2.0/`, **并修改同目录 `pymavlink/generator/mavgen_c.py` 的 `copy_fixed_headers()`**, 把新的 3 个头加进 `"2.0"` 列表——PX4 的 mavlink 头是构建时由 mavgen 拷进 `build/` 的,不改这里 `build/` 里根本不会有 `mavlink_handshake.h`; - 凭证:`fw/_credentials.h` 拷到 `src/modules/mavlink/mavlink_credentials.h`, 并在 `mavlink_main.cpp` 里改那 4 行 `MAV_CRED_*` 别名(见 4.2); - `mavlink_main.h/.cpp`:新增 `mavlink_aes_gcm_state_t` / `mavlink_handshake_ctx_t` 成员与互斥锁;构造函数调 `init_mavlink_security()`(SERVER 角色 + 注入随机源 + 绑定 `status->aes_gcm`);主循环顶部调 `update_handshake()`(**早于** `should_transmit()`,否则 `-w` 下不应答 CLIENT_HELLO); - `mavlink_receiver.cpp`:解析成功后先分流握手 TUNNEL(#385 / payload_type 32769), 再走 **fail-closed 入站闸门**——未 ESTABLISHED 或帧不带 `MAVLINK_IFLAG_ENCRYPTED` 一律丢弃。 > 分步说明见 [src/PX4-Autopilot-1.17.0/README.md](src/PX4-Autopilot-1.17.0/README.md)。 ### 3.4 QGC(`src/qgroundcontrol-4.4.5/`) 地面站是 **client**,负责主动发起握手。 - 将 7 个固定头覆盖到 `libs/mavlink/include/mavlink/v2.0/`; - 凭证:**运行时从磁盘读**,目录只有一个来源——`applicationDirPath() + "/certs"`, 即 exe 同级的 `certs/`(放 `ca_pub.der` / `gcs.cert.der` / `gcs.key.der`)。 加载失败**不会静默降级为明文**:发送方向已用随机占位密钥武装,结果是 「发出去的东西对端解不开」,配合飞控的 fail-closed 闸门就是链路彻底哑掉; - `LinkManager.cc`:链路连通后调 `startHandshakeForLink()` **主动发起**—— 不能等收到第一帧再懒初始化,飞控也在等,两边互等就是永久死锁; - `MAVLinkProtocol.h/.cc`:握手状态机、50ms 定时器驱动、TUNNEL 收发分流、 入站闸门、FAILED 自愈(3 秒后重新发起)。 > 分步说明见 [src/qgroundcontrol-4.4.5/README.md](src/qgroundcontrol-4.4.5/README.md)。 --- ## 4. 集成与使用 ### 4.1 编译宏 所有改动均由宏控制,未定义宏时编译产物与原生 MAVLink 一致: | 宏 | 作用 | | --- | --- | | `MAVLINK_USE_AES_ENCRYPTION` | 启用 AES-128-GCM 载荷加密。**承重**:它决定 `mavlink_message_t` 里有没有 `aes_nonce`/`aes_tag`,`sizeof` 差 28 字节 | | `MAVLINK_USE_X509_HANDSHAKE` | 启用 X.509 握手。`mavlink_types.h` 里已写明它隐含启用加密 | 注入方式: - **PX4**:`src/modules/mavlink/CMakeLists.txt` 里加 ```cmake target_compile_definitions(mavlink_c INTERFACE MAVLINK_USE_X509_HANDSHAKE) ``` (一个宏就够,X509 隐含 AES) - **QGC**(qmake):`qgroundcontrol.pro` 里加 ``` DEFINES += MAVLINK_USE_AES_ENCRYPTION MAVLINK_USE_X509_HANDSHAKE ``` > **为什么 PX4 用 `target_compile_definitions` 而不是 `add_definitions`**:这个宏 > 会改变 `mavlink_message_t` 的 `sizeof`,凡是包含 mavlink 头的编译单元都必须看到 > 同一个值,否则跨模块传递就是 ABI 不一致。挂在 `mavlink_c` 这个 INTERFACE 库上, > 所有依赖它的目标自动继承,不会有漏网的 TU。 > > ⚠️ 无论怎么写,宏都必须和那 7 个头文件来自**同一次构建**。只加宏、库头还是旧的, > 会直接编不过:`mavlink_aes_gcm.h` 引用不到 `msg->aes_nonce`。 > > ⚠️ **纯 PSK 模式(只定义 `MAVLINK_USE_AES_ENCRYPTION`)目前不可用**:QGC 侧 > `setupAESForLink()` 唯一的调用点在 X509 块里的 `startHandshakeForLink()`, > 只开 AES 宏时没有任何路径会去武装 `status->aes_gcm`,链路就是原版明文。飞控侧 > 硬编码的固定 PSK(`00 01 … 0F`)也已删除——那是一把谁也不共享的密钥,留着只会误导。 ### 4.2 证书生成与凭据注入(量产) 每台设备需持有:CA 公钥点(信任锚,全网一致)+ 本机证书 + 本机私钥(各设备 不同)。工具链见 [tools/README.md](tools/README.md): ```sh # ① 一次性:生成根 CA(ca.key.pem 为关键私钥,须离线保管) python tools/gen_pki.py --out-dir certs # ② 每台设备:发证(密钥对 + CA 签名 + 固件 C 头) python tools/gen_device_cert.py --ca-dir certs --device NP-FCS-H05 --out-dir fw ``` | 参数 | 含义 | | --- | --- | | `--ca-dir certs` | 用 `certs/` 里那把 CA 私钥去签名(也就是"谁签的") | | `--device NP-FCS-H05` | 设备标识,写进证书的 CN 字段——给这张证书起个名,一般填 SN | | `--out-dir fw` | 产物落到 `fw/`:`np-fcs-h05_credentials.h` + `.cert.der` + `.key.der` | 固件接入代码(两端一致,完整示例见 `example/gcs/gcs_main.c`): ```c mavlink_handshake_init(&hs, role, &aes, ca_pubkey, cert_der, cert_len, privkey, hw_trng /* 生产须接入硬件随机数 */, unix_clock /* NULL 则跳过有效期 */); if (role == MAVLINK_HANDSHAKE_ROLE_CLIENT) mavlink_handshake_start(&hs); /* 主循环:mavlink_handshake_tick() 计时;mavlink_handshake_poll_tx() 取记录打包为 TUNNEL 发送; * 收到 TUNNEL(#385, payload_type 32769) 调用 mavlink_handshake_handle_tunnel()。*/ /* mavlink_handshake_state() == MAVLINK_HANDSHAKE_ESTABLISHED 后,正常发送业务帧,GCM 自动加解密。*/ ``` ### 4.3 接入真实 PX4 / QGC - **PX4**:7 个头 → `src/modules/mavlink/mavlink/pymavlink/generator/C/include_v2.0/` (并同步改同仓库 `generator/mavgen_c.py` 的固定头列表);凭证头 → `src/modules/mavlink/mavlink_credentials.h`;3 个源文件 → `src/modules/mavlink/`;宏见 4.1;最后**删掉 `build/` 重新 configure**—— PX4 的 `add_custom_command` 依赖里没有列固定头,不删 `build/` 不会重跑 mavgen。 开机日志出现 `X.509 handshake armed (server), CA …` 才算成功。 - **QGC**:7 个头 → `libs/mavlink/include/mavlink/v2.0/`;3 个源文件 → `src/comm/`;三个 DER → **exe 同级的 `certs/`**(调试构建是在 `build/<套件>/staging/certs/`,不是源码根目录);宏见 4.1。 **一步一步照着做**:[使用教程.md](使用教程.md)(含每步的检查点与故障排查)。 参考实现与细节说明: - [src/PX4-Autopilot-1.17.0/README.md](src/PX4-Autopilot-1.17.0/README.md) —— PX4 侧改动; - [src/qgroundcontrol-4.4.5/README.md](src/qgroundcontrol-4.4.5/README.md) —— QGC 侧改动; - 握手胶水完整写法见 [example/](example/) 与握手设计文档。 --- ## 5. 测试与示例 ### 5.1 自测 ```bash cd test run_test.bat # Windows;Linux 使用 sh run_test.sh ``` | 套件 | 验证内容 | | --- | --- | | `test_aes_gcm` | AES-GCM 算法正确性(NIST 标准向量) | | `test_ecc` | P-256 点乘 / ECDH / ECDSA 正确性 | | `test_x509` | 证书校验:合法证书通过、篡改/伪造/过期拒绝 | | `test_handshake` | 完整握手回环:密钥一致、丢包自愈、篡改检测 | 测试证书由 `tools/gen_pki.py`(根 CA)+ `tools/gen_device_cert.py --demo` (gcs/uav leaf + `test_certs.h`)现场生成,**测试 CA 私钥随仓库公开,仅供 自测,不得用于生产**。 > 各测试文件的作用说明见 [test/README.md](test/README.md)。 ### 5.2 双端示例 ```bash cd example build.bat # Windows;Linux 使用 sh build.sh # 终端 1(无人机) uav\uav_main.exe 20 # 终端 2(地面站) gcs\gcs_main.exe 20 ``` 参数 `20` 表示 20% 丢包率(模拟链路不稳定),省略第二个参数表示持续运行。 在 20% 丢包下仍能观察到握手自动重传并最终 ESTABLISHED,验证协议抗丢包能力。 详见 [example/README.md](example/README.md)。 --- ## 6. 常见问题 ### 6.1 加密(AES-GCM) - **未定义编译宏**:改动无效果时,优先检查是否定义了 `MAVLINK_USE_AES_ENCRYPTION`(加密)与 `MAVLINK_USE_X509_HANDSHAKE`(握手); 两个宏都未定义时编译产物与原生 MAVLink 完全一致; - **v1 不支持**:加密仅对 MAVLink v2 生效; - **nonce 复用**:同一密钥下 nonce 不得重复,生产须接入硬件随机数; - **加密与 signing 并用**:GCM 已提供认证,同时启用 signing 属于冗余; - **帧长上限**:加密帧多 28 字节,`MAVLINK_MAX_PACKET_LEN` 由 280 调整为 308。 ### 6.2 握手(X.509) **先按告警码分流**——告警码决定你该查哪里,不要看到"握手失败"就去查证书: | 码 | 名称 | 真实含义 | 往哪查 | | --- | --- | --- | --- | | 5 | `TIMEOUT` | **对端一个字都没回,证书根本没交换过** | 对端固件有没有编进握手、链路是否双向通畅 | | 1 | `BAD_CERT` | 证书未通过校验 | 两端是不是同一把 CA 签发的 | | 3 | `BAD_SIGNATURE` | 签名 / FINISHED 校验失败 | 握手被篡改,或证书与私钥不配对 | | 2 | `UNSUPPORTED_SUITE` | 密码套件不一致 | 两端是否用同一版本的加密库 | | 4 | `FRAGMENT` | 握手报文分片异常 | 链路上有东西在改写报文 | - **告警码 5(最常见)**:默认 1 s 无进展重发整班,最多 4 次后判超时。它**和证书 无关**。先看对端开机日志里有没有 `X.509 handshake armed (server), CA …` —— 没有就是该固件没编进握手(多半是编译宏没生效,且 PX4 需删 `build/` 重编); - **告警码 1**:两端钉扎的 CA 公钥点不一致,或证书不是同一 CA 签发。注意仓库里 `certs/` 与 `tools/certs/` 是**两套 CA**,混用必报码 1; - **握手期间不发业务数据**:`ESTABLISHED` 之前业务帧仍会按加密标志处理,但 此时密钥无意义,务必等 `mavlink_handshake_state()==MAVLINK_HANDSHAKE_ESTABLISHED` 再发; - **GCS 端忘了 `mavlink_handshake_start()`**:client 必须主动发起握手,server 只等待; - **没接硬件 TRNG**:ECDHE 临时密钥与 nonce 依赖随机源,回调为 NULL 时退回 xorshift(仅调试),生产必须接硬件随机数; - **握手成功后 PSK 被热替换**:`aes_state.key` 会被协商密钥覆盖,无需(也不 该)再烧固定密钥。 - **⚠️ 飞控侧 fail-closed**:握手上之前,飞控连心跳都不往外发。所以**原版 QGC、 伴飞电脑、云台、数传中继这些没编加密库的对端全都连不上**——这是预期行为,不是 故障。调试只能用编了同一套库、装了同一套 CA 签发的 `certs/` 的 QGC。 - **⚠️ 飞控在地面重启后 QGC 连不回去**:QGC 是 client,握手建好就不再发 CLIENT_HELLO;飞控重启后回到 WAIT_CF 等它,两边互等。**把链路断开再重连一次** 即可。这是已知行为、有意不自动复位(否则能掐断流量的攻击者可反复逼出重握手)。 - **⚠️ 证书有效期不校验、也没有吊销机制**:`mavlink_handshake_init()` 的 `now_unix` 传 `nullptr`,库明确跳过有效期窗口。一张泄露的证书**永久有效**, 需要时请自行加校验或白名单。 --- ## 7. 关于我们 [NextPilot Flight Control](https://nextpilot.org) 是基于 RT-Thread 与 PX4 的先进飞控系统,面向教育、研究与工业等领域