@agentunion/fastaun-browser 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/_packed_docs/CHANGELOG.md +26 -0
- package/_packed_docs/agent.md/SCHEMA.md +173 -0
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +43 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +205 -0
- package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +197 -0
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +549 -0
- package/_packed_docs/protocol/02-/350/257/201/344/271/246/344/270/216/344/277/241/344/273/273/344/275/223/347/263/273.md +810 -0
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +262 -0
- package/_packed_docs/protocol/04-Peer-/345/255/220/345/215/217/350/256/256.md +180 -0
- package/_packed_docs/protocol/05-Relay-/345/255/220/345/215/217/350/256/256.md +164 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1135 -0
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +234 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +900 -0
- package/_packed_docs/protocol/08-AUN-E2EE.md +413 -0
- package/_packed_docs/protocol/09-/345/256/211/345/205/250/350/200/203/350/231/221.md +316 -0
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +804 -0
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +271 -0
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +329 -0
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +141 -0
- package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -0
- package/_packed_docs/protocol/README.md +71 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +118 -0
- package/_packed_docs/protocol/agent.md/examples/codeagent-claudecode.md +61 -0
- package/_packed_docs/protocol/agent.md/examples/human-developer.md +60 -0
- package/_packed_docs/protocol/agent.md/examples/openclaw-lobster.md +52 -0
- package/_packed_docs/protocol/aun-docs-guide.md +49 -0
- package/_packed_docs/protocol/index.md +114 -0
- package/_packed_docs/protocol//350/215/211/346/241/210-agent.md/347/255/276/345/220/215/345/215/217/350/256/256.md +205 -0
- package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +249 -0
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +337 -0
- package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +80 -0
- package/_packed_docs/protocol//351/231/204/345/275/225C-/347/247/201/351/222/245/347/256/241/347/220/206/344/270/216/350/272/253/344/273/275/346/201/242/345/244/215.md +704 -0
- package/_packed_docs/protocol//351/231/204/345/275/225D-Root_CA_/346/262/273/347/220/206/346/234/272/345/210/266.md +620 -0
- package/_packed_docs/protocol//351/231/204/345/275/225E-Root_CA_/345/207/206/345/205/245/346/265/201/347/250/213.md +605 -0
- package/_packed_docs/protocol//351/231/204/345/275/225F-Issuer_CA_/347/224/263/350/257/267/346/265/201/347/250/213.md +548 -0
- package/_packed_docs/protocol//351/231/204/345/275/225G-AID_/345/255/244/345/204/277/351/242/204/351/230/262/344/270/216/346/225/221/346/217/264/346/234/272/345/210/266.md +513 -0
- package/_packed_docs/protocol//351/231/204/345/275/225H-Identity/346/234/215/345/212/241/345/256/236/347/216/260/346/214/207/345/215/227.md +619 -0
- package/_packed_docs/protocol//351/231/204/345/275/225I-/350/267/250/345/237/237/346/266/210/346/201/257/350/267/257/347/224/261/345/256/236/347/216/260/346/214/207/345/215/227.md +492 -0
- package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +402 -0
- package/_packed_docs/protocol//351/231/204/345/275/225K-Agent_Web/345/217/221/347/216/260/345/215/217/350/256/256.md +130 -0
- package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +267 -0
- package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +367 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +223 -0
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +354 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +172 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +373 -0
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +611 -0
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1152 -0
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +150 -0
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +89 -0
- package/_packed_docs/sdk/09-custody-api-manual.md +445 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +1895 -0
- package/_packed_docs/sdk/09-message-rpc-manual.md +597 -0
- package/_packed_docs/sdk/09-meta-rpc-manual.md +142 -0
- package/_packed_docs/sdk/09-payload-reference.md +702 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +408 -0
- package/_packed_docs/sdk/09-stream-rpc-manual.md +275 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +72 -0
- package/_packed_docs/sdk/INDEX.md +131 -0
- package/_packed_docs/sdk/README.md +307 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +13 -11
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +38 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +179 -97
- package/dist/client.js.map +1 -1
- package/dist/namespaces/auth.d.ts +1 -0
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +20 -6
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/transport.d.ts +9 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +24 -0
- package/dist/transport.js.map +1 -1
- package/package.json +40 -37
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# AUN-E2EE 扩展规范
|
|
2
|
+
|
|
3
|
+
> 版本:2.0-draft
|
|
4
|
+
> 状态:规范性文档
|
|
5
|
+
> 适用范围:AUN 客户端 SDK、客户端应用、跨语言实现
|
|
6
|
+
> 不适用范围:Gateway、Message 模块的加解密实现
|
|
7
|
+
> 定位:**独立安全层**,横跨 `gateway`、`peer`、`relay` 三种连接模式
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. 目标与边界
|
|
12
|
+
|
|
13
|
+
AUN-E2EE 定义 AUN 客户端之间的端到端消息层加密协议。
|
|
14
|
+
|
|
15
|
+
本规范的目标是:
|
|
16
|
+
|
|
17
|
+
- 让 A 与 B 在现有 `message.send` / `event/message.received` 之上实现端到端加密
|
|
18
|
+
- 让 Gateway、Message 模块仅看到最小必要路由元数据和密文 payload
|
|
19
|
+
- 为各语言 SDK 提供统一的密文格式、降级策略、重放保护和错误语义
|
|
20
|
+
|
|
21
|
+
服务端职责为:
|
|
22
|
+
|
|
23
|
+
- 认证发送方
|
|
24
|
+
- 校验消息路由权限
|
|
25
|
+
- 透传 `encrypted: true` 的 payload
|
|
26
|
+
- 存储和转发密文 payload
|
|
27
|
+
- 存储和分发接收方预存的临时公钥(Prekey)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. 无状态设计哲学
|
|
32
|
+
|
|
33
|
+
AUN-E2EE 的核心工程哲学是**完全无状态**——每条消息独立可解密,不依赖任何历史状态。
|
|
34
|
+
|
|
35
|
+
### 2.1 设计选择
|
|
36
|
+
|
|
37
|
+
经典 IM 协议(Signal Double Ratchet、MLS TreeKEM)通过维护有状态的棘轮链实现消息级前向安全和 post-compromise security。AUN-E2EE 有意放弃这条路径,选择每条消息独立生成临时 ECDH 密钥对,原因如下:
|
|
38
|
+
|
|
39
|
+
| 有状态方案的代价 | AUN 场景下的问题 |
|
|
40
|
+
|----------------|----------------|
|
|
41
|
+
| 必须维护 per-peer 的 ratchet state | Agent 可能跨进程、跨设备、无持久连接 |
|
|
42
|
+
| 消息必须按序处理 | Agent 通信天然异步、乱序 |
|
|
43
|
+
| 状态丢失导致断链 | Agent 重启频繁,断链不可接受 |
|
|
44
|
+
| 需要双向在线握手建立会话 | Agent 可能长期离线 |
|
|
45
|
+
|
|
46
|
+
### 2.2 无状态的含义
|
|
47
|
+
|
|
48
|
+
- `E2EEManager` 是**纯工具类**,不持有任何会话状态
|
|
49
|
+
- 每条消息的加密密钥仅取决于当次生成的临时密钥对 + 对方的 prekey/长期公钥
|
|
50
|
+
- 客户端重启、状态丢失、消息乱序均不影响后续消息的加解密
|
|
51
|
+
- 不存在"断链"的概念——任何一条消息都可以独立解密
|
|
52
|
+
|
|
53
|
+
### 2.3 安全折中
|
|
54
|
+
|
|
55
|
+
无状态设计放弃了:
|
|
56
|
+
|
|
57
|
+
- **Post-compromise Security**:密钥泄露后无法通过 ratchet 自愈(通过 prekey 每小时轮换 + 7 天过期来限制影响范围)
|
|
58
|
+
- **消息级前向安全的连续演进**:不像 Double Ratchet 那样每条消息的 key 都从上一条演进而来
|
|
59
|
+
|
|
60
|
+
换来了:
|
|
61
|
+
|
|
62
|
+
- **零断链风险**:任何故障场景下都不会丢失解密能力
|
|
63
|
+
- **零状态同步**:无需 seq 计数器、无需 chain index、无需 skipped key 缓存
|
|
64
|
+
- **极简实现**:各语言 SDK 可以独立实现,不需要复杂的状态机
|
|
65
|
+
- **天然适配 Agent 场景**:跨进程、跨设备、无持久连接均可正常工作
|
|
66
|
+
|
|
67
|
+
这一哲学同样延伸到群组 E2EE(见 [08-AUN-E2EE-Group](./08-AUN-E2EE-Group.md))。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 3. 协议分层与 AUN-Core 的关系
|
|
72
|
+
|
|
73
|
+
AUN-E2EE 是 Layer 3 扩展协议,建立在以下核心能力之上:
|
|
74
|
+
|
|
75
|
+
- `message.send`
|
|
76
|
+
- `event/message.received`
|
|
77
|
+
- `message.pull`
|
|
78
|
+
- AID + 证书链身份体系
|
|
79
|
+
|
|
80
|
+
密文消息通过普通 `message.send` 承载;Gateway 和 Message 模块无需识别其内部字段。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 4. 明文信封与密文载荷
|
|
85
|
+
|
|
86
|
+
### 4.1 明文信封
|
|
87
|
+
|
|
88
|
+
以下字段必须保持为服务端可见的明文,以便进行路由和离线存储:
|
|
89
|
+
|
|
90
|
+
- `to`
|
|
91
|
+
- `delivery_mode`
|
|
92
|
+
- `encrypted`
|
|
93
|
+
- `message_id`
|
|
94
|
+
- `timestamp`
|
|
95
|
+
- `type`
|
|
96
|
+
|
|
97
|
+
### 4.2 密文载荷
|
|
98
|
+
|
|
99
|
+
业务内容位于 `payload` 中,由发送方客户端在调用 `message.send` 前完成加密。服务端不负责加解密,不验证 `payload` 是否真的是密文。
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 5. 术语
|
|
104
|
+
|
|
105
|
+
### 5.1 Prekey
|
|
106
|
+
|
|
107
|
+
接收方预先生成的临时 ECDH 密钥对。公钥(附身份签名)上传到服务端,私钥保存在本地。发送方获取后用于 ECDH 密钥协商。Prekey 不消耗(可多次读取),定期轮换。
|
|
108
|
+
|
|
109
|
+
### 5.2 密文消息
|
|
110
|
+
|
|
111
|
+
通过 `message.send` 传输的加密业务消息,`encrypted` 必须为 `true`,`payload.type` 必须为 `e2ee.encrypted`。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 6. 算法套件
|
|
116
|
+
|
|
117
|
+
### 6.1 实现要求
|
|
118
|
+
|
|
119
|
+
所有 AUN-E2EE 实现:
|
|
120
|
+
|
|
121
|
+
- **MUST** 支持 `P-256 + HKDF-SHA256 + AES-256-GCM`
|
|
122
|
+
- **MAY** 支持其他套件
|
|
123
|
+
|
|
124
|
+
### 6.2 套件标识
|
|
125
|
+
|
|
126
|
+
- `P256_HKDF_SHA256_AES_256_GCM`(必须支持)
|
|
127
|
+
|
|
128
|
+
### 6.3 身份绑定
|
|
129
|
+
|
|
130
|
+
- 发送方 **MUST** 用接收方证书验证 prekey 签名
|
|
131
|
+
- Prekey 签名格式:`sign(identity_private_key, "prekey_id|public_key_b64|created_at")`,其中 `created_at` 为 prekey 创建时间戳(ms)
|
|
132
|
+
- 仅交换公钥而不做签名验证的实现**不符合本规范**
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 7. 加密模式
|
|
137
|
+
|
|
138
|
+
AUN-E2EE 支持两种加密模式,SDK 自动按优先级选择。
|
|
139
|
+
|
|
140
|
+
### 7.1 模式 1:prekey_ecdh_v2(优先)
|
|
141
|
+
|
|
142
|
+
**条件**:服务端有接收方的 prekey
|
|
143
|
+
|
|
144
|
+
**流程**:
|
|
145
|
+
1. 发送方获取接收方 prekey(含签名)
|
|
146
|
+
2. 用接收方证书验证 prekey 签名(含 `created_at` 年龄检查,最大 30 天)
|
|
147
|
+
3. 生成临时 ECDH 密钥对
|
|
148
|
+
4. 四路 ECDH 派生密钥材料:
|
|
149
|
+
- dh1 = ECDH(ephemeral_private, prekey_public)
|
|
150
|
+
- dh2 = ECDH(ephemeral_private, recipient_identity_public_key)
|
|
151
|
+
- dh3 = ECDH(sender_identity_private, prekey_public)
|
|
152
|
+
- dh4 = ECDH(sender_identity_private, recipient_identity_public_key)
|
|
153
|
+
- combined = dh1 || dh2 || dh3 || dh4
|
|
154
|
+
5. HKDF-SHA256(combined, info="aun-prekey-v2:{prekey_id}") → message_key(32 字节)
|
|
155
|
+
6. AES-256-GCM(message_key, plaintext, AAD) → ciphertext + tag
|
|
156
|
+
7. 发送方用身份私钥对 `ciphertext + tag + aad_bytes` 签名(ECDSA-SHA256),附带 `sender_signature` 和 `sender_cert_fingerprint`
|
|
157
|
+
|
|
158
|
+
**安全特性**:
|
|
159
|
+
- 前向安全(临时密钥用完即丢)
|
|
160
|
+
- 一消息一密钥(每条消息独立临时密钥对)
|
|
161
|
+
- 四路 ECDH 绑定双方身份(类似 X3DH:dh3/dh4 将发送方身份绑定到密钥协商中),防止 prekey 被替换后的中间人攻击
|
|
162
|
+
- 发送方签名提供不可否认性和消息来源认证
|
|
163
|
+
- 支持接收方离线
|
|
164
|
+
|
|
165
|
+
### 7.2 模式 2:long_term_key(降级)
|
|
166
|
+
|
|
167
|
+
**条件**:接收方无 prekey,但有证书
|
|
168
|
+
|
|
169
|
+
**流程**:
|
|
170
|
+
1. 发送方获取接收方证书
|
|
171
|
+
2. 生成临时 ECDH 密钥对
|
|
172
|
+
3. 双路 ECDH 派生密钥材料:
|
|
173
|
+
- dh1 = ECDH(ephemeral_private, recipient_identity_public_key)
|
|
174
|
+
- dh2 = ECDH(sender_identity_private, recipient_identity_public_key)
|
|
175
|
+
- combined = dh1 || dh2
|
|
176
|
+
4. HKDF-SHA256(combined, info="aun-longterm-v2") → message_key(32 字节)
|
|
177
|
+
5. AES-256-GCM(message_key, plaintext, AAD) → ciphertext + tag
|
|
178
|
+
6. 发送方用身份私钥对 `ciphertext + tag + aad_bytes` 签名
|
|
179
|
+
|
|
180
|
+
**安全特性**:
|
|
181
|
+
- 一消息一密钥(每条消息独立临时密钥对)
|
|
182
|
+
- dh2 绑定双方身份,提供发送方认证
|
|
183
|
+
- 无前向安全(长期私钥泄露则历史消息可解密)
|
|
184
|
+
- 发送方签名提供不可否认性
|
|
185
|
+
- 支持接收方离线
|
|
186
|
+
|
|
187
|
+
### 7.3 模式选择策略
|
|
188
|
+
|
|
189
|
+
SDK **MUST** 按以下优先级自动选择:
|
|
190
|
+
|
|
191
|
+
1. **优先**:prekey_ecdh_v2(服务端有接收方 prekey)
|
|
192
|
+
2. **降级**:long_term_key(无 prekey 时,需客户端安全策略允许)
|
|
193
|
+
|
|
194
|
+
> Python SDK 默认 `require_forward_secrecy=true`,无 prekey 时拒绝 long_term_key 降级并抛出错误。需显式配置 `require_forward_secrecy=false` 才允许降级。
|
|
195
|
+
|
|
196
|
+
### 7.4 兼容性
|
|
197
|
+
|
|
198
|
+
- 发送端 **MUST** 使用 `prekey_ecdh_v2` 模式发送
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 8. 密文 payload 格式
|
|
203
|
+
|
|
204
|
+
### 8.1 prekey_ecdh_v2 格式
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"type": "e2ee.encrypted",
|
|
209
|
+
"version": "1",
|
|
210
|
+
"encryption_mode": "prekey_ecdh_v2",
|
|
211
|
+
"suite": "P256_HKDF_SHA256_AES_256_GCM",
|
|
212
|
+
"prekey_id": "uuid",
|
|
213
|
+
"ephemeral_public_key": "base64(X9.62 uncompressed point)",
|
|
214
|
+
"nonce": "base64(12 bytes)",
|
|
215
|
+
"ciphertext": "base64",
|
|
216
|
+
"tag": "base64(16 bytes)",
|
|
217
|
+
"sender_signature": "base64(ECDSA-SHA256 over ciphertext+tag+aad_bytes)",
|
|
218
|
+
"sender_cert_fingerprint": "sha256:hex",
|
|
219
|
+
"aad": {
|
|
220
|
+
"from": "alice.agentid.pub",
|
|
221
|
+
"to": "bob.agentid.pub",
|
|
222
|
+
"message_id": "uuid",
|
|
223
|
+
"timestamp": 1710504000000,
|
|
224
|
+
"encryption_mode": "prekey_ecdh_v2",
|
|
225
|
+
"suite": "P256_HKDF_SHA256_AES_256_GCM",
|
|
226
|
+
"ephemeral_public_key": "base64",
|
|
227
|
+
"recipient_cert_fingerprint": "sha256:...",
|
|
228
|
+
"sender_cert_fingerprint": "sha256:...",
|
|
229
|
+
"prekey_id": "uuid"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 8.2 long_term_key 格式
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"type": "e2ee.encrypted",
|
|
239
|
+
"version": "1",
|
|
240
|
+
"encryption_mode": "long_term_key",
|
|
241
|
+
"suite": "P256_HKDF_SHA256_AES_256_GCM",
|
|
242
|
+
"ephemeral_public_key": "base64(X9.62 uncompressed point)",
|
|
243
|
+
"nonce": "base64(12 bytes)",
|
|
244
|
+
"ciphertext": "base64",
|
|
245
|
+
"tag": "base64(16 bytes)",
|
|
246
|
+
"sender_signature": "base64(ECDSA-SHA256 over ciphertext+tag+aad_bytes)",
|
|
247
|
+
"sender_cert_fingerprint": "sha256:hex",
|
|
248
|
+
"aad": {
|
|
249
|
+
"from": "alice.agentid.pub",
|
|
250
|
+
"to": "bob.agentid.pub",
|
|
251
|
+
"message_id": "uuid",
|
|
252
|
+
"timestamp": 1710504000000,
|
|
253
|
+
"encryption_mode": "long_term_key",
|
|
254
|
+
"suite": "P256_HKDF_SHA256_AES_256_GCM",
|
|
255
|
+
"ephemeral_public_key": "base64",
|
|
256
|
+
"recipient_cert_fingerprint": "sha256:...",
|
|
257
|
+
"sender_cert_fingerprint": "sha256:..."
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 8.3 AAD 字段
|
|
263
|
+
|
|
264
|
+
两种模式使用相同的 AAD 字段集(`prekey_id` 仅在 prekey_ecdh_v2 模式中存在):
|
|
265
|
+
|
|
266
|
+
| 字段 | 说明 |
|
|
267
|
+
|------|------|
|
|
268
|
+
| `from` | 发送方 AID |
|
|
269
|
+
| `to` | 接收方 AID |
|
|
270
|
+
| `message_id` | 消息唯一标识 |
|
|
271
|
+
| `timestamp` | 发送时间戳(ms) |
|
|
272
|
+
| `encryption_mode` | 加密模式 |
|
|
273
|
+
| `suite` | 算法套件 |
|
|
274
|
+
| `ephemeral_public_key` | 发送方临时公钥 |
|
|
275
|
+
| `recipient_cert_fingerprint` | 接收方证书公钥指纹 |
|
|
276
|
+
| `sender_cert_fingerprint` | 发送方证书公钥指纹 |
|
|
277
|
+
| `prekey_id` | Prekey 标识(仅 prekey_ecdh_v2) |
|
|
278
|
+
|
|
279
|
+
AAD 序列化方式(Canonical JSON for AAD):
|
|
280
|
+
|
|
281
|
+
1. **键排序**:所有对象(包括嵌套对象)的键 **MUST** 按 Unicode 码点升序排列(递归排序)
|
|
282
|
+
2. **紧凑格式**:键值对之间用 `,`,键和值之间用 `:` 分隔,无多余空白
|
|
283
|
+
3. **UTF-8 直接输出**:非 ASCII 字符(如中文)**MUST** 直接以 UTF-8 编码输出,**MUST NOT** 转义为 `\uXXXX`
|
|
284
|
+
4. **数值精度**:整数值 **MUST** 序列化为不带小数点的十进制数
|
|
285
|
+
5. **布尔值**:`true` / `false`(小写)
|
|
286
|
+
6. **空值**:`null`
|
|
287
|
+
|
|
288
|
+
等价的 Python 实现:`json.dumps(aad, sort_keys=True, separators=(",", ":"), ensure_ascii=False)`
|
|
289
|
+
|
|
290
|
+
> **跨语言实现要求:**
|
|
291
|
+
> - Go 的 `json.Marshal` 默认满足此规范(UTF-8 直接输出 + 自动键排序)
|
|
292
|
+
> - JavaScript/TypeScript 的 `JSON.stringify` 默认满足 UTF-8 直接输出,但 **MUST** 确保嵌套对象键递归排序
|
|
293
|
+
> - 所有语言 **MUST NOT** 将非 ASCII 字符转义为 `\uXXXX`(与客户端签名的 Canonical JSON 规范不同,见 Group E2EE §11.3.2)
|
|
294
|
+
>
|
|
295
|
+
> **注意**:此规范与客户端操作签名(Group E2EE §11.3.2)的 Canonical JSON 不同。签名哈希要求 `ensure_ascii=True`(`\uXXXX` 转义),因为签名由客户端生成、服务端验证,跨语言必须字节级一致。AAD 序列化用 UTF-8 直接输出,因为加密和解密在同一协议栈内完成,只需保证发送方和接收方使用相同规则。
|
|
296
|
+
|
|
297
|
+
AAD 匹配校验时 **MAY** 跳过 `timestamp`(服务端可能替换外层时间戳)。
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 9. Prekey 管理
|
|
302
|
+
|
|
303
|
+
### 9.1 接收方职责
|
|
304
|
+
|
|
305
|
+
- **MUST** 上线后上传 prekey
|
|
306
|
+
- **SHOULD** 定期轮换 prekey(建议每小时)
|
|
307
|
+
- **MUST** 用身份私钥签名 prekey:`sign("prekey_id|public_key_b64|created_at")`
|
|
308
|
+
- **MUST** 保留旧 prekey 私钥至少 7 天(解密在途消息)
|
|
309
|
+
- Prekey **SHOULD** 每小时轮换,最大有效期 30 天
|
|
310
|
+
|
|
311
|
+
### 9.2 服务端职责
|
|
312
|
+
|
|
313
|
+
- **MUST** 存储接收方最新的 prekey
|
|
314
|
+
- **MUST** 提供 `message.e2ee.get_prekey` 和 `message.e2ee.put_prekey` RPC
|
|
315
|
+
- Prekey 不消耗(读取不删除),由接收方主动覆盖更新
|
|
316
|
+
|
|
317
|
+
### 9.3 Prekey RPC
|
|
318
|
+
|
|
319
|
+
**上传 prekey**:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{"method": "message.e2ee.put_prekey", "params": {
|
|
323
|
+
"prekey_id": "uuid",
|
|
324
|
+
"public_key": "base64(DER SubjectPublicKeyInfo)",
|
|
325
|
+
"signature": "base64(ECDSA signature)",
|
|
326
|
+
"created_at": 1710504000000
|
|
327
|
+
}}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**获取对方 prekey**:
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{"method": "message.e2ee.get_prekey", "params": {"aid": "bob.agentid.pub"}}
|
|
334
|
+
// 返回: {"found": true, "prekey": {"prekey_id": "...", "public_key": "...", "signature": "...", "created_at": 1710504000000}}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 10. 重放保护
|
|
340
|
+
|
|
341
|
+
### 10.1 本地防重放
|
|
342
|
+
|
|
343
|
+
- 接收方 **MUST** 维护本地 `seen_messages` 集合
|
|
344
|
+
- 以 `{sender_aid}:{message_id}` 为 key 去重
|
|
345
|
+
- 同一 key 的消息 **MUST** 被拒绝
|
|
346
|
+
|
|
347
|
+
### 10.2 服务端防重放(可选增强)
|
|
348
|
+
|
|
349
|
+
- 接收方 **MAY** 调用 `message.e2ee.record_replay_guard` RPC 进行跨进程防重放
|
|
350
|
+
- 参数:`sender_aid`, `message_id`, `encryption_mode`, `timestamp_ms`, `ephemeral_pk_hash`
|
|
351
|
+
- 返回 `duplicate: true` 表示消息已被处理
|
|
352
|
+
|
|
353
|
+
> **注意**:Python SDK 当前仅在单条消息解密路径(推送/单条接收)上调用服务端 replay guard;`message.pull` 批量解密路径仅做本地批内去重,不逐条调用服务端。
|
|
354
|
+
|
|
355
|
+
### 10.3 防篡改
|
|
356
|
+
|
|
357
|
+
- 所有路由关键字段纳入 AAD,任何篡改导致 AEAD 解密失败
|
|
358
|
+
- 接收方 **MUST** 校验 AAD 中 `from`、`to`、`message_id`、`encryption_mode`、`suite`、`ephemeral_public_key`、`recipient_cert_fingerprint`、`sender_cert_fingerprint` 与 payload 一致
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 11. 发送方签名
|
|
363
|
+
|
|
364
|
+
### 11.1 签名要求
|
|
365
|
+
|
|
366
|
+
所有 E2EE 加密消息(prekey_ecdh_v2 和 long_term_key 模式)**MUST** 附带发送方签名。
|
|
367
|
+
|
|
368
|
+
### 11.2 签名载荷
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
sign_payload = ciphertext_bytes + tag_bytes + aad_bytes
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
其中 `aad_bytes` 为 AAD 的 JSON 序列化(`sort_keys=True, separators=(",",":")`)。
|
|
375
|
+
|
|
376
|
+
### 11.3 签名算法
|
|
377
|
+
|
|
378
|
+
使用发送方身份私钥(与 AID 证书绑定的密钥对)执行 ECDSA-SHA256 签名。
|
|
379
|
+
|
|
380
|
+
### 11.4 Envelope 字段
|
|
381
|
+
|
|
382
|
+
| 字段 | 说明 |
|
|
383
|
+
|------|------|
|
|
384
|
+
| `sender_signature` | base64 编码的 ECDSA-SHA256 签名 |
|
|
385
|
+
| `sender_cert_fingerprint` | 发送方证书公钥指纹,格式 `sha256:hex`,用于接收方查找证书验签 |
|
|
386
|
+
|
|
387
|
+
### 11.5 接收端行为
|
|
388
|
+
|
|
389
|
+
- 接收方 **MUST** 验证 `sender_signature`
|
|
390
|
+
- 缺少 `sender_signature` 的消息 **MUST** 被拒绝(返回解密失败)
|
|
391
|
+
- 验签失败的消息 **MUST** 被拒绝
|
|
392
|
+
- 接收方通过 `sender_cert_fingerprint` 查找本地缓存的发送方证书公钥进行验签
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 12. 安全约定
|
|
397
|
+
|
|
398
|
+
- 加密失败时 **MUST NOT** 静默降级为明文
|
|
399
|
+
- 临时密钥用完即丢
|
|
400
|
+
- Prekey 私钥 **SHOULD** 加密存储
|
|
401
|
+
- 每条消息使用独立的临时密钥对和 nonce
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## 13. 变更记录
|
|
406
|
+
|
|
407
|
+
| 版本 | 日期 | 变更 |
|
|
408
|
+
|------|------|------|
|
|
409
|
+
| 2.0-draft-r3 | 2026-04 | 明确前向保密默认策略;补充 server_replay_guard 覆盖范围说明 |
|
|
410
|
+
| 2.0-draft-r2 | 2026-04 | prekey_ecdh_v2 升级为四路 ECDH(§7.1);long_term_key 改为 2DH+HKDF 取代 ECIES(§7.2);新增发送方签名(§11);AAD 增加 sender_cert_fingerprint 和 prekey_id(§8.3);prekey 签名格式增加 created_at(§6.3, §9.1)|
|
|
411
|
+
| 2.0-draft-r1 | 2026-04 | 升级为 prekey_ecdh_v2(四路 ECDH);HKDF info 改为 `aun-prekey-v2:{prekey_id}` |
|
|
412
|
+
| 2.0-draft | 2026-04 | 简化为 prekey_ecdh_v2 + long_term_key 两级降级;移除在线协商(ephemeral_ecdh);移除会话状态机;E2EEManager 退化为纯工具类 |
|
|
413
|
+
| 1.0-draft | — | 初始版本,三级降级(ephemeral_ecdh + prekey + long_term_key) |
|