# AUN-E2EE 扩展规范

> 版本：1.0-draft
> 状态：规范性文档
> 适用范围：AUN 客户端 SDK、客户端应用、跨语言实现
> 不适用范围：Gateway、Message 模块、Federation Service 的加解密实现
> 定位：**独立安全层**，横跨 `gateway`、`peer`、`relay` 三种连接模式，不属于任一子协议。不替代 `peer.*` 身份认证，不替代 TLS 传输加密，保护的是业务消息 payload 内容。

---

## 1. 目标与边界

AUN-E2EE 定义 AUN 客户端之间的端到端消息层加密协议。

本规范的目标是：

- 让 A 与 B 在现有 `message.send` / `event/message.received` 之上建立端到端加密会话
- 让 Gateway、Message 模块、Federation Service 仅看到最小必要路由元数据和密文 payload
- 为各语言 SDK 提供统一的握手流程、状态机、密文格式、重放保护和错误语义

本规范**不要求**服务端参与：

- 密钥交换
- 明文加解密
- 会话状态管理
- 密文 payload 结构解析

服务端职责仅为：

- 认证发送方
- 校验消息路由权限
- 透传 `encrypted: true` 的 payload
- 存储和转发密文 payload

---

## 2. 与 AUN-Core 的关系

AUN-E2EE 是 Layer 3 扩展协议，建立在以下核心能力之上：

- `message.send`
- `event/message.received`
- `message.pull`
- AID + 证书链身份体系

E2EE 控制消息和密文消息都通过普通 `message.send` 承载；Gateway 和 Message 模块无需识别其内部字段。

交叉引用：

- 若采用官方 AUN SDK，SDK 的公共接口形状、E2EE 暴露策略和客户端职责边界见 `docs/AUN-SDK-跨语言设计方案.md`
- 本文档是协议规范，定义跨语言客户端必须遵循的握手流程、状态机、密文结构与安全要求
- SDK 设计文档不能替代本协议；非 SDK 实现也必须遵循本文档

---

## 3. 明文信封与密文载荷

### 3.1 明文信封

以下字段必须保持为服务端可见的明文，以便进行路由和离线存储：

- `to`
- `persist`
- `encrypted`
- `message_id`
- `timestamp`
- `type`

### 3.2 密文载荷

业务内容必须位于 `payload` 中，并由发送方客户端在调用 `message.send` 前完成加密。

因此：

- 服务端不负责把明文转为密文
- 服务端不验证 `payload` 是否真的是密文
- 是否构成真正 E2EE，取决于 SDK 是否按本规范构造 `payload`

---

## 4. 术语

### 4.1 E2EE 会话

发送方与接收方之间的一段双向加密上下文，包含：

- `session_id`
- 协商出的算法套件
- 双方身份绑定材料
- 双方方向性计数器
- 会话生命周期状态

### 4.2 会话方向

每个 E2EE 会话是双向的，但计数器按方向区分：

- A -> B 方向有独立 `send_counter` / `recv_counter`
- B -> A 方向有独立 `send_counter` / `recv_counter`

### 4.3 控制消息

通过普通 `message.send` 传输的 E2EE 协商消息，`payload.type` 取值为：

- `e2ee.negotiate`
- `e2ee.accept`
- `e2ee.reject`
- `e2ee.close`

### 4.4 密文消息

通过普通 `message.send` 传输的密文业务消息，`encrypted` 必须为 `true`，`payload.type` 必须为 `e2ee.encrypted`。

---

## 5. 算法套件

### 5.1 实现要求

所有 AUN-E2EE 实现：

- **MUST** 支持 `P-256 + HKDF-SHA256 + AES-256-GCM`
- **SHOULD** 支持 `X25519 + HKDF-SHA256 + ChaCha20-Poly1305`
- **MAY** 支持 `P-384 + HKDF-SHA384 + AES-256-GCM`
- **MAY** 支持 `SM2 + SM3-KDF + SM4-GCM`

