docs: describe workspace signing boundaries
ci/woodpecker/pr/web-build Pipeline failed
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
justme committed 2026-10-14 04:06:29 +08:00
1 parent b5cf3d5958
commit 58506f82de
7 files changed
+185 -43

No files matched your search

+20 -12
View File
@@ -4,36 +4,44 @@
## 领域边界
Account 使用不可修改的 username 作为当前版本稳定身份,displayName/avatar 为可修改的个人资料。AccountSettings 与资料在加密保险库中同步。AccountDevice 是账号下的一个浏览器安装身份,具有独立登录 session、名称与撤销状态;每个频道的设备 S 继续独立生成。ChannelMember 绑定账号与频道角色,不绑定某一次登录或某个设备。
### 已确认的下一版规则
Channel 是成员权限和内容加密的共同边界。Thread 属于 Channel,Folder 属于 Thread,文件与消息必须属于 Thread。Thread 不再创建第二套成员、密钥或网络连接;当前频道成员可读取该频道所有 thread。创建频道时自动建立“常规”thread,用户可继续创建其他 thread,分别上传文件或发消息。
注册设置初始密码。密码和绑定的 Passkey 是账号认证/解锁入口,不要求旧设备批准新设备;Passkey 的 WebAuthn 验证由 .NET 10 官方组件完成。支持 PRF 时浏览器封装保险库解密凭据;PRF 输出不传给服务器。不支持 PRF 的 Passkey 可登录,资源解密仍需密码。绑定需重新验证密码,撤销后该凭据不能再创建会话。
Member 加入空间后自动获得默认频道的聊天读取/发言和文件读取/上传权,只能修改自己的内容,不能管理频道或邀请成员。其他频道单独授权;历史聊天和文件由 Owner 分别决定是否向新成员开放。历史选择在接受邀请时生效:关闭时分别轮换该数据域 epoch,仅给新成员当前 epoch;已获授权的成员保留历史访问。之后修改开关只影响后续邀请,不撤回已交付密钥。
Account 使用不可修改的 username 作为稳定身份,displayName/avatar 由后端个人资料接口管理。AccountDevice 是账号下的浏览器身份,具有独立登录 session、名称与撤销状态;每个空间的设备 S 独立生成。空间成员绑定账号与角色,不绑定某次登录或设备。
空间是成员管理与同步的边界,不是内容密钥边界。频道属于空间,分组归属空间,文件夹属于频道,资源归属由后端校验和保存。新空间与初始“常规”频道在同一事务中创建。每频道独立维护 chat 与 files 两个数据域及 epoch;分组仅组织频道,不创建密钥域。
## 模块互通
账号模块提供当前身份、个人资料、设备会话与解锁后的账号密钥。频道模块负责创建/邀请/接受/打开频道、thread 和文件目录。加密模块不读取 UI 或数据库,只提供账号密钥封装、频道凭据封装和帧加解密。Worker 是三者的编排入口,组件经 store/action 调用它。
账号模块提供当前身份、个人资料、设备会话与解锁后的账号密钥。空间模块负责创建空间、初始频道、成员邀请与加入;频道模块负责频道、分组、文件夹和频道内的聊天/文件访问。权限、历史范围、epoch 和授权收件人由后端计算、持久化和校验。浏览器只根据服务端返回的授权计划生成密钥并进行封装;它不能选择或扩大授权集合。加密模块不读取 UI 或数据库,只提供账号密钥封装、随机对象密钥封装和帧加解密。Worker 是 API 与密码学之间的编排层,不是业务规则来源。
账号登录授权访问服务端;频道内容密钥授权解密内容。这两种能力分开校验,服务端不能通过登录 token 推出频道密钥。频道主的成员记录才是 owner 权限的最终依据,不能凭复制管理密钥冒充另一个账号。
## 密钥与多设备
账号邀请私钥保存在密码加密的保险库中,多台设备登录同一账号后解锁同一账号私钥。频道创建时生成独立随机 K 和管理密钥 A;创建者将 owner 凭据封装给自己,服务端原子存储频道、owner 成员与该账号密钥包。邀请其他账号时仅封装 member 凭据,不能交付 A。密钥包绑定 recipient、channelId 和 mode;服务端保存密文,收件账号自动领取并在本机解密。
账号邀请私钥保存在密码加密的保险库中,多台设备登录同一账号后解锁同一账号私钥。空间创建时生成空间访问凭据,Owner 凭据与 Owner 账号密钥包由后端原子存储;它们不再加密频道内容。频道 chat/files 域密钥在浏览器生成,并通过 Owner 账号 ECDH 公钥封装;服务端保存密文,获授权成员的账号可领取并在本机解封。
已加入成员的新设备只凭账号登录即可恢复频道密钥,不需要邀请者在线,不需要复制带密钥的链接。每个浏览器有独立设备登录凭据和独立频道 S;读取账号频道列表不占席位,实际打开频道才为设备加入席位。设备撤销会撤销其账号 sessions;账号频道成员身份保留,其他设备继续访问。
已加入成员的新设备只凭账号登录即可解封账号分发给该账号的数据域密钥,不需要邀请者在线,也不需要复制带内容密钥的链接。每个浏览器有独立设备登录凭据和独立空间终端 S;读取账号空间列表不占成员名额,实际打开空间才登记设备席位。同一账号在一个空间只占一个成员名额,设备数量不受成员上限影响。设备撤销会撤销其账号 sessions;账号空间成员身份及其人数名额保留,其他设备继续访问。
本机记住登录保存 session token、不可导出的保险库 CryptoKey 和加密保险库,不保存密码/verifier。它允许本机用户解锁此账号;退出删除该记录。登录态与本机资源数据库在同一启动屏障后恢复,避免先加载访客资源再切换账号。
本机记住登录保存 session token、不可导出的保险库 CryptoKey 和加密保险库。勾选记住账号密码时,将填充资料用另一把不可导出的本机 AES-GCM 密钥加密保存;不保存 verifier。退出清除自动恢复 session,保留已选择保存的填充资料,明确清除时全部移除。登录态与本机资源数据库在同一启动屏障后恢复,避免先加载访客资源再切换账号。
## 文件目录与内容
thread/文件夹结构以及文件目录记录使用频道加密日志。上传文件先完成密文上传,再发布加密 file.published 目录记录,包含文件 ID、thread/folder、名称、类型、大小;成员同步目录无需下载文件内容,也不消耗下载次数。目录与文件内容中的元数据必须相互匹配,否则拒绝展示。
频道、分组、目录和资源归属通过服务端结构接口管理,前端拉取快照并渲染。上传文件完成密文传输后提交资源归属;归属操作也保存在本机队列中,断网重试不丢失。文件名称、类型、大小继续保存在加密 file.published 元数据中,读取元数据不消耗下载次数;下载内容后校验元数据一致性。
频道 K 经 HKDF 派生内容根与每文件独立 AES-GCM 密钥;保留现有逐帧认证、FINAL、连续性、padding、ID/AAD 校验与持久化密文重试。下载逐块解密到本机临时存储,完整性和大小校验成功才交付文件;出错清除临时内容。OPFS 不可用时使用 Blob 回退并明确限制。
不迁移旧加密结构或旧账户资料,不提供导入接口。新结构与新资料直接由后端管理,客户端不再处理结构控制记录。
空间访问凭据不再用于频道内容加密。每频道 chat/files 域各自生成随机 32 字节 epoch 密钥,按账号公钥封装;每个聊天条目、文件及加密发布元数据再独立生成随机对象密钥,由对应域密钥加密封装。对象封装的 AAD 绑定空间、频道、域、epoch 和 itemId。保留现有逐帧认证、FINAL、连续性、padding、ID/AAD 校验与持久化密文重试。下载逐块解密到本机临时存储,完整性和大小校验成功才交付文件;出错清除临时内容。OPFS 不可用时使用 Blob 回退并明确限制。
## 安全边界
共享频道密钥意味着任何成员都能解密历史,不能通过移除成员收回其已持有的 K 或明文。当前不实现密钥 epoch 轮换或前向保密,不能声称移除成员可阻止恶意服务端向其提供将来密文。服务端成员/设备撤销负责正常服务下的访问控制。
频道域 epoch 与随机对象密钥允许新成员按设置只领取当前 epoch,服务端也会在写入时检查作者、频道授权及当前 epoch。`WorkspaceChannel` 是唯一频道聚合;Chat 与 FileGroup 是独立域,授权和密钥隔离,但共享对象帧存储。账号设备已具备根签证书和 FINAL 对象帧签名验证基础;频道结构命令、删除事件和成员/设备撤销后的域轮换仍未完成。已获得的历史密钥和明文无法被收回,因此不能声称已实现前向保密。
首次邀请公钥来自服务端目录,必须如实保留公钥替换风险;AES-GCM 认证证明密文未被未持钥者篡改,不证明具体发送账号。逐账号签名、密钥透明性与密钥轮换属于单独的协议升级,不能以补齐账号功能代替这些保证。前端代码与运行终端仍是信任根。
账号签名根公钥和根签名设备证书已有基础实现,首次从服务端获得的其他账号根公钥使用 TOFU 固定;首次密钥替换仍无法检测。频道结构、权限、作者归属和聊天/文件生命周期目前尚未改为签名事件,客户端尚未校验 Owner Capability 或事件签名。密钥透明性、授权/事件签名和成员撤销密钥轮换仍未完成,不能宣称已抵御主动恶意服务端。前端代码与运行终端仍是信任根。
账号昵称与设备名是不同概念,昵称不参与密钥派生。头像仅接受有大小上限的 PNG/JPEG/WebP,保存在加密资料中,禁止第三方头像 URL 和可执行 SVG。
账号昵称与设备名是不同概念,昵称不参与密钥派生。头像接受有大小上限的 PNG/JPEG/WebP,由后端管理,禁止第三方头像 URL 和可执行 SVG。聊天发送者由服务端条目作者和账号资料提供,不接受加密内容中的身份声明。
界面术语采用「空间 → 频道分组 → 频道 → 文件夹/聊天」。内部 Channel/Thread 命名保留,分别映射空间/频道;分组沿用加密 CONTROL 日志,不引入新的成员或密钥体系。
界面术语采用「空间 → 频道分组 → 频道 → 文件夹/聊天」。旧资源路由 /channels 和帧协议 threadId 保留,分别映射空间/频道;新结构接口使用 /spaces。账号保险库保存私钥和访问凭据,资料与结构不再由客户端合并同步。