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.
5.8 KiB
使用 age 标准格式保存敏感配置
状态:已接受并实施(2026-10-21);用户通过 implement 指令确认实施。验证结果见交付记录。
敏感配置的加密存储采用标准 age 文件格式,使用 AgeSwift 实现。目的是减少自研加密协议与文件容器的维护成本,让维护者能够使用标准 age 兼容命令行工具解密检查配置,并使配置文件长期独立于特定语言、实现与维护者。此选择的主要目标不是提高现有方案的密码学强度。
该决定修订 ADR-0035 与 ADR-0042 中涉及的加密方法和文件封装规定。当前行为仍以代码为准。
已确认的范围与兼容边界
服务器库和代理监听配置同时替换自研加密格式。运行配置与 LAN 认证文件不在本次范围内,其加密另案处理。
延续 NG2 尚未发版、不迁移历史格式的决定。现有自研加密文件属于不支持的历史格式:保留原文件并阻止普通写入,不自动转换,不静默按新安装处理。沿用各自既有的用户明确确认重置流程;未确认重置不覆盖数据。此决定不授权在设计讨论期间重置本机配置或 Keychain 项。
已确认的格式与密钥
直接保存标准二进制 age 文件,不在 age 文件外增加自定义文件头。服务器库文件名改为 catalog.age,代理监听配置文件名改为 proxy-listening.age;配置结构版本放在加密后的 JSON 内。检测到旧 .enc 文件时,仍按不支持的历史格式处理,不因新路径不存在而创建空配置。
两个存储分别生成独立的 X25519 identity,将标准 AGE-SECRET-KEY-1... 私钥文本保存在经典登录钥匙串中,继续限制获准应用访问。正常保存复用 identity,用户明确确认重置时生成新的 identity。
按存储分别建立新的 age Keychain 命名空间,读取其中的 identity,由 AgeSwift 尝试匹配解密,不增加密钥索引文件。成功加载后缓存匹配的 identity,正常保存继续使用它。重置时先保存并回读新 identity,再原子替换密文,保留旧 identity;写盘失败或中途退出时,旧文件仍能用旧 identity 解密。密钥记录随重置次数增加,接受相应读取成本。
保留既有加载失败后的用户确认重置入口,涵盖文件损坏、格式不支持和密钥不可用等情况,不收窄为仅 identity 丢失才能重置。加载失败不自动判定 identity 丢失,也不自动重置。
标准工具与可读格式
开发文档说明如何使用系统 Keychain 工具取得 identity,再使用标准 age CLI 解密;不新增应用内私钥导出界面。保留系统授权要求,私钥不进入日志或诊断报告。
应用接受二进制与 ASCII armor 两种标准 age 文件,保存时统一写二进制。解密仍使用对应存储的 identity,解密后的 JSON 必须通过原有结构与内容校验。不增加口令加密、SSH 密钥或多接收方管理功能。
沿用的行为与失败保护
两个存储继续拥有独立的密钥与恢复生命周期:服务器库加载失败时保留原文件、阻止写入并提供重试或确认创建空库;监听配置加载失败时阻断正常工作区启动,用户确认重置成功后才解除阻断。失败或等待确认期间,保留既有后台代理和系统代理;成功保存后再按原有规则收敛运行状态。
保留后台读取不触发 Keychain 授权弹窗、显式交互入口控制授权、成功读取后在当前进程缓存的规则。Keychain 某个候选 identity 不可读取时,不将其视为文件损坏或自动新建密钥;能否用其他可访问候选正常读取,应以实际解密结果为准。没有可用匹配 identity 时报告读取失败并保留文件。
保留完整值校验、串行保存、仅写密文临时文件、同目录原子替换和保存成功后才发布内存值的契约。目录权限仍为 0700,文件为 0600。服务器库和监听配置的明文大小上限分别保持 64 MiB 和 1 MiB,读取前限制密文大小并考虑 age 分块与 armor 开销,解密后再次检查明文大小。AgeSwift 提供内存加解密 API,不承担文件提交、Keychain 或大小限制。
不迁移、读取或清理旧 AES 密钥记录;不增加自动密钥清理、轮换、完整库加密备份或手动锁定功能。用户主动执行的旧版 ShadowsocksX-NG 导入与现有配置组导出、分享保持既有用途。
实施与验收要求
通过 Swift Package Manager 精确固定 AgeSwift 0.1.0;其 Swift API 底层使用 Rust age 与预编译 XCFramework。实际接入时核实发布产物、校验和及应用和测试目标的链接,不以本地同名仓库作为交付依赖。移除生产自研 AES 文件封装读写,更新相关格式文档及 ADR-0035、ADR-0042 的现行描述,历史研究报告保留原时点内容。
验收覆盖两个存储的完整字段往返、重复保存密文不同、错误或缺失 identity、篡改与截断、大小限制、JSON 结构版本与内容校验,以及二进制和 armor 读取。使用标准 age CLI 与应用实现做双向互操作验证,不仅验证库内自行加解密。
验证旧 .enc 和旧 JSON 的存在不会触发静默初始化;新 .age 存在时以新文件为准,不回退到旧文件。验证提交失败与重置中断保留旧文件和旧 identity 的可读性、两个存储恢复互不影响,以及启动恢复门禁。真机核实 Keychain ACL、后台非交互读取、显式授权和退出重启后的读取,并验证文档中的系统 Keychain 工具与 age CLI 操作;未执行的验证如实标为未验证。