MAVLink 安全通信系列(8):常见问题与排障
返回系列总目录。
本系列第 8 篇(补充篇)。将主线中容易遇到的问题集中成一份速查:编译宏、 证书、握手、随机源、内存、业务时机、凭证管理,最后给一张 ALERT 错误码表 和一条排查流程图。遇到问题先按现象对号入座。
1. 编译与宏
改动完全没效果,产物和原生 MAVLink 一样
这是最常见的问题。所有改动都由两把宏控制:
| 宏 | 作用 |
|---|---|
MAVLINK_USE_AES_ENCRYPTION | 启用 AES-128-GCM 载荷加密 |
MAVLINK_USE_X509_HANDSHAKE | 启用 X.509 握手(隐含启用加密) |
两个宏都不定义时,编译产物与原生 MAVLink 逐字节一致——这是设计上的向后 兼容,但也意味着"忘了定义宏 = 白改"。注入方式:
# PX4
add_definitions(-DMAVLINK_USE_AES_ENCRYPTION -DMAVLINK_USE_X509_HANDSHAKE)# QGC
DEFINES += MAVLINK_USE_AES_ENCRYPTION MAVLINK_USE_X509_HANDSHAKE只想要加密、不想要握手(用固定 PSK)?只定义 MAVLINK_USE_AES_ENCRYPTION 即可。
v1 上加密不生效:加密仅对 MAVLink v2 生效,v1 没有 incompat_flags 字段, 无法声明"加密帧"(见第 1 篇)。
2. 证书相关
握手报 BAD_CERT(证书校验失败)
按可能性从高到低排查:
- 两端钉扎的 CA 公钥点不一致——UAV 和 GCS 用了不同的
ca_pubkey; - 证书不是同一 CA 签发——对端证书是另一个 CA 签的;
- 证书过期——
notBefore/notAfter之外(需提供了now_unix回调才校验); - 证书被篡改 / 截断 / 编码非法——DER 解析边界检查不通过。
证书过期不报错:now_unix 回调为 NULL 时,实现会跳过有效期校验。这是 实现行为,不是安全上可默认接受的策略;用于生产时应在建立握手前确保可信时间已 就绪,并提供 uint64_t (*now_unix)(void),否则应明确拒绝握手或采用经过评审的 替代时间/有效期策略。
证书可以复制吗:可以,证书是公开数据。所以握手还要签名自证(VERIFY 记录)证明"对端持有证书对应私钥"。证书验证只检查签发链/策略,不能代替私钥 持有证明;证书是否撤销还需 CRL/OCSP 或其他机制,而当前实现没有在线撤销检查。 本实现也没有将证书身份绑定到 sysid。
3. 握手相关
握手超时失败(TIMEOUT)
默认逻辑:航班全部发出后 1000ms 无进展 → 从头发整个航班,最多 4 次, 仍无进展即 FAILED(TIMEOUT)。排查:
- 是否周期调用了
mavlink_handshake_tick(now_ms)(建议每 100ms 一次); - 链路丢包是否过重(可先用
loss=0排除); - 对端是否真的在运行握手(server 只等待,不会主动发起)。
GCS 一直不动:client 必须主动调用 mavlink_handshake_start(),server 只 应答。GCS 忘了 start() 是常见坑。
签名 / FINISHED 校验失败(BAD_SIGNATURE):握手消息被篡改,或证书与 私钥不配对(证书里的公钥对不上注入的私钥标量)。
FAILED 后能不能续:不能。FAILED 是终态,任何失步只能重新 init 从头 握手——状态机刻意不提供"半截恢复",换实现简单与可证性(见第 5 篇)。
4. 随机源
没接安全随机源会怎样:ECDHE 临时私钥、握手随机数与每帧 nonce 都依赖随机源。 回调为 NULL 时退回内置 xorshift32——它不是密码学安全随机数生成器,仅允许 非安全演示。生产应接硬件熵源支持的 CSPRNG/DRBG,确保正确初始化、熵健康检测和 故障时 fail-closed,否则:
- ECDHE 临时私钥或 random 可预测 → 会话秘密可能被恢复,PFS 不成立;
- nonce 可能重复 → AES-GCM 的机密性和认证保证被破坏(见第 2 篇)。
当前随机回调接口不向调用方传播熵源失败状态;集成层必须在随机源异常时禁止握手 和加密,不能返回伪造数据或静默退回 xorshift。
mavlink_handshake_init(&hs, role, &aes, ca_pubkey, cert_der, cert_len,
privkey, hw_trng_callback /* 生产接硬件 TRNG */, now_unix);5. 内存与栈
嵌入式上握手栈溢出:这是资源类问题里最该先查的。纯软件实现无堆分配, 但栈峰值约 3~4 KB(峰值在"收证书 → X.509 验签 → ECDSA 验签"链路上)。 握手应放独立线程并分配 4~6 KB 栈,别塞进默认 1~2 KB。
| 项 | 数值 |
|---|---|
| Flash(握手全栈) | P-256 ~7.2 KB + 状态机 ~4.3 KB + X.509 ~2.2 KB + SHA-256 ~1.2 KB ≈ 15 KB |
| 全栈 Flash | 加 AES-GCM 约 2 ~ 4 KB,共 17 ~ 19 KB |
| RAM | mavlink_handshake_ctx_t 2272 字节/链路 + mavlink_aes_gcm_state_t 40 字节 |
| 栈峰值 | 约 3~4 KB |
帧长溢出:加密帧多 28 字节(nonce 12 + tag 16),MAVLINK_MAX_PACKET_LEN 已由 280 调为 308,别用旧值裁缓冲。
6. 业务时机
握手期间业务帧被加密了但没意义:ESTABLISHED 之前业务帧仍会按加密标志 照常加密,但此时密钥无意义(握手还没协商出会话密钥)。上层必须保证 mavlink_handshake_state() == MAVLINK_HANDSHAKE_ESTABLISHED 之后再发业务帧。
握手成功后 PSK 被覆盖:aes_state.key 会被协商出的会话密钥热替换,无需 (也不该)再烧固定密钥。这是设计行为,不是 bug。
加密与 signing 并用:是否同时启用取决于用途与实现兼容性。GCM 标签能认证 密文和 AAD,但本项目当前一把会话密钥双向复用,不能从标签判断发送方向;v2 signing 使用另一套共享密钥/时间戳机制,可能提供不同的重放状态语义,但不会补上 未纳入 GCM AAD 的 len/msgid。不要仅以“冗余”决定开关,需检查叠加后的线格式、 认证覆盖范围和接收端行为。
nonce 能防业务消息重放吗:不能。nonce 只参与 GCM 的密钥流与认证计算;接收端 没有记住已用 nonce 时,原样重放同一会话中的合法帧仍可能验证通过。MAVLink seq 是 8 位循环字段,当前加密解析逻辑也没有基于它的 anti-replay 窗口。需由协议/ 应用另外实现认证的新鲜性计数器、窗口及重启后的状态恢复。
GCM 覆盖完整 MAVLink header 吗:不覆盖。当前 AAD 为 5 字节: incompat_flags、compat_flags、seq、sysid、compid;len 与 msgid 未纳入认证。CRC 可以重算,因此不能把 CRC 或 GCM 标签解释为完整帧头保护。若 这些字段参与解析、路由或授权决策,应在投入前修订 AAD 格式并同步两端。
7. 凭证与私钥管理
| 凭证 | 敏感性 | 红线 |
|---|---|---|
| CA 公钥点(65B) | 公开 | 全网一致,烧进设备当信任锚 |
| 本机证书 DER | 公开 | 可随固件分发 |
| 本机私钥标量(32B) | 机密 | 绝不落盘、绝不外传 |
CA 私钥 ca.key.pem | 机密 | 离线/加密保管,绝不进设备 |
- 优先方案:私钥在安全元件内生成并不可导出,产线只拿 CSR 去签;
- 无法使用安全元件时:应设计受控的密钥注入、设备唯一性、镜像/备份保护和 泄露响应流程。把私钥编入固件后“烧完即删”不足以保证私钥不被复制或恢复,必须 作为明确接受的残余风险进行评审;
- 测试 CA 私钥(
test/test_certs.h、tools/certs/里那套)随仓库公开, 严禁用于生产,生产凭证用gen_pki.py/gen_device_cert.py在离线环境另建。
8. ALERT 错误码速查表
握手失败时,mavlink_handshake_alert(ctx) 返回 1 字节错误码(定义在 mavlink_handshake.h):
| 值 | 符号 | 含义 | 常见原因 |
|---|---|---|---|
| 1 | BAD_CERT | 证书校验失败 | CA 公钥点不一致 / 非同一 CA / 过期 / 非法 DER |
| 2 | UNSUPPORTED_SUITE | 套件不支持 | suite 字节≠1,或 record 版本≠0x01 |
| 3 | BAD_SIGNATURE | 签名 / FINISHED 失败 | 握手被篡改 / 证书与私钥不配对 |
| 4 | FRAGMENT | 分片非法 | 分片越界、重组超 620B、DONE 带 body 等 |
| 5 | TIMEOUT | 超时 | 1000ms 无进展重发 4 次仍失败 |
| 6 | UNEXPECTED | 意外记录 | 记录顺序错、长度不符、状态失步 |
收到对端 ALERT 后,本地进入 FAILED 并不回声告警;告警单发一次、不重传。
9. 排查流程图
把上面的点串成一条"症状 → 定位 → 处理"的路径:
flowchart TD
S{"出什么问题了?"}
S -->|"改动没效果"| A1["检查编译宏是否定义<br/>MAVLINK_USE_AES_ENCRYPTION / _X509_HANDSHAKE"]
S -->|"编译/链接失败"| A2["gcc 路径 / set CC= 覆盖<br/>MAVLINK_MAX_PACKET_LEN=308"]
S -->|"握手 FAILED"| B1["读 mavlink_handshake_alert()"]
B1 --> B2{"错误码?"}
B2 -->|"BAD_CERT"| C1["比对两端 ca_pubkey / 证书是否同 CA / 是否过期"]
B2 -->|"BAD_SIGNATURE"| C2["证书与私钥是否配对 / 握手是否被篡改"]
B2 -->|"TIMEOUT"| C3["是否周期 tick() / 对端是否运行 / 丢包是否过重"]
B2 -->|"FRAGMENT/UNEXPECTED"| C4["检查链路乱序 / 记录长度 / 分片上限"]
S -->|"ESTABLISHED 但业务乱"| D1["是否 ESTABLISHED 后才发业务帧<br/>是否接了硬件 TRNG"]
S -->|"嵌入式栈溢出"| D2["握手放独立线程,分配 4~6 KB 栈"]10. 延伸阅读
主线文章见系列总目录。本文用于排查问题,不替代第 7 篇的威胁 模型与安全边界分析,也不构成安全审计结论。
本系列来自 nextpilot-secure-mavlink 项目,代码、测试、示例与设计文档都在仓库里,欢迎对照源码深挖与共建。
