设备身份链:有缓存 DongleId 就跳过注册

作者:

在

结论:设备身份链把「本地密钥 → 注册 → 令牌签发 → 各消费方」串成一条线。关键判定很简单:有公钥且 Params 已有 DongleId 时直接写回、不联系注册 API;没有公钥时把 ID 设为 UnregisteredDevice,即使已有缓存 ID 也是如此。而 is_registered_device() 只检查 Params 中 DongleId 不为 None 或哨兵值,不重新证明密钥、后端账户状态或设备真实性。设备注册、账户配对和车辆控制安全是三个不同问题。本文为对源码快照 c8fb906 的静态分析与隔离探针,未访问注册后端、未读取真实设备密钥、未验证实板网络。

启动与注册链

manager_init() 先写入 HardwareSerial,再调用 register(show_spinner=True),把非空结果设为 DONGLE_ID 环境变量并绑定日志上下文。因为 UnregisteredDevice 也是非空字符串,所以它不会触发「注册失败」异常;随后 manager_thread() 依据 DongleId 值排除 manage_athenad 和 uploader。可见未注册并不等于整个系统停止运行。

阶段 关键函数/组件 实际行为
密钥定位 get_key_pair() / Paths.persist_root() 依次查找 /persist/comma/ 下 RSA、ECDSA 私钥和公钥,返回首个完整文件对及 RS256/ES256;两对都无则返回三个 None,只读不生成
本地标识 register() 优先读 Params 的 DongleId,仅当其为 None 时才尝试持久分区的 comma/dongle_id;有公钥且已有 ID 时直接写回,不联系注册 API
无密钥 register() 没有公钥就把 ID 设为 UnregisteredDevice,即使已有缓存 ID 也是如此,并切换 Offroad_UnregisteredHardware 告警(PC 除外)
在线注册 register() / api_get() 取序列号与 IMEI,用私钥签发含 register: true、一小时到期的 JWT,向 v2/pilotauth/ POST;这些值经 params=... 作 URL 查询参数传入,而非 JSON 请求体
注册结果 register() HTTP 402/403 视为未注册;其他状态解析 JSON 中 dongle_id;异常线性退避,最长睡眠 15 秒后无限重试,60 秒后仅更新 spinner 文案
注册判定 is_registered_device() 只检查 Params 中 DongleId 不为 None 或哨兵值,不重新证明密钥、后端账户或设备真实性

这里还有两处细节:HARDWARE.get_serial() 在 comma 平台读取启动命令行序列号,get_imei() 从 modem 状态取值(基类默认返回空字符串);注册循环只以 None 判断 IMEI 是否就绪,所以空字符串并不会被这一循环拒绝。私钥的出厂写入/轮换机制不在这些函数中,不能据此推断供应链安全性。

manager_init() 把 register() 的返回值直接作为 DONGLE_ID 环境变量使用,因此注册结果既决定日志上下文,也决定哪些进程被排除;但「未注册」不是异常路径,设备仍可进入离线运行,只是对应的云端相关进程被略过。

由此,启动链的第一判据并不是「能否联网」,而是「本机是否存在完整密钥对」:密钥决定注册走向,网络只在无缓存 ID 时被使用。

令牌与消费者

Api.get_token() 以本地私钥生成含 identity、nbf、iat、exp 的 JWT,默认一小时有效;缺私钥时抛 RuntimeError。api_get() 只有显式传入 access_token 才加入 Authorization: JWT ...,并始终加入版本 User-Agent;Api.get()/post() 本身不会自动附带认证令牌。

缺私钥时 Api.get_token() 直接拒绝签发,因此依赖令牌的消费方在无密钥设备上根本拿不到授权;这也解释了为何缺密钥会走 UnregisteredDevice 分支而非带着旧 ID 继续联网。

消费方 关键入口 使用方式
Athena 主连接 athenad.main() 连接 WebSocket 时将新令牌放入 jwt cookie;连接与重连另由 Athena 管理
本地代理 startLocalProxy() 另签令牌连接远端 WebSocket;本地转发还有限定端口白名单,令牌不是本地端口授权的全部条件
上传 Uploader.do_upload() 为获取上传 URL 显式传 access_token=self.api.get_token();实际上传仍走返回的 URL 与 headers
配对 PairingDialog._get_pairing_url() 生成附加 pair: true 的 JWT 并嵌入二维码 URL;二维码应视为敏感凭据
状态查询 PrimeState._fetch_prime_status() 未注册时不请求;UI 辅助函数要求系统时间有效,签发两小时令牌,并以单调时间约一小时为桶缓存一个令牌
GNSS 辅助 pigeond 向设备辅助定位 API 显式传身份令牌;不参与车辆控制许可