### 5.2 套件标识

算法套件使用字符串标识：

- `P256_HKDF_SHA256_AES_256_GCM`
- `X25519_HKDF_SHA256_CHACHA20_POLY1305`
- `P384_HKDF_SHA384_AES_256_GCM`
- `SM2_SM3_KDF_SM4_GCM`

### 5.3 身份绑定

E2EE 会话中的临时密钥必须绑定到 AUN 身份：

- 发起方和接收方必须用各自 AID 对应的私钥，对握手摘要进行签名
- 对端必须验证签名与 AID 证书链

仅交换临时公钥而不做身份签名绑定的实现**不符合本规范**

---

## 6. 会话状态机

### 6.1 状态定义

| 状态 | 说明 |
|---|---|
| `idle` | 尚未与对端建立 E2EE 会话 |
| `negotiating` | 已发送或收到 `e2ee.negotiate`，等待对端确认 |
| `accepted` | 已收到 `e2ee.accept`，等待本地完成共享密钥派生 |
| `active` | 会话已建立，可发送/接收密文消息 |
| `rekeying` | 旧会话仍可接收，正在建立新会话 |
| `closing` | 已发送或收到 `e2ee.close`，等待清理 |
| `closed` | 会话已正常关闭 |
| `failed` | 会话因错误进入失败状态 |

### 6.2 状态迁移

| 当前状态 | 触发条件 | 下一个状态 |
|---|---|---|
| `idle` | 发送 `e2ee.negotiate` | `negotiating` |
| `idle` | 收到 `e2ee.negotiate` | `negotiating` |
| `negotiating` | 收到 `e2ee.accept` 并完成校验 | `accepted` |
| `accepted` | 本地完成共享密钥派生 | `active` |
| `active` | 发起重协商 | `rekeying` |
| `rekeying` | 新会话建立成功 | `active` |
| 任意非终态 | 收到 `e2ee.close` | `closing` |
| `closing` | 本地清理完成 | `closed` |
| 任意非终态 | 校验失败 / 解密失败 / 计数器错误 | `failed` |

### 6.3 SDK 要求

SDK：

- **MUST** 持久化 `active` / `rekeying` 会话状态
- **MUST** 在进程重启后恢复未过期会话
- **MUST NOT** 因连接断开自动丢失会话

---

## 7. 控制消息格式

所有控制消息都通过普通 `message.send` 发送，且：

- `encrypted` 必须为 `false` 或缺省
- `payload.type` 必须为本节定义的 E2EE 控制消息类型

### 7.1 `e2ee.negotiate`

用于发起会话协商。

```json
{
  "jsonrpc": "2.0",
  "method": "message.send",
  "params": {
    "to": "bob.aid.pub",
    "persist": true,
    "payload": {
      "type": "e2ee.negotiate",
      "version": "1",
      "session_id": "uuid-v4",
      "supported_suites": [
        "P256_HKDF_SHA256_AES_256_GCM",
        "X25519_HKDF_SHA256_CHACHA20_POLY1305"
      ],
      "ephemeral_public_key": "base64",
      "identity_key_fingerprint": "sha256:...",
      "nonce": "base64",
      "signature": "base64"
    }
  }
}
```

字段要求：

- `session_id`：**MUST** 为 UUID v4
- `supported_suites`：**MUST** 非空
- `ephemeral_public_key`：**MUST** 为与候选套件兼容的临时公钥
- `nonce`：**MUST** 为随机值
- `signature`：**MUST** 为对握手摘要的签名

握手摘要为以下字段按确定顺序序列化后的摘要：

- `version`
- `session_id`
- `supported_suites`
- `ephemeral_public_key`
- `identity_key_fingerprint`
- `nonce`

### 7.2 `e2ee.accept`

用于确认所选套件并返回接收方握手材料。

