结论:设备身份链把「本地密钥 → 注册 → 令牌签发 → 各消费方」串成一条线。关键判定很简单:有公钥且 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 声明,状态查询则缓存约一小时令牌。令牌用途不同,彼此不能替代,这也是把身份能力按用途收窄的现实依据。
自制硬件与插件化落点
- 硬件身份适配器:让平台提供序列号、网络模组标识和持久密钥能力;不要把 comma 的
/persist/comma文件布局当成所有新硬件的永久 ABI。是否接入原服务端需由服务提供方确认。 - 受信任身份服务:把私钥读取与签名封装在固定核心或硬件安全模块;插件只获得按用途、受限时效的令牌请求能力,不获得私钥文件路径或任意
payload_extra权限。 - 云端插件隔离:Athena、上传、配对与 GNSS 分别声明所需身份能力;令牌用途/受众分离、最小权限、脱敏日志、网络超时与离线降级由核心契约统一约束,不得把云端订阅/配对状态当作执行器安全门的替代品。
- 启动策略显式化:把「无密钥」「有密钥但无缓存」「后端拒绝」「网络长期不可用」建成不同设备状态;当前
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 初始化。
延伸阅读
- 设备配对与数据 Firehose —— 配对声明与数据上传如何共用身份链
- 路段下载、缓存与工具登录链 —— 工具侧认证文件与车端设备身份的信任域分离
- Athena 上传队列的持久化与重试 —— 身份令牌在上传链路里的实际使用点
- 网络设置与 eSIM —— IMEI 与网络模组标识在设备身份中的位置
参考资料
- 项目 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