Files

337 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Matrix 设备认证(SAS)— 知识沉淀
> 本文档是 `ha-matrix-e2e``custom_components/matrix_e2ee`,基于 matrix-nio 0.26.0 + vodozemac)在
> Matrix 设备验证方面**全部已知知识的汇总**,范围:官方规范、官方文档/实现偏差、认证流程、代码定位、
> 注意事项。目的:避免重复排查、重复实现、重复踩坑。
>
> 配套文档:[`docs/DEVICE_VERIFICATION.md`](DEVICE_VERIFICATION.md)(人话操作指南 + 源码级流程)、
> [`SECURITY.md`](../SECURITY.md)(信任模型与威胁边界)、[`docs/DEVELOPMENT.md`](DEVELOPMENT.md)(测试纪律)。
>
> 维护约定:完成/发现新的验证相关 issue 后,把结论同步到本文档对应章节和「Issue 索引表」,不要只留在 Linear。
---
## 1. 官方规范(Matrix Spec
规范来源:matrix-spec `content/client-server-api/modules/end_to_end_encryption.md`(注意:**不是**
`sas_verification.md`,后者 404)。涉及两个机制:**验证框架**(`m.key.verification.request/ready/start/.../done/cancel`
**SAS 方法**`m.sas.v1`)。
### 1.1 验证框架
- **消息通道**:同一账号内验证用 **to-device** 消息;不同用户之间建议用 **in-room** 消息。本项目(bot 验证自己的
另一台设备)走 to-device。
- **Session ID**to-device 用 `transaction_id`in-room 用 event ID。
- **流程**`request`(发起方声明支持的 methods)→ 对方提示接受 → `ready`(接受方回 methods 交集)→ 用户选方法 →
`start` → 方法内交换 → `done`。**任意时点**任一方都可发 `cancel`(带 code)。
- **多设备广播**to-device 的 `request` 广播到对方所有设备(同一 txn)。其中一台接受后,对其他设备发 `cancel`
code `m.accepted`);用户在另一台拒绝则 code `m.user`。in-room 只发一次 request,无 cancel-to-others。
- **提示自动消失**to-device 按 `timestamp`in-room 按 `origin_server_ts`)起 **10 分钟**,或收到后 **2 分钟**
先到为准。拒绝请求**必须**发 `cancel`code=`m.user`
### 1.2 SAS 方法 `m.sas.v1` — 安全原理
灵感来自 **ZRTP hash commitment**responder 在 `accept` 里先发**自己公钥的 hashcommitment**initiator 收到
commitment 后才发自己的公钥。攻击者只有一次猜的机会:验证 n bit 则成功率 `1/2^n`SAS 全 40+ bit → ~1/10^12)。
分两个阶段:
1. **密钥协商**:双方各生成临时 Curve25519 密钥对,交换公钥(带 commitment 保护),ECDH 得共享秘密。
2. **密钥验证**:用共享秘密派生 HMAC,互相认证各自的设备 ed25519 key。
### 1.3 SAS 全流程(18 步,initiator = Alice / responder = Bob
| # | 动作 | 消息 | 内容要点 |
|---|---|---|---|
| 1 | 线下安全会面 | — | 双方对照设备显示内容 |
| 2 | 开始验证 | — | 任一方发起 |
| 3 | Alice 发 start | `m.key.verification.start` | **必须先拿到 Bob 设备 key**;含 key_agreement/hash/mac_method/short_authentication_string |
| 4 | Bob 选算法 | — | 从 Alice 支持列表里挑 key agreement/hash/MAC/SAS 方法 |
| 5 | Bob 需已持 Alice 设备 key | — | 否则先 key query |
| 6 | Bob 生成临时 Curve25519 对 | — | 对公钥做 SHA-256 |
| 7 | Bob 回 accept | `m.key.verification.accept` | 含 `commitment`(自己公钥的 hash |
| 8 | Alice 存 commitment | — | 后续校验 |
| 9 | Alice 生成临时对、发 key | `m.key.verification.key` | **只发公钥** |
| 10 | Bob 发自己 key | `m.key.verification.key` | 已无 commitment 保护风险 |
| 11 | Alice 校验 commitment | — | `commitment == hash(Bob 公钥 + Alice 的 start content)` |
| 12 | 双方 ECDH | — | 临时密钥 → 共享秘密 |
| 13 | 双方显示 SAS | — | emoji/decimal(多方法时用户选) |
| 14 | 用户比对 | — | 两边一致才继续 |
| 15 | 算 MAC | — | 对每个要验证的 key + key ID 列表 |
| 16 | 双方发 mac | `m.key.verification.mac` | |
| 17 | 校验对方 MAC | — | 每个 key 的 MAC + key 列表 MAC 全对 → 设备已验证 |
| 18 | 双方发 done | `m.key.verification.done` | |
**要验证哪些 key**:本设备的 ed25519 key + master signing key(跨用户时**应**含 MSK;「验证他人单设备」的做法已废弃)。
### 1.4 错误处理与 cancel code
- 随时可 cancel**10 分钟超时**txn 闲置 10min 也过期)。
- 同一设备多次发起 → recipient 全部 cancel。
- **未知 txn → cancel**(入站 `start`/`cancel` 除外)。
- 无共同方法 → cancelSAS 不符 → cancel;乱序 → cancel。
- SAS 专属 cancel code`m.unknown_method``m.mismatched_commitment``m.mismatched_sas`
- 框架层 code`m.user`(用户拒绝)、`m.accepted``m.timeout``m.unexpected_message``m.key_mismatch`
### 1.5 MAC 计算(wire 格式,容易错)
- **HKDF 参数**HKDF-SHA256IKM = 共享秘密,**无 salt**。
- **MAC info 串**(逐字节拼接,无分隔符):
`MATRIX_KEY_VERIFICATION_MAC` + 发 MAC 方 `user_id` + `device_id` + 对方 `user_id` + `device_id` + `transaction_id`
+ `key_id`(单个 key);对 key 列表用字符串 `KEY_IDS`
- **HMAC 对象**
- 单个 key → **unpadded base64** 编码的公钥;
- key 列表 → 字典序排序、**逗号连接、无空格**的 `alg:id` 列表,例如
`ed25519:Cross+Signing+Key,ed25519:DEVICEID`
- MAC 值 base64 编码后放进 `mac` 消息的 `mac``keys` 字段。
- **版本(关键)**:规范要求「所有当前实现都应用 `hkdf-hmac-sha256.v2`」。legacy `hkdf-hmac-sha256`v1)因 libolm
原始实现 bug 使用了**错误 base64 编码**,v2 修正;**v1 已废弃,双方都支持 v2 时 MUST NOT 用 v1**。
### 1.6 SAS 派生
- **HKDF info`curve25519-hkdf-sha256`**
`MATRIX_KEY_VERIFICATION_SAS|` + start 发起方 `user_id|` + `device_id|` + start 方公钥(unpadded base64`|`
+ accept 方 `user_id|` + `device_id|` + accept 方公钥(unpadded base64`|` + `transaction_id`
废弃的 `curve25519` 方法:无 `|` 分隔、不含公钥。
- **decimal**:取 5 字节 → 3 个 13-bit 数(08191)各 +1000 → 三个 10009191 的数。
位运算:`(B0<<5|B1>>3)+1000``((B1&0x7)<<10|B2<<2|B3>>6)+1000``((B3&0x3F)<<7|B4>>1)+1000`
- **emoji**:取 6 字节 → 前 42 bit → 7 组 6-bit → 7 个 063 索引 → 查 64 格 emoji 表
JSON 在 `matrix-org/matrix-spec` 仓库 `data-definitions/sas-emoji.json`)。
### 1.7 Cross-signing(本项目的「不做」依据)
三把 ed25519 对:**MSK**master signing key,签 USK/SSK,代表用户身份)、**USK**user-signing key,只自己可见,
签他人 MSK)、**SSK**self-signing key,签自己的设备 key)。作用:只需验证一次 MSK 即可信任该用户所有设备。
本集成因 **nio 0.26 不支持自签 cross-signing key**,且自签会把 cross-signing 权威放到 HA 主机上(违背信任边界),
故**不做** SSSS / Key Backup 导入(W1N-147 结论)。可对他人设备验证其 MSK,但 bot 自身无法 bootstrap 新设备。
---
## 2. 官方规范 vs nio 0.26 的偏差(文档/实现漂移 — 最容易重复踩)
| # | 规范/预期 | nio 0.26.0 实际 | 修复 | 出处 |
|---|---|---|---|---|
| 1 | `request → ready` 由框架处理 | **没有实现**request 被解析为 `UnknownToDeviceEvent` 丢弃 → Element 端显示取消 | 集成自建 `_handle_verification_request` + `_send_verification_ready` | W1N-173, PR #25 |
| 2 | 会话有 10 分钟活动窗口 | `Sas._last_event_time` 只在 `__init__` 赋值、**从不刷新** → `timed_out` 60s 后必真;`clear_verifications()` 每 sync 取消 | monkeypatch `Sas.timed_out`:只留 5min `_max_age`;集成超时 240s 先行触发 | W1N-172, PR #23 |
| 3 | commitment 为 **unpadded base64** | 迁移 vodozemac 后发 **hexdigest**Element 拒收(`m.key_mismatch` | `_apply_sas_commitment_patch``from_key_verification_start` + `_check_commitment` 都改回 unpadded base64 | PR #28, v0.2.8 |
| 4 | emoji 索引由底层算出 | vodozemac 已返回 7 个最终索引,nio 仍按 libolm 时代 bit-slicing **二次转换** → 两端 emoji 不一致、`m.mismatched_sas` | monkeypatch `_generate_emoji` 直接用 indices | W1N-175, PR #29 |
| 5 | 协商 `hkdf-hmac-sha256`(v1) 时 wire 用 libolm **invalid base64** | nio 用标准 `calculate_mac` → MAC 阶段失败 | monkeypatch `get_mac` + `receive_mac_event`,按 `chosen_mac_method``calculate_mac_invalid_base64`libolm-compat feature | W1N-177, PR #30 |
| 6 | 应优先/只用 `.v2` MAC | nio 0.26 **只协商 v1,不提供 `.v2`** | 保持 v1 + invalid_base64 与 rust-sdk/Element 互操作;**不要**自己加 `.v2`(会扩大协商面但 nio 没实现) | W1N-177 |
| 7 | `keys_query` 应覆盖会话各方 | 只取「共享加密房间用户」→ bot 与自身设备不共享房间 → **永不查询自己** → 入站 SAS 建不起来 | 登录/恢复后把自身 `user_id` 加进 `users_for_key_query` 并预热 | W1N-166, PR #18 |
| 8 | 收到 start 前应有设备 key | 新设备首次 start 命中 device_store KeyError → 丢弃 + 不建 SAS | `_repair_dropped_start`:查 key 后把同一 start 重喂给 `nio.olm.handle_key_verification` | W1N-170, PR #23 |
| 9 | 校验失败应发 `m.key_mismatch` cancel | `receive_mac_event`「无已验证设备」分支置 canceled 后**缺 return**,下一行覆盖为 `mac_received` | 上游 bug**Backlog**W1N-179 | W1N-179 |
另:nio 的 `Sas.verified = (state == mac_received) ∧ sas_accepted``get_mac()``sas_accepted=False` 时抛
`LocalProtocolError`——集成代码曾因裸调 get_mac 被 `except Exception` 吞掉而卡死(W1N-142)。
---
## 3. 认证流程(本项目实际路径)
### 3.1 对外接口(已定型)
**服务(admin-only 用 `async_register_admin_service` 强制,W1N-150**
| 服务 | 权限 | 说明 |
|---|---|---|
| `start_verification` | **admin** | bot 发起 SAS(入站由 Element 发起时无需调用) |
| `confirm_verification` | **admin** | 人工比对 emoji 后确认;内部 = `accept_sas` + `get_mac`verified 时再 `verify_device` |
| `cancel_verification` | **admin** | 取消 |
| `verify_device_by_fingerprint` | **admin** | 单向信任;`user_id` + `device_id` + `ed25519` **精确匹配**,否则 `fingerprint_mismatch` |
| `reauthenticate` | **admin** | Config Flow reauth |
| `get_fingerprint` | 普通注册 | 只读;返回 bot 的 `ed25519`/`curve25519` |
| `send_message` | 普通注册 | 发消息 |
**事件**
- `matrix_e2ee_verification` — 各 `stage``started` / `sas`(含 `emojis`/ `done` / `canceled`,带 `expires_at`
W1N-145 `VerificationPrompt``transaction_id/user_id/device_id/emojis/expires_at`)。
- `matrix_e2ee_fingerprint` — 启动后 emit bot 指纹。
- `matrix_e2ee_command` / `matrix_e2ee_error` — 命令事件(仅 `verified=True` 且 allowlist 命中才触发)。
**错误码**`const.py`):`verification_peer_denied`(发起者非 bot 自身账号或 `allowed_users`)、`verification_timeout`
(集成超时 4min)、`fingerprint_mismatch``invalid_transaction``device_missing`
### 3.2 双向流程(wire 时序)
**方向 AElement 发起(入站)** — 由 `handle_to_device_event` + `_handle_verification_request` 驱动:
```
Element HA bot (matrix_e2ee)
│ m.key.verification.request {txn, methods:[m.sas.v1], timestamp}
│ ──────────────────────────────────────────────► _handle_verification_request
│ 校验: sender 允许 / from_device、txn 非空 /
│ methods 含 m.sas.v1 / timestamp 界内
│ ◄────────────────────────────────────────────── m.key.verification.ready {methods:[m.sas.v1]}
│ m.key.verification.start {txn, ...}
│ ──────────────────────────────────────────────► handle_to_device_event (start 分支)
│ _get_sas==None → _repair_dropped_start(查 key 重喂)
│ accept_key_verification(txn) → emit started
│ ◄────────────────────────────────────────────── m.key.verification.acceptnio 内部,含 commitment
│ m.key.verification.key ... key 交换 → 双方显示 emoji
│ ──────────────────────────────────────────────► (key 分支) emit sas {emojis, expires_at}
│ HA 侧人工比对 emoji → confirm_verification(txn)
│ confirm_short_auth_string(txn)
│ = accept_sas + get_mac + (verified→verify_device)
│ ◄────────────────────────────────────────────── m.key.verification.mac(只发一次, W1N-169
│ m.key.verification.mac → (mac 分支) sas.verified → emit done
```
**方向 Bbot 发起(出站)**`start_verification(user_id, device_id)``nio.start_key_verification(device)` 发出
start;后续 accept/key/emoji 同方向 A;比对后同样 `confirm_verification` 完成。key 的发送**完全由 nio 内部负责**
`handle_key_verification``if not sas.we_started_it: share_key()`),集成不再手动 `share_key()`W1N-169)。
**状态机要点**
- 集成只在 start 分支记录 monotonic 时间(`_mark_sas_started`);超时判断 = `sas.timed_out` **或**
monotonic 差 ≥ 240s`_sas_is_timed_out`),超时 → `_timeout_verification`cancel + emit `verification_timeout`
- `confirm_verification` 是**唯一**完成验证的路径:先查超时,再 `nio.confirm_short_auth_string(txn)``sas.verified`
→ emit `done`,否则 emit `sas`(等用户再次 confirm)。
- 入站门控:所有分支(start/key/mac/cancel)统一 `_bootstrap_allowed(sender)`——`sender == session.user_id`
**或** `sender ∈ allowed_users`,否则 emit `verification_peer_denied`W1N-143)。
### 3.3 路径二:单向指纹验证(降级/备选)
`get_fingerprint` 取 bot 指纹 → Element「Manually Verify by Text」;信任对端用 `verify_device_by_fingerprint`
(精确匹配后 `nio.olm.verify_device`,**本地单向信任,不是 SAS,不产生 SAS 事件**)。
---
## 4. 代码地图(`custom_components/matrix_e2ee/`
### client.py1525 行;无 models.py,模型在此文件)
**nio 补丁区(160398**
| 函数 | 行 | 作用 |
|---|---|---|
| `_apply_sas_timeout_patch` | 164 | monkeypatch `Sas.timed_out`verified/canceled→False`now-creation ≥ _max_age(5min)`→canceled+`_timeout_error`+True。**忽略 nio 60s 事件超时 bug** |
| `_sas_commitment(pubkey, canonical)` | 197 | SHA-256(pubkey+canonical) 的 unpadded base64 |
| `_apply_sas_commitment_patch` | 208 | patch `from_key_verification_start`accept commitment+ `_check_commitment`initiator 校验)→ unpadded base64 |
| `_apply_sas_emoji_patch` | 256 | `_generate_emoji` 直接用 `established_sas.bytes(info).emoji_indices` 映射,不做 bit-slicing |
| `_apply_sas_mac_patch` | 286 | 见下 |
| `_select_mac_func` | 304 | `chosen_mac_method=="hkdf-hmac-sha256"``calculate_mac_invalid_base64`,否则 `calculate_mac` |
| `get_mac`patch | 309 | `sas_accepted=False`/canceled 抛 `LocalProtocolError`;按规范拼 info、`ed25519:{own_device}` + `KEY_IDS` |
| `receive_mac_event`patch | 339 | verified→return`state!=key_received`→canceledKEY_IDS 校验→`_key_mismatch_error`;逐 key device_id 匹配 + MAC 校验 → verified_devices;空→canceled**缺 return,复刻 W1N-179 bug** |
| `_patch_nio_sas_timeout/_commitment/_emoji/_mac` | 188/246/277/390 | `try: from nio … except Exception: return`(测试无 nio 时 no-op |
**验证服务(9361525**
| 函数 | 行 | 作用 |
|---|---|---|
| `enable_verification_callbacks` | 936 | 注册 `handle_to_device_event`KeyVerificationEvent+ `_handle_verification_request`ToDeviceEvent |
| `_emit_verification(stage, **extra)` | 947 | fire `matrix_e2ee_verification` + warning log |
| `_verification_expires_at` | 953 | now UTC + `_verification_timeout` ISO 串 |
| `_lookup_device` / `_get_sas` | 959 / 972 | device_store 查设备 / `nio.key_verifications.get(txn)` |
| `_sas_party` / `_sas_emojis` | 978 / 990 | 提取对端 (user,device) / `sas.get_emoji()`(异常吞掉→None`list[list[str]]` |
| `_mark_sas_started` / `_sas_is_timed_out` | 1006 / 1009 | monotonic 记录 / 超时判断 |
| `async_start_verification` | 1018 | 拒软登出;查设备→`nio.start_key_verification`emit `started` |
| `async_verify_device_by_fingerprint` | 1060 | `actual.strip()!=ed25519.strip()` 精确等值(W1N-159)→`fingerprint_mismatch``nio.olm.verify_device` |
| `async_confirm_verification` | 1090 | 超时检查→`nio.confirm_short_auth_string(txn)`verified→emit `done`,否则 emit `sas`。**唯一验设备路径** |
| `async_cancel_verification` | 1139 | `nio.cancel_key_verification(txn, reject=False)` → emit `canceled` |
| `_timeout_verification` | 1169 | cancel + emit `verification_timeout` + `timeout` |
| `_bootstrap_allowed(sender)` | 1187 | `sender==session.user_id``sender in allowed_users`W1N-143 门控) |
| `_repair_dropped_start` | 1196 | `_query_device_keys(sender)` 后重喂 `nio.olm.handle_key_verification(event)`W1N-170 |
| `_handle_verification_request` | 1222 | 解析 request;校验 sender/txn/methods/timestamp → `_send_verification_ready`**不建 SAS 状态**W1N-173 |
| `_send_verification_ready` | 1283 | 回 `ready` {from_device: own, methods:[m.sas.v1], transaction_id} |
| `handle_to_device_event` | 1314 | 主分发:门控→cancel→timeout→startemoji 检查/repair/accept/emit)→keyemit sas)→macverified→emit done |
| `_verification_kind` | 1458 | 类名/type 串 → start/key/mac/cancel |
| `_request_timestamp_valid` | 1447 | 未来≤5min 且 age≤10min |
| `_transaction_id_from_verifications` | 1480 | 匹配 other user+device;唯一则兜底 |
| `_verification_error_code` | 1496 | timeout→`verification_timeout`LocalProtocolError/does not exist→`invalid_transaction`unverified→`unverified_device`;否则 `send_failed` |
### const.py79 行)
`SERVICE_START/CONFIRM/CANCEL_VERIFICATION``SERVICE_VERIFY_DEVICE_BY_FINGERPRINT``ATTR_ED25519/TRANSACTION_ID/
USER_ID/DEVICE_ID``EVENT_VERIFICATION`/`EVENT_FINGERPRINT`;错误码见 §3.1`VERIFICATION_TIMEOUT_SECONDS=240`
`VERIFICATION_REQUEST_MAX_FUTURE_MS`/`MAX_AGE_MS``SAS_METHOD_V1="m.sas.v1"``VERIFICATION_REQUEST/READY/START/
ACCEPT/KEY/MAC/DONE/CANCEL` 类型串。
### __init__.py
`_register_services`(136) 服务→client 方法映射;`_fire_event`(123)`_options`(126) 读 `allowed_users/allowed_rooms`
`async_setup_entry`(268)/`async_unload_entry`(321)。
---
## 5. 注意的问题(Checklist / 教训)
**协议 / wire 层**
1. **MAC 生成与校验必须一起改**`get_mac` + `receive_mac_event` 同步 monkeypatch),只改一边 = 互不兼容。
2. **`.v2` 不要自己加**nio 不提供 `_mac_v2`,不要扩大协商面。
3. **accept/key/mac 的发送权要分清**key 归 nio 内部(`we_started_it` 判断),mac 只由 `confirm_verification` 发;
集成只做事件映射与人工 confirm 触发(W1N-169 双发教训)。
4. commitment 必须 unpadded base64(不是 hexdigest);emoji 索引不得二次转换;legacy MAC 用 invalid base64。
5. SAS HKDF info / MAC info 都是**无分隔符逐字节拼接**,字段顺序(发起方在前)与方向不能错。
**nio 状态机坑**
6. `Sas.timed_out` 被 nio 60s bug 污染 → 必须 monkeypatch,用 240s 集成超时先行。
7. `get_mac()``sas_accepted=False` 抛异常 → 必须走 `confirm_short_auth_string`,别裸调。
8. 入站 start 需设备 key`_get_sas==None` 时先 `_repair_dropped_start`(查 key 重喂),不要直接放弃。
9. `keys_query` 默认不含 bot 自己 → 登录/恢复后主动预热自身 keys。
**信任 / 安全**
10. **无自动信任**:同账号 ≠ 可信,包括 `@bot` 自身设备(W1N-153)。
11. 指纹比较**必须精确等值**,禁 `casefold()`unpadded base64 大小写敏感,W1N-159)。
12. 验证/指纹/reauth 服务必须 admin-onlyW1N-150);入站所有分支统一门控 `_bootstrap_allowed`W1N-143)。
13. request 的 timestamp 校验:未来≤5min、age≤10minW1N-173)。
14. fail-closed:未验证设备不触发命令事件、不回退明文、`ignore_unverified_devices` 永不开启、日志不落 secretW1N-136)。
**运维 / 测试**
15. 设备 ID 变了 = store 丢失 → 按 runbook 重新验证,不静默新建设备(W1N-134/138)。
16. 测试只用 FakeNio/FakeSas**协议级问题单测不可见** → 缺真实 homeserver e2eW1N-171 Backlog)。
17. 上游 nio `receive_mac_event` 缺 return bug 已复刻到我们的 patch 里(W1N-179 Backlog),改上游时同步改。
---
## 6. Issue 索引表(Linear → 知识)
状态:✅ Done ⏳ Backlog
| Issue | 主题 | 知识章节 | 状态 |
|---|---|---|---|
| W1N-134 | M2 E2EE 登录/原子 session/恢复同一设备 | §5-15 | ✅ |
| W1N-135 | M2 加密收发 + sync tokens(消费验证状态) | §3 | ✅ |
| W1N-136 | M2 fail-closed、allowlist、不记录 secret | §5-14 | ✅ |
| W1N-137 | M3 SAS 服务/事件 + verified-device 策略 | §3.1 | ✅ |
| W1N-138 | M4 软登出/store 丢失恢复/diagnostics | §5-15 | ✅ |
| W1N-141 | 集成设备安全验证指南(parent of AF | — | ✅ |
| W1N-142 | A SAS auto-completion(修 get_mac 卡死)→ 后被 W1N-153 撤销自动分支 | §2 / §5-7 | ✅ |
| W1N-143 | B 入站 SAS 发起者门控 | §3.2 / §5-12 | ✅ |
| W1N-144 | C 单向指纹验证(get_fingerprint / verify_device | §3.3 | ✅ |
| W1N-145 | D VerificationPrompt 模型 + expires_at | §3.1 | ✅ |
| W1N-147 | F SECURITY.md + SAS/指纹指南(cross-signing 不做 SSSS | §1.7 | ✅ |
| W1N-149 | 安全加固总纲(P0 自动确认 / P0 admin-only / P1 指纹门控) | §5 | ✅ |
| W1N-150 | B admin-only 强制 | §3.1 / §5-12 | ✅ |
| W1N-151 | C verify_device_by_fingerprinted25519 精确门控) | §3.3 | ✅ |
| W1N-153 | A 删除同账号 SAS 自动确认 | §5-10 | ✅ |
| W1N-154 | E 文档改为手动确认 + 新指纹服务名 | — | ✅ |
| W1N-156 | 加固跟进:allowlist 拆分 + P2 质量(**未做** | §8 | ⏳ |
| W1N-157 | F 人话版验证指南(docs/DEVICE_VERIFICATION.md | — | ✅ |
| W1N-158 | M5 Config Flowreauth/设备验证 UI 路径动机) | — | ✅ |
| W1N-159 | casefold 指纹比较 bug → 精确匹配 | §5-11 | ✅ |
| W1N-162 | Config Flow reconfigure + reauth | — | ✅ |
| W1N-165 | 文档更新 + release 0.2.0SAS 保持 service/event-based | §8 | ✅ |
| W1N-166 | keys_query 不查同账号 → 预热 own device keys | §2-7 / §5-9 | ✅ |
| W1N-169 | key/mac 双发(协议正确性) | §5-3 | ✅ |
| W1N-170 | 启动后新增设备首次 start 被丢 → 自动查 key 重喂 | §2-8 / §5-8 | ✅ |
| W1N-171 | 缺真实 homeserver 端到端 SAS 测试 | §5-16 | ⏳ |
| W1N-172 | nio 60s 必死超时(_last_event_time 不刷新) | §2-2 / §5-6 | ✅ |
| W1N-173 | request → ready 桥接(nio 缺框架) | §2-1 / §5-13 | ✅ |
| W1N-174/176 | 部署 matrix_e2ee(e) 到 hass.windy.lan | — | ✅ |
| W1N-175 | emoji 双转换(vodozemac indices | §2-4 | ✅ |
| W1N-177 | legacy MAC invalid-base64 | §2-5/6 / §5-1/2 | ✅ |
| W1N-178 | 部署 v0.2.10(含 legacy MAC 修复) | — | ✅ |
| W1N-179 | receive_mac_event 缺 return 覆盖 canceled**未做** | §2-9 / §5-17 | ⏳ |
---
## 7. 当前版本状态与待办
- 已发布至 **v0.2.10**wire 修复线:commitment v0.2.8 → emoji v0.2.9 → legacy MAC v0.2.10,均已部署
hass.windy.lan)。
- SAS 保持 **service/event-based**v0.2.x);HA 内 live SAS emoji UI 延后到 v0.3W1N-165 / SECURITY.md)。
**Backlog(避免重复工作,动手前先查这 3 条)**
1. **W1N-156** — 拆分 `allowed_users` 为「命令权限」与「SAS 验证权限」两组;SAS 不支持算法显式 cancel;
setup/stop/reauth async lock;事务绑定(存预期 user/device/创建时间);CI manifest 依赖校验;ruff。
2. **W1N-179** — nio `receive_mac_event` 缺 return(上游 + 我们的 monkeypatch 同步修,加单测)。
3. **W1N-171** — 真实 homeserver 端到端 SAS 测试(docker Synapse 或人工 runbook)。