Skip to content

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_idrecipient_device_id、发送方、会话 ID、密文、实例完整域名、附件信息或用户可读文本。Gateway 发给 APNs 的 payload 只包含通用 content-available/最小 badge/collapse 控制;具体字段需按 Apple 当前规范验证后冻结。

6. 路由生命周期

  1. 客户端向 Gateway 建立 APNs token 到随机 route 的映射,或通过隐私保持的中转注册;具体注册拓扑为待总控决策
  2. 消息实例只保存 route 引用,不保存可读 token。
  3. token 更新、App 重装、设备撤销或用户关闭 Push 时轮换/注销 route。
  4. APNs 返回永久无效时 Gateway 禁用映射,并以不泄漏 token 的错误类别通知消息实例。
  5. 唤醒请求短期去重和限流,不形成长期通信历史。

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 凭据;未完成前不得作主权宣传。

系统初步设计 · 待决事项不代表批准 · 安全方案尚未完成独立审计