Files
WebDrop/docs/e2ee-boundary-comparison.md
T
justme d946fc5e8a feat(api)!: fold devices into users and report member presence
Contract 7.0.0 (breaking). Devices become an attribute of the account;
members are managed per user, and only unauthenticated guests keep a
per-terminal registration.

- rename seats -> max_members, seats_used -> members_used,
  seats_full -> members_full, SeatJoined -> JoinResult, the `seats`
  stream control message -> `members`, and POST /channels/{id}/seats ->
  POST /channels/{id}/join (stored channel settings are migrated on start)
- drop per-terminal encrypted device labels and the seat list/release
  endpoints; add GET/DELETE /channels/{id}/guests for guest terminals
- authenticate account members by account session only: the terminal key
  is optional for logged-in accounts, no seat row is written for them,
  and gated-download grants are counted per user (per terminal for guests)
- GET /channels/{id}/members now returns online, devices and
  online_devices per account; presence is tracked in memory from active
  WebSocket/SSE subscriptions and is not pushed over the stream
- owner invitation list no longer returns accounts that already joined
2026-10-14 22:18:59 +08:00

117 lines
15 KiB
Markdown
Raw 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.
# E2EE 协作架构边界对照
对照来源:用户提供的 `D:/Users/JustMe/Downloads/E2EE_Collaboration_Architecture.md`。
核查对象:2026-10-13 当前工作区代码,包含尚未提交、尚未部署的后端业务模型改造。此次对照不修改密钥协议,不迁移旧数据。
## 判断
当前实现已推进为“后端管理业务结构和访问策略、浏览器持有频道数据域密钥并加解密资源”的模型。
参考文档是“后端协调存储、客户端验证授权”的设备身份、频道数据域和 epoch 模型。
两者的主要差距在身份可信来源、密钥分发粒度、授权验证和撤销后的密码学隔离,而非 CRUD 或界面布局。
## 对照
| 边界 | 参考文档 | 当前代码 | 判断 |
|---|---|---|---|
| 业务结构 | 后端提供 Identity、Space、Channel、Chat、File、Sync 模块 | 账户资料、空间成员、频道、分组、目录、文件归属已放到后端,前端接口类型从 DTO 生成 | 模块方向一致 |
| 资源加密 | 设备加密,服务端保存密文 | Worker + WebCrypto,AES-GCM 分帧,AAD/ID/FINAL/大小校验,持久化密文重试 | 可沿用基础能力 |
| 密码与数据密钥 | 账户认证不直接充当数据加密根 | 密码/可用的 Passkey PRF 解锁保险库;随机账号 ECDH 私钥解开账号 Key Envelope;频道数据域密钥独立随机生成 | 密码不直接生成资源密钥;账号恢复仍与 E2EE 身份私钥恢复耦合 |
| 用户身份 | Signing Identity 与 Device Keys | 账号保险库保存 P-256 签名根密钥;本机保存不可导出的设备签名私钥,根签名设备证书由后端校验并登记 | 身份密钥基础已落地;尚未用于业务事件 |
| 新设备 | 可信恢复或已有设备审批 | 用户已决定不要求旧设备审批;新设备以账号密码/支持的 Passkey PRF 解锁同一保险库私钥 | 与参考文档的设备审批模型不同;账号被攻破会暴露该账号全部空间密钥 |
| 设备分发 | 每个获授权设备各有 Key Envelope | 按 username 分发;设备只有 UUID 和 session(v7 起不再有按空间的席位或终端密钥,访客除外),没有设备解包公钥 | 分发对象不同 |
| Space | 主要是授权管理范围 | 成员、频道目录、日志与同步边界;不再作为频道内容根 | 业务边界已收束,仍由服务端定义 |
| Channel | Chat 与 FileGroup 有独立数据域和密钥 | 每频道独立 chat/files 随机 epoch key;对象另有随机 key,由域密钥封装 | 数据域已落地,密钥包仍按账号而非设备 |
| 授权链 | Owner 签名 Capability,客户端验证 | owner/member + bearer 能力令牌 + 服务端成员表,客户端没有验证签名授权链 | 服务端仍是授权判断的信任方 |
| 管理与读取 | Admin 可以只管理而没有解密权限 | 没有 Admin Capability;Owner 管理凭据与频道数据域密钥格式分开,但 Owner 同时能管理并读取其已授权频道 | 仍缺管理授权与数据读取权限分离 |
| 细分权限 | 按频道、聊天/文件域授予 read/write/upload 等权限 | 默认频道权限、历史策略、epoch 与收件人计划由后端计算;邀请提交时原子校验域密钥包集合,日志、对象下载和写入按权限检查 | 初始权限和邀请策略已落地;其他频道授权 UI 和成员撤销后的轮换待补 |
| Epoch | 成员或设备撤销触发新 epoch,排除被撤销者 | Owner 关闭新成员历史时,邀请流程按 chat/files 分别轮换 epoch;服务端拒绝旧 epoch 写入 | 新成员历史隔离已实现;成员撤销后轮换、并发与离线恢复未实现 |
| FileKey | 每文件随机 FileKey,由 FileGroupKey 封装 | 每条目(文件、消息、加密元数据)独立随机对象密钥,由对应频道域 epoch key 封装 | 已具备独立重封装基础,批量 rewrap/撤销流程未做 |
| 消息事件 | 已签名的 create/edit/delete/reaction,客户端 reduce | 完整对象的最终帧经账号设备签名并绑定密文 manifest;编辑是签名新对象后再删除旧对象,删除/频道结构命令尚无签名事件 | 创建/发布事件基础已接入,删除和结构授权仍缺 |
| 作者证明 | 消息签名绑定用户/设备 | 后端 AuthorAccount 与账号资料决定显示身份 | 能提供正常服务下的身份归属,不能抵抗恶意服务端改写 |
| 前端业务缓存 | 后端定义账户、空间、频道、权限及 API 契约 | DTO 从后端生成;IndexedDB `SavedResource` 仍缓存服务端设置/角色与本机凭据,在线发送不再根据缓存上传开关授权 | 服务端是在线权限来源;本地快照仍需与密钥/队列拆分 |
| 存储部署 | PostgreSQL、Redis、S3 | SQLite、本机 blob、进程内 ChannelHub;NativeAOT + WebSocket/SSE/HTTP 已有 | 部署规模不同,不是首要密码学差距 |
已收回后端的有:邀请/历史授权计划、默认访问权限、epoch 选择与域密钥包原子提交;上传权限不再读取本地 `membersCanUpload` 缓存;频道/分组创建直接返回服务端生成的实体 ID;`chat/files` 与 `open/invite` 已由后端枚举定义并生成前端联合类型。
## 关键代码依据
1. [accountCrypto.ts](../web/src/core-bridge/worker/accountCrypto.ts):密码派生登录 verifier 和保险库密钥;账号保险库另含 ECDSA P-256 根签名密钥。`generateInvitationKeys()` 仍专用于 ECDH 邀请。
2. [account.ts](../web/src/core-bridge/worker/account.ts)、[accountChannels.ts](../web/src/core-bridge/worker/accountChannels.ts):新设备登录后恢复同一账号私钥,按账号领取空间授权包。
3. [SqliteAccountStore.cs](../server/src/WebDrop.Storage/SqliteAccountStore.cs)、[SqliteAccountSigning.cs](../server/src/WebDrop.Storage/SqliteAccountSigning.cs):账号保存根签名公钥,设备目录保存根签名证书;尚无设备解包公钥/旧设备审批。
4. [domainCrypto.ts](../web/src/core-bridge/worker/domainCrypto.ts)、[channelDataKeys.ts](../web/src/core-bridge/worker/channelDataKeys.ts):频道 chat/files epoch key 与随机对象 key 在客户端生成,账号 ECDH envelope 与对象封装也在客户端执行。`packages/core/kdf.ts` 仍服务于访问凭据和独立 Share/旧资源路径,不是新 Workspace 的内容密钥路径。
5. [ChannelWorkspace.cs](../server/src/WebDrop.Core/ChannelWorkspace.cs):后端创建频道、分组、目录并校验资源归属;`WorkspaceChannel` 保存 chat/files epoch 与新成员历史策略。
6. [ChannelCrypto.cs](../server/src/WebDrop.Core/ChannelCrypto.cs):后端计算邀请 epoch/收件人计划,在单事务校验并提交域 envelopes、默认访问权和 epoch;按频道、域、epoch 过滤对象并校验历史起点、日志读取、下载与写入权限。客户端只生成密钥并封装到该计划指定的账号。
7. [channelSend.ts](../web/src/core-bridge/worker/channelSend.ts):文本编辑目前是新内容上传成功后删除旧条目。
8. [reduce.ts](../web/src/store/reduce.ts):作者和资源位置来自后端快照,没有作者签名或签名状态验证。
## 用参考文档的威胁模型审视当前实现
### 正常服务和被动泄露
加密模式下,数据库和 blob 不保存资源明文或未封装的空间 K。数据密钥随机生成,不能仅凭登录 token 或数据库中的 verifier 直接推出保险库密钥或内容密钥。
机密性仍依赖密码强度、端点安全和实际密钥分发过程;不能将此结论扩展为对主动恶意服务端的完整防护。
### 主动恶意服务端
- 邀请时,发送端从服务端目录读取收件账号公钥。当前没有可信签名身份、公钥固定或授权链验证;服务端替换公钥可使新邀请的密钥包被攻击者解开。
- 服务端可改写未签名的成员、权限、作者和结构数据。客户端即使正确校验资源 AEAD,也不能据此验证作者、授权链或当前业务归属。
- 已移除成员仍持有此前领取的频道域密钥。成员移除后的 epoch 轮换未实现;若恶意服务端继续向其提供后续密文,当前协议不能保证其无法解密。
- 客户端没有验证 Owner 签名授权链;恶意服务端可伪造访问快照、作者归属或返回过期签名状态。
因此,当前实现仍没有达到参考文档对“不可信服务器”的完整目标。现在的后端执行频道/域授权,并由客户端只解开获授权的密钥包;这能收束正常服务下的访问边界,但后端仍是身份目录、授权与事件归属的信任方。后端访问控制与客户端独立验证签名授权,是不同的安全保证。
## 正确的前后端责任划分
后端继续定义、存储和提供业务实体及接口,包括账户资料、设备目录、空间、频道、分组、文件夹、成员和同步游标。
客户端负责生成和保管私钥、建立设备信任、签署并验证授权、校验 Key Envelope/epoch/消息签名、加解密资源,然后把验证通过的数据交给渲染层。
这些客户端职责不应被当作“重复的业务实现”删除。CryptoProtocol 的结构可以统一定义并生成类型,但密码学验证必须在客户端执行,不能由不可信服务器替客户端宣布验证成功。
## 当前前端可继续收束的部分
已经由后端定义并生成前端 DTO 的有:账户资料、空间/频道/分组/文件夹、资源位置、频道设置与限制、成员和同步接口。`ChannelInfo`、`Item`、`PreviewKind` 等 camelCase 类型是 UI 或解密后的投影,不是第二份后端实体;AES 帧解析、账号私钥、Key Envelope 和对象 key 也必须留在客户端。
仍有几处边界值得收回或明确:
1. `SavedResource` 仍把 endpoint secret、链接等本机凭据与频道名称、成员人数上限等服务端展示快照放在同一结构;账号保险库也同步部分快照。建议拆为本机凭据、服务端快照和传输队列,离线快照只用于只读展示。
2. Worker 从本地 `maxDownloads` 提供上传表单初始值;服务端已校验每个 GATED 文件的下载上限,缓存仅影响表单显示,不决定授权。
3. `account.ts` 仍包含用户名输入规范化和账号本地恢复编排。服务端负责账户验证、设备 session、个人资料和保险库并发版本;浏览器只生成/持有私钥,并执行解锁加密保险库所需的本地 KDF。
文件名、MIME、大小等 E2EE 正文结构和 `file.published` 不能移到服务器定义,否则服务器需解析或生成加密正文。有一处重复是有意的:加密文件元数据保留上传时原始 thread/folder,供解密后完整性核对;后端 WorkspaceLocation 表示当前可移动的位置。两者应明确命名为“原始来源”和“当前目录位置”,不能混为一个字段。
参考文档把 Server 定义为不可信,同时要求客户端验证身份、签名授权和事件。后端权威管理账户与空间业务状态并不冲突,但这不代表客户端能防止恶意后端篡改状态。若保留“不信任 Server”的安全目标,后端可负责存储与 API 真值,签名身份、Capability 和 epoch 变更仍须客户端验证;在这些协议实现前,产品只能承诺正常服务下的后端权限执行与内容 E2EE,不能声称抵抗主动恶意服务器。
边界原则:除端到端密钥生成、封装、解密与签名验证外,所有可由请求参数决定的业务规则都必须由后端计算和校验。前端校验、禁用按钮和隐藏菜单只能改善交互,不能决定账户角色、频道成员范围、权限、默认值、资源归属或状态转换。
## 参考文档还需补足的协议细节
### 已确认的实施目标(尚未部署)
- 账号根签名密钥签发设备证书;设备私钥独立生成。新设备通过密码或 Passkey 解锁保险库恢复根密钥,不要求旧设备批准。
- 首次收到的账号根公钥采用 TOFU 并展示“未核验”;之后密钥变化必须阻止静默更新。可选使用独立渠道/二维码核对指纹。TOFU 不防服务端在首次接触时替换密钥。
- 第一阶段覆盖频道结构命令与聊天 create/edit/delete 事件。服务端拒绝无效签名并执行权限规则;浏览器同样验证签名、证书和授权链后才更新投影。
- 现有服务端仍保存/排序/推送密文与事件;明文加解密继续只在浏览器。服务端分配的 seq 不由客户端签名,事件需绑定客户端 event ID、作者设备、类型、频道/epoch 和密文摘要。
- **可信根**:首次确认用户/Owner 签名公钥的方式,避免服务端替换整个身份及授权链。
- **设备恢复**:项目当前选择不要求旧设备审批;需明确密码、支持 PRF 的 Passkey 和不支持 PRF 的 Passkey 各自能恢复哪些账号/频道密钥,以及账号失陷的影响范围。
- **签名格式**:确定签名覆盖的 subject/device/scope/permissions/version/epoch/密钥指纹等字段、规范编码和验证规则。
- **状态一致性**:签名不能单独防止服务端返回旧的合法授权。需要定义版本、前序状态、可信检查点,以及跨设备回滚/分叉的检测范围。
- **轮换过程**:原子提交新 epoch 与剩余设备的 envelopes,拒绝撤销后的旧 epoch 写入,处理离线设备、并发轮换和上传中的文件。
- **历史读取策略**:新成员能否读历史、移除成员后哪些旧 FileKey 需要重新封装。重新封装不能撤回已被保存的旧密钥或明文。
- **元数据可见性**:明确账号资料、频道/目录名称、资源归属和访问时间哪些允许服务端看到。文档中的“用户数据”不能替代字段级隐私范围。
Epoch 轮换可以隔离正确切换后新数据的读取权限,不自动提供完整前向保密。服务端拒绝服务及删除密文仍属于无法单靠加密消除的可用性风险。
## 按依赖推进的顺序
1. 确定签名身份、可信根、设备证书与设备解包密钥;定义新协议版本,不迁移旧数据。
2. 设计 Owner/Admin/Member 的签名 Capability,将管理能力和数据读取权拆开,并让后端给出可验证、原子提交的授权变更计划。
3. 将账号级 envelopes 升级为设备级 envelopes,明确项目选择无旧设备审批后,Passkey 恢复、设备失陷与私钥复制的边界。
4. 为成员/设备撤销实现每个 ChannelChat/ChannelFileGroup 的原子 epoch 轮换,覆盖离线成员、并发邀请和上传中的资源。
5. 引入签名 ChatEvent、作者验证、可信同步检查点,再接入客户端投影和界面。
6. 拆分 `SavedResource` 中的后端快照与本机密钥/队列,移除 Worker 对缓存权限字段的业务判断,在线操作以服务端授权为准。
7. 根据多实例和存储规模需求替换数据库、事件通知和对象存储适配器。
已完成的后端 CRUD、契约生成、Worker 分层、频道数据域、对象 key 封装、分帧 AEAD、完整性检查及持久化重试可以作为基础;下一阶段重点应转向可验证授权、设备级密钥分发、撤销轮换和前端缓存收束。