外观
ADR 006 推送网关隐私模型
- 状态:提案,待总控决策
- 范围:消息实例、Push Gateway、APNs、iOS 客户端、三种部署模式
- 不在本 ADR 冻结:密码协议、身份地址、备份格式、开源许可、生产安全声明
1. 背景
iOS 后台实时通知必须经 APNs。官方 App 的 APNs 凭据由官方 Bundle ID 的开发者持有,普通自托管者不能为同一 Bundle ID 独立发送 Push。若消息实例直接把消息内容或稳定身份交给 Gateway,会扩大元数据和内容信任边界。
1.1 来源 MUST
- APNs payload 不包含发送人、消息正文、会话 ID、完整实例域名或附件信息。
- Gateway 不接收消息密文,只接收随机
wake_route_id和无内容唤醒。 - 用户可关闭官方 Gateway;前台 WSS 仍工作,后台采用系统允许的最佳努力拉取并提示可能延迟。
- 完全主权部署使用独立 Bundle ID、自有 APNs 凭据和自有 Gateway,不共享官方私钥。
1.2 已验证事实
当前没有 Gateway、消息服务端、APNs 集成或抓包证据。因此本文是设计提案,不证明 payload、日志或存储已满足隐私要求。
2. 决策驱动因素
- Gateway 无法读取内容,也不能查询消息实例。
- 消息实例尽量不持有 APNs token;Gateway 尽量不知道用户身份、会话和消息实例域名。
- Push 失败不能影响已持久化密文的最终拉取。
- 支持官方、普通私有和完全主权三条线路,且不混淆其主权边界。
- 路由可撤销、轮换、限流和防重放。
3. 选项
3.1 选项 A 消息实例直连 APNs
优点是组件少。缺点是官方 Bundle ID 的凭据无法安全分发给普通自托管者;每个私有实例会直接持有 token 与 APNs 凭据,泄漏面大。仅适用于完全主权的独立 Bundle ID,不适合作为官方 App 的普通私有模式。
3.2 选项 B 官方 Gateway 代理 opaque wake
消息实例只提交随机 wake_route_id 和通用唤醒事件,Gateway 将其映射到 APNs token。优点是普通私有实例可使用官方 App 的后台通知,且 Gateway 不接触密文。缺点是官方仍可观察 route 与唤醒时间,消息实例也需信任官方 Gateway 的可用性。
3.3 选项 C 关闭 Push
优点是普通私有实例不与官方 Gateway 交互。缺点是 iOS 后台收信可能明显延迟,不能承诺实时。前台 WSS 和主动拉取仍可用。
3.4 选项 D 自有 Gateway 与独立 App 身份
适用于完全主权。优点是凭据和路由由部署者掌握;缺点是需要独立 Bundle ID、签名、APNs 配置、构建与分发,并仍依赖 Apple/APNs。
4. 推荐方案
待总控决策:按产品线路提供 B、C、D,而不是选一个覆盖所有部署:
- 官方实例:B,由官方运营消息实例与 Gateway,但权限和数据域分离。
- 普通私有实例:管理员/用户明确选择 B 或 C。
- 完全主权:D;不允许借用官方凭据,也不把 B 称为完全主权。
- A 只作为 D 的内部实现形态,不允许向普通私有实例分发官方 APNs 凭据。
推荐理由是它满足来源 MUST,同时把“私有消息存储”和“独立推送主权”清楚拆开。
5. 最小数据契约
消息实例向 Gateway 的请求概念上仅包含:
json
{
"gateway_api_version": 1,
"wake_route_id": "random-opaque-id",
"event": "new_data_available",
"idempotency_key": "random-one-time-key"
}不得包含 message_id、recipient_device_id、发送方、会话 ID、密文、实例完整域名、附件信息或用户可读文本。Gateway 发给 APNs 的 payload 只包含通用 content-available/最小 badge/collapse 控制;具体字段需按 Apple 当前规范验证后冻结。
6. 路由生命周期
- 客户端向 Gateway 建立 APNs token 到随机 route 的映射,或通过隐私保持的中转注册;具体注册拓扑为待总控决策。
- 消息实例只保存 route 引用,不保存可读 token。
- token 更新、App 重装、设备撤销或用户关闭 Push 时轮换/注销 route。
- APNs 返回永久无效时 Gateway 禁用映射,并以不泄漏 token 的错误类别通知消息实例。
- 唤醒请求短期去重和限流,不形成长期通信历史。
7. 待总控决策
7.1 route 注册拓扑
- 选项 1:客户端分别注册 Gateway 与消息实例。数据分离最好,但客户端流程和关联防护更复杂。
- 选项 2:消息实例中转注册。实现简单,但实例短暂接触 APNs token,扩大边界。
- 选项 3:Gateway 颁发一次性 route 凭证,客户端只把 route 引用交给实例。分离较好,需要防伪造和重放协议。
- 建议:优先评估 3,其次 1,不推荐 2。
- 验证:抓包、服务日志、数据库和崩溃包中检查 token/实例/身份关联;模拟重放和 route 猜测。
7.2 Gateway 认证
- mTLS:强实例认证,但证书签发/撤销复杂。
- 短期签名 token:易跨网络部署,但签发和轮换需防关联。
- 私网认证:只适用于同一运营域,不覆盖普通私有实例。
- 建议:公开 Gateway 使用可轮换的实例凭证 + 请求签名/防重放;具体算法跟随安全评审,不在此发明。
7.3 保留与限流
route 映射、唤醒日志和失败码的保留期、每实例/route 限流阈值待压测与隐私评估。建议只保留运行所需最短周期,不保存逐消息时间线。
8. 安全与隐私后果
- Gateway 仍能观察 route 与唤醒时间,APNs 仍能观察 token 和通知时序;无内容不等于无元数据。
- 通用 collapse 可能合并多个唤醒,客户端必须拉取直到游标耗尽。
- Push 可能丢失、延迟或乱序;消息正确性不能依赖 Push。
- Gateway 被攻陷时攻击者可能制造唤醒或分析时序,但不应取得消息密文、明文、联系人或实例查询权限。
9. 验证与接受标准
- OpenAPI schema 明确无 envelope、message、sender、conversation 和 domain 字段。
- 抓包检查消息实例到 Gateway、Gateway 到 APNs 的 payload 只有允许字段。
- 扫描双方日志、指标、trace、崩溃包和备份,无 token 全值、密文、身份或会话信息。
- Gateway/APNs 断开、限流、乱序、重复和 token 无效时,消息仍通过游标拉取到达。
- 关闭 Gateway 后 UI 明确后台延迟,前台 WSS 和主动拉取正常。
- 完全主权构建证明使用独立 Bundle ID/APNs 凭据;未完成前不得作主权宣传。