Replace the custom encrypted container with AgeSwift 0.1.0 so standard age tools can inspect sensitive configuration independently of the app. Use independent Keychain X25519 identities for catalog.age and proxy-listening.age. Preserve atomic commits and confirmed recovery; reject historical NG2 formats without migration or cleanup.
8.6 KiB
持久服务器库整体加密与主密钥恢复边界
后续修订:加密格式、文件名、identity 存储和匹配方式按 ADR-0050 执行;范围外的监听配置采用独立主密钥整体加密,见 ADR-0042;该修订已实施,服务器库自身的恢复规则保持不变。
Status: Accepted(2026-10-18)。
实施状态:整体加密、记录直接携带秘密及移除 NG2 历史格式兼容均已实施。当前实现以代码为准,验证结果见对应交付记录。
持久服务器库采用标准 age 文件格式整体加密,通过 AgeSwift 使用 X25519 identity,详见 ADR-0050。密码、完整插件参数、订阅 URL 与配置目录、订阅关联、收藏共同进入密文;服务器库专用 Keychain 项只保存标准 age identity 私钥文本,不再逐项保存这些凭据。监听认证等范围外凭据保持独立存储;测试 URL 按 ADR-0036 保存为非敏感偏好。sslocal-active.json 与派生认证文件的加密另案处理。
主密钥采用经典登录钥匙串,并以严格 ACL 限制获准应用访问。该选择优先考虑开源分发中的维护者签名变更和用户自行编译;不通过放宽到任意应用访问来换取兼容。更换签名后的前台重新授权与迁移路径仍须设计,并在真实签名构建中验证,不能承诺静默读取旧密钥。
启动和后台读取不得主动触发授权弹窗;遇到需要交互的状态时显示可重试的错误,只有用户主动解锁时请求系统授权。当前进程成功读取过密钥后继续使用内存资料。签名变更后的重新授权沿用该显式入口,实际系统授权结果以真机验证为准。
首版不提供完整服务器库的独立密码加密备份。用户接受主密钥丢失后通过重新导入或订阅恢复资料;未另存的手动资料无法保证恢复。ADR-0018 的 SIP-008 明文配置组导出保持其原有用途,不等同于完整服务器库的加密备份。
内存与加载失败
应用成功解密后,主密钥与服务器库明文在当前应用进程内保留至退出;首版不增加手动锁定功能。此后锁定钥匙串不撤销当前进程已经读取的资料,当前应用继续工作。应用重新启动后重新读取主密钥。
整体解密使服务器密码随服务器库进入内存,因此调整 ADR-0030 的按需读取边界:不再承诺只有打开编辑界面才装载密码,也不再承诺插件参数明文只在详情会话中持有。详情界面仍不显示密码,编辑草稿仍与详情展示分离。非秘密展示投影、诊断与日志不得因整体解密而包含秘密。
加载失败时保留原文件,阻止服务器库写入,并提供重试入口;已有后台代理服务继续运行。不得将解密失败、密钥不可用、格式不支持或文件损坏自动视为空库。加载失败后,只有用户明确确认放弃原配置时,才进入新建流程。
用户可以在加载失败后明确确认创建空库,并覆盖旧文件。该操作必须独立于重试,确认文案必须说明覆盖将放弃旧服务器库;取消操作不得修改旧文件。此授权是产品行为决定,不是本次设计讨论中立即删除本机数据的授权。重置时创建新主密钥,成功保存新空库后清除活动目标并撤下原服务器出口,保留代理服务开关意图,沿用现有空库收敛行为。新空库保存失败时保留旧文件与原运行状态。
加载失败状态不得作为空库传入启动时的活动目标重展开流程。当前活动目标独立持久化;以空库重展开会清除目标并部署无服务器出口的运行配置,违背失败时保留已有代理运行的决定。加载失败状态必须与真正空库明确区分。
记录与提交
服务器记录直接保存密码与可选的完整插件参数,订阅记录直接保存订阅 URL。不再保留库内凭据引用、独立秘密字典或按引用读取秘密的存储。主密钥仍独立保存在 Keychain;范围外凭据存储不因此改变。
目录、订阅与收藏组成完整工作副本,秘密随所属记录一起修改。提交执行「修改副本 → 校验 → 整体加密并原子保存 → 发布完整已提交值」。失败时丢弃副本,保留旧字段与旧秘密,不再为服务器库执行逐项凭据补偿。运行时收敛仍在持久化成功后进行,运行时失败不回滚已提交服务器库。
复制记录时复制凭据值并生成新节点身份,后续修改互不影响;订阅刷新保留既定节点身份与顺序规则,不再复用或清理凭据引用。订阅 URL 编辑必须先持久化成功再请求刷新,且不因 URL 编辑本身重展开服务器出口。
生产服务器库存储只提供加密写入,移除未注入加密存储时的明文 JSON 写入路径。显式配置组导出与分享保持原有用途;非秘密展示投影、诊断与日志不得直接携带完整记录。
历史格式与旧版导入
NG2 尚未发版,不再支持其历史数据迁移,包括旧 catalog.json 与逐项 Keychain 凭据,以及已实现的「目录 + 引用秘密表」加密格式。新记录格式使用明确的新版本,只读取当前格式;移除旧版本兼容解码、自动迁移与迁移清理,不读取或清理旧逐项 Keychain 凭据。
持久文件路径按 ADR-0050 改为 catalog.age。发现旧自研 catalog.enc 或旧格式库时报告不支持的格式,阻止写入,不自动转换、不退回其他旧文件,也不静默当作新安装。只有当前库与历史 .enc、.json 均不存在时才按全新空库处理;旧 catalog.json 的存在检查只用于识别旧库,不读取其内容。用户仍可明确确认放弃旧库并创建空库;该操作只提交新的 catalog.age,不增加对旧文件或旧 Keychain 项的清理。
保留用户主动执行的旧版 ShadowsocksX-NG 服务器导入与再次导入。这是 ADR-0002 确定的产品功能,不属于 NG2 内部格式迁移。导入直接构造携带凭据值的手动记录,保留既定身份、完成标记和失败恢复语义;不导入旧版偏好或改写旧版数据。
主密钥生命周期
首版不提供手动或定期轮换入口。正常保存沿用匹配的 identity,每次生成新的 age 密文;用户明确重置服务器库时创建新主密钥。密钥创建与首份文件提交的失败保护属于实现契约,不得先删除旧密钥再尝试写入新文件。
实施与验收
实现保持单应用进程、串行保存与单个已匹配 identity 的内存模型;Keychain 保留重置前的 identity,不引入多进程迁移协调、自动轮换或历史密文恢复系统。采用标准 age v1,不附加自研文件头;JSON 结构版本在密文内,读取兼容二进制和 ASCII armor。服务器库保存过程只写密文临时文件,不向日志或诊断报告增加秘密。主密钥不写入普通文件、日志或诊断报告;运行契约沿用本文开头规定的范围。磁盘权限沿用目录 0700、文件 0600。
验收集中于本次实际改变的路径:
- 文件与加密:完整资料往返不丢字段;多次保存生成不同密文;错误密钥、篡改与不支持版本返回错误;文件不含样本资料明文。
- 格式与失败:历史明文库与旧加密格式均报告不支持,不转换、不覆盖、不清理旧凭据;提交失败保留旧文件和完整已提交内存值;只有明确确认重置才创建空库。
- 记录与提交:创建、编辑、导入与订阅刷新同时提交字段和秘密;复制后凭据修改互不影响;订阅 URL 保存失败不请求刷新;新存储不存在生产明文写入路径;保留诊断与展示投影脱敏。
- 产品行为:加载失败不写空库、不重展开原活动目标;授权仅由用户主动触发;取消重置保留旧数据,确认且保存成功后清除目标;现有导入、编辑、订阅与激活兼容。
- 真机:验证实际构建下的主密钥保存和读取、退出重启、钥匙串锁定与前台授权;运行进程验证与纯文件测试分别记录。没有实际执行的签名身份变更或跨机恢复测试标为未验证,不扩展为首版完整签名组合测试矩阵。
用户通过 implement 指令确认实施本次修订。上述验收条件是实施检查清单,实际执行结果见对应提交与交付记录。
研究依据见 服务器配置文件整体加密方案研究。该报告是候选方案与调研记录,本 ADR 仅采纳上述明确列出的决定。