## 9. 安全考虑

### 9.1 威胁模型

AUN 在 `gateway`、`peer`、`relay` 三种模式下都面临安全威胁，只是攻击面位置不同：
- 未授权访问
- 中间人攻击
- DDoS 攻击
- 会话劫持
- 私钥泄露
- 协议降级与链路劫持
- 假地址注入与恶意升级信令

### 9.2 防护措施

**传输层安全**：
- 强制 TLS (wss://)
- 证书验证
- 最低 TLS 1.2

**认证**：
- JWT token 身份认证（Auth 服务签发，ECDSA 签名）
- token 过期机制（建议 1-24 小时）
- token 刷新机制
- 会话隔离（不同用户消息严格隔离）
- Peer / Relay 模式下的证书链验证与双向 nonce challenge

> **授权说明**：AUN 协议层仅定义身份认证（authentication），不定义细粒度授权（authorization）。JWT claim 中的 `aid` 标识请求者身份，各 AUN 服务根据 `aid` 和自身业务逻辑实现资源访问控制。协议通过三个错误码区分不同的拒绝场景：`-32013` 表示未完成握手、`-32005` 表示 token 过期需重新登录、`-32003` 表示业务层权限拒绝。

**限流与防护**（推荐实现）：
- 连接频率限制
- 消息速率限制
- 最大消息大小限制
- IP 黑名单

> 注：以上防护措施是实现层面的建议，不是协议强制要求。实现者可根据实际需求选择合适的防护策略。

**私钥安全**：
- Gateway 不持有用户 AID 私钥
- 私钥完全由客户端管理
- 私钥永远不传输到 Gateway
- Gateway 使用 Auth 服务签发的 JWT token 代表用户身份
- 客户端负责私钥的安全存储（详见第 3.5 节）

### 9.3 责任边界


**Gateway 的责任**：
- **消息路由**：根据 AID 转发消息到目标
- **JWT token 验证**：验证客户端提供的 token 有效性
- **请求转发**：将 `auth.*` 等请求转发到对应的 AUN 服务（Auth、Message 等）

> **重要**：Gateway 不处理业务逻辑。AID 创建、两步登录等所有 `auth.*` 方法由 Auth 服务处理，Gateway 只负责路由转发。

**应用开发者的责任**：
- 用户身份管理
- 客户端私钥生成和安全存储
- 私钥访问控制（本地）
- 跨设备同步策略（可选）
- 密钥备份和恢复机制

**客户端的责任**：
- 私钥本地生成
- 私钥安全存储（Keychain/Keystore/IndexedDB）
- 用私钥签名 nonce 完成认证
- 私钥永远不离开客户端

**关键点**：
- Gateway 是 AUN 网络的一种接入网关
- 私钥管理完全由客户端控制
- Gateway 不参与私钥管理
- 符合零信任安全模型

### 9.4 JWT 信任模型分析

#### 9.4.1 单一信任根架构

AUN 的 JWT 认证基于**单一信任根**：只有 Auth 服务持有签发私钥，所有其他服务通过公钥验证。

```
┌─────────────────────────────────────────────────┐
│              单一信任根架构                        │
├─────────────────────────────────────────────────┤
│                                                 │
│   Auth 服务                                  │
│   ├─ 持有私钥（唯一能签发 token）                 │
│   └─ 签发 JWT token                             │
│                                                 │
│   所有 AUN 服务                                  │
│   ├─ HB (Heartbeat)                             │
│   ├─ MSG (Message)                              │
│   ├─ Storage                                    │
│   ├─ Group                                      │
│   └─ 都持有 Auth 服务的公钥证书               │
│      └─ 可以验证 token（ECDSA 非对称签名）       │
│                                                 │
│   Gateway                                       │
│   ├─ 只能转发认证请求                            │
│   ├─ 无法伪造 Auth 服务的签名                │
│   └─ 恶意 Gateway 签发的假 token 会被拒绝        │
│                                                 │
└─────────────────────────────────────────────────┘
```

#### 9.4.2 防止 Gateway 作恶

1. **无法伪造 token**：
   - Gateway 没有 Auth 服务的私钥
   - 无法生成有效的 ECDSA 签名
   - 所有服务都会拒绝无效签名的 token

2. **无法冒充其他用户**：
   - Token 中的 `aid` 字段标识用户身份
   - 即使恶意 Gateway 用自己的 AID 获取 token
   - 也无法访问其他用户的资源（token 中的 aid 与资源归属不匹配）

3. **私钥不经过 Gateway**：
   - 客户端本地签名 nonce
   - Gateway 只转发签名，看不到私钥
   - 无法伪造用户签名

#### 9.4.3 跨域消息路由安全

跨 Issuer 通信的安全由多层机制保障：

1. **mTLS 双向认证**：Gateway 间通过 mTLS 建立连接，双方证书链必须可验证到受信 Root CA
2. **Issuer 验证**：接收方验证消息 `from` 字段的 Issuer 与 mTLS 证书 Issuer 一致，防止跨 Issuer 伪造
3. **发送者签名**：消息携带发送者的 ECDSA 签名，接收方可用发送者公钥验证消息真实性
4. **E2EE 保护**：消息内容端到端加密，中转 Gateway 仅转发密文，无法解密

**攻击隔离**：
- 即使某个 Issuer 的 Gateway 被攻破，也只能影响该 Issuer 的用户
- 无法伪造其他 Issuer 用户的消息（Issuer 验证机制）
- 无法解密消息内容（E2EE 保护）

### 9.5 连接升级与模式切换安全

`peer.offer` 允许把现有稳定通道升级为更优链路，但也引入了新的攻击面。实现方必须把升级视为独立的安全敏感流程，而不是普通业务消息。

#### 9.5.1 降级攻击

攻击者可能阻断 `peer.offer`、`peer.accept` 或 `peer.switch`，迫使双方长期停留在 `gateway` 或 `relay` 通道。

**影响**：
- 无法使用低延迟直连
- 更容易暴露链路元数据
- 可能诱导实现方错误地把“升级失败”视为“对端不可用”

**要求**：
- 升级失败时，客户端 **MUST** 保持原有已认证通道可继续工作
- 实现 **MUST NOT** 因升级失败而丢弃当前业务连接
- 实现 **SHOULD** 对升级失败与业务通道失败分别记录日志

#### 9.5.2 假地址注入

攻击者可能通过伪造 `peer.offer` 中的 `endpoints`，诱导一方去连接恶意地址。

**风险**：
- 目标被引导连接攻击者控制的主机
- 实现若错误地“只看地址不验身份”，可能造成链路劫持

**要求**：
- `peer.offer` 只能作为“尝试建立新通道”的信令，**MUST NOT** 单独构成身份信任依据
- 新通道建立后，双方 **MUST** 重新执行 `initialize(mode=peer)` 与完整 `peer.*` 认证
- 只有在新通道 `peer.confirmed` 成功后，才可发送 `peer.switch`
- 实现 **SHOULD** 对 `peer.offer.endpoints` 做基本策略校验，如协议、端口范围、地址白名单或 NAT 策略检查

#### 9.5.3 回退通道滥用

若实现保留旧通道作为 fallback，攻击者可能利用“主通道/备通道并存”造成重复投递、状态不一致或把敏感流量强制打回旧通道。

**要求**：
- 切换期间旧通道 **MUST** 只作为 fallback，不应与新主通道无序并发发送同一业务消息
- 实现 **SHOULD** 为主通道选择维护显式状态，如 `primary` / `fallback`
- `meta.status` **SHOULD** 暴露当前主通道与 fallback 状态，便于诊断

#### 9.5.4 升级信令重放

旧的 `peer.offer`、`peer.accept`、`peer.switch` 被重放，可能导致重复切换、错误回退或异常连接风暴。

**要求**：
- 升级控制消息 **MUST** 带 `session_id`
- 升级控制消息 **SHOULD** 限定短有效期，如 `expires_in <= 30s`
- 实现 **SHOULD** 对已处理的 `session_id` 去重
- 超时或已完成的升级会话 **MUST NOT** 被再次接受

#### 9.5.5 连接升级的最小安全原则

1. 原通道先存在且已认证
2. 升级信令仅交换地址和意图，不直接授予信任
3. 新通道重新执行完整认证
4. 新通道认证成功后再切换主通道
5. 旧通道在切换确认前保持可用
6. 升级失败时安全回退，不影响业务可用性

### 9.6 公开 AP 同步与 Agent 发现安全

`search.*` 与公开 AP 同步把 `agent.md` 从“单点公开页面”提升为“可在全网传播的索引对象”，因此攻击面不再只是页面内容本身，还包括来源伪造、镜像污染、快照篡改和抓取滥用。

#### 9.6.1 来源伪造与索引投毒

攻击者可能伪造 `source_ap`、伪造 `agent_md_url` 或向公开 AP 注入伪造的 `agent.md`，诱导聚合器把恶意记录加入全网搜索结果。

**要求**：
- 聚合器 **MUST NOT** 仅凭 `aid` 字符串就信任一条 `agent.md`
- 聚合器 **SHOULD** 校验 `origin_url` 是否与该 `aid` 的公开 Web 入口规则一致
- 聚合器 **SHOULD** 记录 `source_ap`、`origin_url`、`agent_md_sha256` 和抓取时间，便于审计与回滚
- 对无法验证来源、缺少更新时间或频繁抖动的记录，聚合器 **SHOULD** 降权或隔离

#### 9.6.2 快照篡改与增量插入

若公开 AP 返回的 `search.snapshot` 或 `search.changes` 被中间人篡改，聚合器可能同步到错误内容，导致全网目录污染。

**要求**：
- 公开 AP 同步接口 **MUST** 运行在 TLS 保护的通道上
- 公开 AP **SHOULD** 为快照、增量页或其摘要提供签名校验材料
- 聚合器 **SHOULD** 对 `snapshot_version`、`sequence`、`cursor` 单调性做校验，发现倒退或跳变时拒绝直接覆盖本地索引
- 若校验失败，聚合器 **MUST** 将该来源标记为异常，并停止继续应用该来源的增量变更

#### 9.6.3 删除传播与误删风险

删除事件若处理不当，可能导致恶意 AP 通过伪造 `delete` 批量清空公开索引，或让聚合器误以为某个 Agent 已永久下线。

**要求**：
- 聚合器 **MUST** 把 `delete` 解释为“该来源撤回了该公开记录”，而不是默认代表全网永久删除
- 聚合器 **MUST** 结合原始来源与其他有效来源判断是否真正移除某个 `aid`
- 对高价值 Agent，聚合器 **SHOULD** 采用延迟删除、二次确认或多来源确认策略

#### 9.6.4 抓取滥用与资源消耗

公开 AP 若无节制暴露快照和增量接口，可能被恶意客户端反复抓取，造成带宽、存储和 CPU 消耗。

**要求**：
- 实现 **SHOULD** 对 `search.snapshot`、`search.changes` 施加速率限制、分页上限与并发控制
- 实现 **SHOULD** 为大快照提供分页、压缩和缓存
- 实现 **SHOULD** 区分普通终端查询和 AP 同步抓取的限额策略
- 对异常频繁的游标回退、全量重拉或大规模枚举行为，服务端 **SHOULD** 记录审计日志并触发告警

#### 9.6.5 最小安全原则

1. 公开搜索结果默认视为最终一致，而非绝对真相
2. 来源元数据必须和内容一起传播
3. 快照与增量都应可校验、可审计、可回放
4. 删除语义必须谨慎，不能让单个镜像来源删除全网记录
5. 聚合器要能隔离异常来源，而不是把异常扩散到全网索引

### 9.7 证书轮换后的验签时序校验

仅验证“签名数学正确 + 证书链有效 + 证书未过期”不足以满足 AUN 的证书轮换安全要求。

如果旧证书在数据库中已被标记为 `verify_only`，但验签方不检查签名时间，那么攻击者仍可能使用旧私钥伪造“看起来合法”的新消息。因此，AUN 要求验签流程增加证书状态与时间窗口校验。

#### 9.7.1 额外校验规则

验签方除标准 PKI 校验外，还应执行以下逻辑：

1. 根据 `cert_sn` 精确定位签名所用证书
2. 获取证书状态：`active_signing`、`verify_only`、`revoked`、`expired`
3. 校验 `sign_time` 是否落在证书有效期内
4. 若证书状态为 `verify_only`，则必须额外满足：
   - `sign_time < deactivated_at`
5. 若证书状态为 `revoked`，默认拒绝

#### 9.7.2 在线验签与历史验签

AUN 建议区分两类验签场景：

| 场景 | 推荐策略 |
|------|---------|
| 在线实时消息 | 要求签名证书在 `sign_time` 时处于 `active_signing` |
| 历史审计/归档验证 | 可接受 `verify_only`，但仍要求 `sign_time` 落在其活跃期内 |

#### 9.7.3 参考伪代码

```python
def verify_message(envelope, payload, cert_store):
    cert = cert_store.get_by_serial(envelope["cert_sn"])
    if cert is None:
        return False, "cert not found"

    if cert.lifecycle_state == "revoked":
        return False, "cert revoked"

    if envelope["sign_time"] < cert.valid_notbefore or envelope["sign_time"] > cert.valid_notafter:
        return False, "sign_time outside cert validity"

    if cert.lifecycle_state == "verify_only":
        if cert.deactivated_at is None:
            return False, "missing deactivated_at"
        if envelope["sign_time"] >= cert.deactivated_at:
            return False, "signature created after cert deactivation"

    signed_bytes = canonical_encode({
        "aid": envelope["aid"],
        "cert_sn": envelope["cert_sn"],
        "alg": envelope["alg"],
        "sign_time": envelope["sign_time"],
        "msg_id": envelope["msg_id"],
        "payload_hash": envelope["payload_hash"],
    })

    return verify_signature(cert.public_key, envelope["sig"], signed_bytes, envelope["alg"])
```

#### 9.7.4 与实现层的职责分工

该安全规则需要多层配合：

- **签发/轮换服务**：更新证书状态，将旧证书从 `active_signing` 切换为 `verify_only`
- **持钥组件**：只允许当前 `active_signing` 证书产生新签名
- **验签组件**：执行状态与时间窗口校验

若缺少最后一层，旧私钥在协议层仍可能被用来伪造“新消息”。


---