注册 JWT 与常规身份 JWT 的载荷、用途不同,pair 令牌又增加配对声明。源码显示的是客户端如何签发和传送,并不显示服务端如何验证签名、声明、撤销、过期或配对授权;因此不能仅凭本地 DongleId、Prime 缓存或可生成 JWT 就推断设备已被云端接受。

消费者侧的差别同样明显:Athena 主连接与本地代理用不同令牌连接远端 WebSocket,上传是显式传令牌换取 URL,配对令牌带 pair 声明,状态查询则缓存约一小时令牌。令牌用途不同,彼此不能替代,这也是把身份能力按用途收窄的现实依据。

自制硬件与插件化落点

  1. 硬件身份适配器:让平台提供序列号、网络模组标识和持久密钥能力;不要把 comma 的 /persist/comma 文件布局当成所有新硬件的永久 ABI。是否接入原服务端需由服务提供方确认。
  2. 受信任身份服务:把私钥读取与签名封装在固定核心或硬件安全模块;插件只获得按用途、受限时效的令牌请求能力,不获得私钥文件路径或任意 payload_extra 权限。
  3. 云端插件隔离:Athena、上传、配对与 GNSS 分别声明所需身份能力;令牌用途/受众分离、最小权限、脱敏日志、网络超时与离线降级由核心契约统一约束,不得把云端订阅/配对状态当作执行器安全门的替代品。
  4. 启动策略显式化:把「无密钥」「有密钥但无缓存」「后端拒绝」「网络长期不可用」建成不同设备状态;当前 register() 的无限重试可能阻塞 manager 初始化,须先确定离线启动与降级策略。

验证与缺口

仓库已有 test_registration.py,覆盖缓存命中、缺密钥、首次注册成功及 402 拒绝;但没有覆盖 ECDSA 路径、403、IMEI 空值、长时间网络失败、损坏 JSON、密钥轮换或服务端令牌授权。隔离探针确认:缺公钥会把已有缓存 ID 覆盖为 UnregisteredDevice;空字符串 IMEI 可进入注册请求;一次后端异常使重试睡眠 1 秒后继续,NotImplementedError 不重试;Api.get_token() 对额外载荷使用 payload.update(),调用方可覆盖默认声明,缺私钥则拒绝签发。

需要强调边界:覆盖默认声明只是「客户端可构造能力」,不代表服务端接受任意声明;探针不执行真实密钥读取、JWT 编码、HTTP 或设备串号查询,上游测试也未在本环境运行。

常见问题

有了 DongleId 就说明设备已在云端注册了吗?

不能这样推断。is_registered_device() 只检查 Params 中 DongleId 是否为 None 或哨兵值,不重新证明密钥、后端账户状态或设备真实性;有缓存 ID 时 register() 甚至直接写回、不联系注册 API。

没有公钥但有缓存的 DongleId 会怎样?

会被覆盖为 UnregisteredDevice。只要没有公钥,register() 就把 ID 设为该哨兵值,即使已有缓存 ID 也是如此,并切换 Offroad_UnregisteredHardware 告警(PC 除外)。

哪些 API 请求会自动带上令牌?

不会自动。api_get() 只有显式传入 access_token 才加入 Authorization: JWT ...;Api.get()/post() 本身不会自动获取身份令牌,Uploader.do_upload() 则是显式传入。

注册失败的设备会不会直接停车?

不会。UnregisteredDevice 是非空字符串,不会触发 manager_init() 的注册失败异常;manager_thread() 只是依据 DongleId 排除 manage_athenad 和 uploader。不过无限重试可能阻塞 manager 初始化。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/DEVICE_IDENTITY_REGISTRATION_DEEP_DIVE.md(启动注册链、令牌签发与各消费方边界)
  • 项目 02 —— openpilot-research/probes/test_device_registration_contract.py(注册与 is_registered_device() 的假依赖隔离探针)
  • 项目 02 —— openpilot-research/probes/test_device_api_key_token_contract.py(get_key_pair() 与令牌签发契约探针)

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注