```json
{
  "type": "e2ee.accept",
  "version": "1",
  "session_id": "uuid-v4",
  "selected_suite": "P256_HKDF_SHA256_AES_256_GCM",
  "ephemeral_public_key": "base64",
  "identity_key_fingerprint": "sha256:...",
  "nonce": "base64",
  "signature": "base64"
}
```

### 7.3 `e2ee.reject`

用于拒绝会话协商。

```json
{
  "type": "e2ee.reject",
  "version": "1",
  "session_id": "uuid-v4",
  "reason": "no_common_suite",
  "supported_suites": [
    "P256_HKDF_SHA256_AES_256_GCM"
  ]
}
```

### 7.4 `e2ee.close`

用于显式关闭会话。

```json
{
  "type": "e2ee.close",
  "version": "1",
  "session_id": "uuid-v4",
  "reason": "rekey"
}
```

---

## 8. 密文 payload 格式

### 8.1 通用格式

密文业务消息必须满足：

- `encrypted` = `true`
- `payload.type` = `e2ee.encrypted`

```json
{
  "jsonrpc": "2.0",
  "method": "message.send",
  "params": {
    "to": "bob.aid.pub",
    "persist": true,
    "encrypted": true,
    "payload": {
      "type": "e2ee.encrypted",
      "version": "1",
      "session_id": "uuid-v4",
      "suite": "P256_HKDF_SHA256_AES_256_GCM",
      "counter": 12,
      "nonce": "base64",
      "ciphertext": "base64",
      "tag": "base64",
      "aad": {
        "from": "alice.aid.pub",
        "to": "bob.aid.pub",
        "message_id": "msg-123",
        "timestamp": 1710504000000
      }
    }
  }
}
```

### 8.2 字段要求

| 字段 | 要求 |
|---|---|
| `version` | **MUST** 为 `"1"` |
| `session_id` | **MUST** 指向已建立会话 |
| `suite` | **MUST** 与会话套件一致 |
| `counter` | **MUST** 为发送方向单调递增无符号整数 |
| `nonce` | **MUST** 为当前 AEAD 所需长度 |
| `ciphertext` | **MUST** 为 Base64 编码 |
| `tag` | **MUST** 为 Base64 编码 |
| `aad` | **MUST** 包含外层明文信封中参与认证的字段副本 |

### 8.3 AAD 要求

发送方和接收方都必须把以下字段纳入 AEAD AAD：

- `from`
- `to`
- `message_id`
- `timestamp`
- `session_id`
- `counter`

这样即使 Gateway 改写路由头，也会导致认证失败。

> **实现说明**：`timestamp` 始终参与 AEAD tag 计算（即发送方写入 AAD 的 `timestamp` 值被 AEAD 认证保护）。但由于 Gateway/Message 服务在持久化和转发时可能使用服务端时间戳替换外层信封的 `timestamp` 字段，接收方在校验 AAD 与外层信封一致性时，**MAY** 跳过 `timestamp` 字段的匹配检查。这不会削弱 AEAD 保护——任何对 AAD 中 `timestamp` 值的篡改都会导致 AEAD 解密失败。

---

## 9. 重放保护与计数器

### 9.1 发送方规则

发送方：

- **MUST** 为每个方向维护单独 `send_counter`
- **MUST** 在每次发送密文消息后递增
- **MUST NOT** 重用同一 `(session_id, counter, nonce)` 组合

### 9.2 接收方规则

接收方：

- **MUST** 为每个方向维护 `max_seen_counter`
- **MUST** 拒绝 `counter <= max_seen_counter` 的重复消息
- **MAY** 实现有限窗口乱序接收，但窗口大小必须显式配置

默认规则：

- 单聊 SDK **SHOULD** 采用严格单调递增校验

### 9.3 解密失败处理

当发生以下任一情况时，接收方必须拒绝消息：

- `session_id` 不存在
- `counter` 回退
- `suite` 不匹配
- AAD 校验失败
- AEAD tag 校验失败

接收方可：

- 本地记录错误
- 触发重协商
- 发送 `e2ee.close`

---

## 10. 会话恢复与重协商

### 10.1 会话恢复

若客户端重启但本地持久化状态仍在，则：

- **MUST** 恢复 `session_id`
- **MUST** 恢复方向计数器
- **MUST** 恢复会话套件

客户端恢复后可直接继续发送密文消息，无需重新握手。

### 10.2 触发重协商的条件

以下情况应触发新会话协商：

- 会话过期
- 本地状态丢失
- 连续解密失败
- 对端显式要求 `rekey`
- 算法升级

### 10.3 重协商规则

重协商必须：

- 使用新的 `session_id`
- 不覆盖旧会话的历史消息解密能力
- 在新会话进入 `active` 前保留旧会话只读能力

### 10.4 禁止静默降级

若 E2EE 协商失败：

- SDK **MUST NOT** 自动降级为明文发送
- 是否允许降级，必须由上层应用显式决定

---

## 11. 错误码与关闭语义

### 11.1 `e2ee.reject.reason`

| 值 | 含义 |
|---|---|
| `no_common_suite` | 双方无共同算法套件 |
| `unsupported_version` | 对端不支持该 E2EE 版本 |
| `bad_signature` | 握手签名无效 |
| `identity_mismatch` | 身份绑定校验失败 |
| `policy_denied` | 本地策略拒绝建立 E2EE |
| `busy_rekeying` | 对端正在重协商 |

### 11.2 `e2ee.close.reason`

| 值 | 含义 |
|---|---|
| `normal` | 正常关闭 |
| `rekey` | 请求使用新会话替换当前会话 |
| `decrypt_failed` | 连续解密失败 |
| `counter_violation` | 计数器异常或重放嫌疑 |
| `session_expired` | 会话过期 |
| `local_state_lost` | 本地状态丢失，需重新建立 |

### 11.3 SDK 本地错误枚举

SDK 至少应暴露以下本地错误：

- `E2EE_SESSION_NOT_FOUND`
- `E2EE_BAD_SIGNATURE`
- `E2EE_BAD_COUNTER`
- `E2EE_DECRYPT_FAILED`
- `E2EE_UNSUPPORTED_SUITE`
- `E2EE_SESSION_EXPIRED`
- `E2EE_DOWNGRADE_BLOCKED`

---

## 12. 与 `message.*` 的集成要求

### 12.1 `message.send`

- 握手消息通过普通 `message.send` 发送
- 密文业务消息通过 `message.send + encrypted:true` 发送

### 12.2 `message.pull`

SDK 在从服务端拿到消息后：

- 若 `encrypted != true`，按普通明文处理
- 若 `encrypted == true`，且 `payload.type == "e2ee.encrypted"`，则按本规范解密
- 若 `encrypted == true` 但 payload 不符合本规范，返回 SDK 级错误

### 12.3 离线消息

离线消息允许服务端持久化，但持久化内容应为密文 payload，而不是明文业务对象。

---

## 13. 安全要求

实现 AUN-E2EE 的 SDK：

- **MUST** 使用加密安全随机数生成 `nonce`
- **MUST** 验证对端证书链和握手签名
- **MUST** 在解密前校验 `session_id`、`suite`、`counter`
- **MUST NOT** 把会话共享密钥写入明文日志
- **MUST NOT** 自动明文降级
- **SHOULD** 为每个会话设置有效期，默认 24 小时
- **SHOULD** 在设备锁定能力可用时使用系统密钥存储保存会话材料

---

## 14. 实现说明

本规范只约束：

- 客户端如何建立和管理 E2EE 会话
- 客户端如何构造和解析密文 payload

本规范不要求修改当前 `message` 服务端实现。  
后续 SDK 集成应以本规范为准，服务端继续保持“透明中转 + 密文持久化”的角色。
